Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

qi4L/Unhooker-go

Repository files navigation

✅磁盘覆盖脱钩

假设Ntdll已经被挂钩, 取消挂钩 DLL 的过程如下:

  1. 将 ntdll.dll 的新副本从磁盘映射到进程内存
  2. 查找挂钩的 ntdll.dll 的 .text 部分的虚拟地址
    • 获取 ntdll.dll 基地址
    • 模块基址 + 模块的 .text 部分 VirtualAddress
  3. 查找新映射的 ntdll.dll 的 .text 部分的虚拟地址
  4. 获取挂钩模块的 .text 部分的原始内存保护
  5. 将 .text 部分从新映射的 dll 复制到原始(挂钩的)ntdll.dll 的虚拟地址(在第 3 步中找到)——这是取消挂钩的主要部分,因为所有挂钩的字节都被磁盘中的新字节覆盖
  6. 将原始内存保护应用到原始 ntdll.dll 的刚脱钩的 .text 部分

这个方法理论上可以应用于其他dll。

✅Threadless Process Injection

来自 BsidesCymru 2023 演讲 Needles Without the Thread

✅动态API解析

如果使用IDA或者x64debug之类的工具看自己编写的马,很容易发现,不论是API函数还是DLL,很轻松就可以找到。

那么就可以使用一些算法来让API函数与DLL,不是明文的出现在代码中(这里推荐 djb2)。

其次通过GetProcAddress的方式,来获取函数地址,然后指向函数指针,以这种方法可以规避一定的EDR产品。

我并没有归纳所有调用方式,但会给出一个示例:

dll1 = djb2md5.API(dll1)
funcAdd = djb2md5.API(funcAdd)
hNtdll, err1 := syscall.LoadLibrary(dll1)
if err1 != nil {
  log.Fatal(err1, " LoadLibrary")
}
ret, err2 := syscall.GetProcAddress(hNtdll, funcAdd)
if err2 != nil {
  log.Fatal(err2, " GetProcAddress")
}
addr, _, _ = syscall.SyscallN(GetProcAddressHash("6967162730562302977", "5569890453920123629"), uintptr(0), uintptr(len(pp1)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)

✅直接系统调用

在最新的CS4.8也集成了这种调用方式,但那是开箱即用的,这很不好,对于杀软方很容易就可以打标。

且大多数 EDR 产品将在用户态下挂钩 win32 api 调用。

这里给出泛用的GO汇编示例:

from GO

// func Syscall(callid uint16, argh ...uintptr) (uint32, error, error)
TEXT	·WinApiSyscall(SB),NOSPLIT,$168-64
	NO_LOCAL_POINTERS
	CALL	runtime·entersyscall<ABIInternal>(SB)
	MOVQ	trap+0(FP), BP	// syscall entry
	// copy args down
	LEAQ	a1+8(FP), SI
	LEAQ	sysargs-160(SP), DI
	CLD
	MOVSQ
	MOVSQ
	MOVSQ
	SYSCALL
	MOVQ	AX, r1+32(FP)
	MOVQ	$0, r2+40(FP)
	CMPL	AX, $-1
	JNE	ok3

	LEAQ	errbuf-128(SP), AX
	MOVQ	AX, sysargs-160(SP)
	MOVQ	$128, sysargs1-152(SP)
	MOVQ	$SYS_ERRSTR, BP
	SYSCALL
	CALL	runtime·exitsyscall(SB)
	MOVQ	sysargs-160(SP), AX
	MOVQ	AX, errbuf-168(SP)
	CALL	runtime·gostring(SB)
	LEAQ	str-160(SP), SI
	JMP	copyresult3

ok3:         // 返回值
	CALL	runtime·exitsyscall(SB)
	LEAQ	·emptystring(SB), SI

copyresult3: // 错误
	LEAQ	err+48(FP), DI

	CLD
	MOVSQ
	MOVSQ

	RET

from ScareCrow

TEXT ·Allocate(SB),$0-56
		XORQ AX,AX
        MOVW callid+0(FP), AX
        MOVQ PHandle+8(FP), CX 
        MOVQ SP, DX 
        ADDQ $0x48, DX
        MOVQ $0,(DX)
        MOVQ ZeroBits+35(FP), R8
        MOVQ SP, R9 
        ADDQ $40, R9
        ADDQ $8,SP
        MOVQ CX,R10
        SYSCALL
        SUBQ $8,SP
        RET

//Shout out to C-Sto for helping me solve the issue of  ... alot of this also based on https://golang.org/src/runtime/sys_windows_amd64.s
#define maxargs 8
//func Syscall(callid uint16, argh ...uintptr) (uint32, error)
TEXT ·NtProtectVirtualMemory(SB), $0-56
	XORQ AX,AX
	MOVW callid+0(FP), AX
	PUSHQ CX
	MOVQ argh_len+16(FP),CX
	MOVQ argh_base+8(FP),SI
	MOVQ	0x30(GS), DI
	MOVL	$0, 0x68(DI)
	SUBQ	$(maxargs*8), SP
	MOVQ	SP, DI
	CLD
	REP; MOVSQ
	MOVQ	SP, SI
	SUBQ	$8, SP
	MOVQ	0(SI), CX
	MOVQ	8(SI), DX
	MOVQ	16(SI), R8
	MOVQ	24(SI), R9
	MOVQ	CX, X0
	MOVQ	DX, X1
	MOVQ	R8, X2
	MOVQ	R9, X3
	MOVQ CX, R10
	SYSCALL
	ADDQ	$((maxargs+1)*8), SP
	POPQ	CX
	MOVL	AX, errcode+32(FP)
	MOVQ	0x30(GS), DI
	MOVL	0x68(DI), AX
	MOVQ	AX, err_itable+40(FP)
	RET

缺点:汇编代码在 Windows 操作系统版本之间的某些点上是不同的,有时甚至在服务包/内置编号之间也是不同的。

✅间接系统调用

通过函数地址 + syscall.SyscallN的方式来调用API,就是间接系统调用。

✅Patch Inline Hooking

通过应用正确的函数调用,重新钩住被钩住的函数。

这个方法理论上可以应用于其他函数。

❌Export Address Table (EAT)

间接调用通常是结合使用 GetModuleHandle 和 GetProcAddress来解析系统调用的地址。另一种方式是在进程环境块(PEB)中手动定位NTDLL.dll,通过解析导出地址表(EAT)找到系统调用。

如果使用内存中已有的 NTDLL 基地址,这将不会绕过任何系统调用的 UM 挂钩。但是GO是不会自己把dll加载进去的,所以有效,但是要记得去卸载DLL。

❌Dual-load 1 (Section)

KnownDlls是对象命名空间中的一个目录,其中包含进程加载的最常见 DLL 的部分对象。

存储在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs下。

它通过减少可执行文件的加载时间来提高性能,并且可以通过打开节名\KnownDlls\ntdll.dll将 NTDLL 的新副本映射到进程中。

重新加载之后,我们就相当于获取了一个未被hook的ntdll对象,就可以使用其中的syscall方法了。

有些产品不会挂钩 NtMapViewOfSectionEx,但这仅在 Windows 10 1803 之后可用。 上面的代码把NtMapViewOfSection换成NtMapViewOfSectionEx。

✅NativePayload

一个简单的思路,延时 + RWX 更改为 X 或 RX ,来源

✅未公开的API

逆向DLL中的未公开的API,只要序列号没有函数名的,然后直接系统调用传参即可。 这样的API很可能不在EDR的名单上可以过。

缺点:效果好成本也大,且实战环境多样,在win10 dll中找到的未公开API,win7 和 windows servse中很可能无。

✅BlockOpenHandle

阻止任何进程打开你的进程的句柄,只允许 SYTEM 打开你的进程的句柄,这样就可以避免远程内存扫描器

🦚unhook库

推一个unhook库,看函数名就知道集成了些unhook技巧。 可以从磁盘或者内存中加载函数,但是系统调用自己去定义,他只定义了一部分系统调用,所有调用有些函数时候就会出现莫名其妙的错误。

🦜TODO

  • 更新更多的EDR绕过技术;

参考