使用 Nim 实现进程空洞化注入

花费几天时间使用 nim 复刻了 ZwProcessHollowing,这个作者可能没自己编译过,源码有几个问题。

步骤

  1. 启动一个暂停的 notepad.exe
  2. 取消映像
  3. 申请 notepad.exe 映像区域的内存权限
  4. 将 test.exe 的区段写入
  5. 修复重定位区域
  6. 修改程序入口

实现情况

成功将 test.exe 注入挖空的 notepad.exe

后面尝试绕过了天擎

image

image

源码问题

两个问题全在这几行里面

1
2
3
4
5
6
// Source.cpp
if (RtlCopyMemory(sourceImgSection->Name, relocSectionName, 5) != 0)
{
sourceImgSection++;
continue;
}

第一个就是函数 RtlCopyMemory​ ,这里应该是比较内存,但是作者写成了复制内存,而且这个函数是没有返回值的。

第二个问题就是作者只在这里让区段遍历了,如果重定位区段后面还有区段,则会乱写内存,所以得在这个循环的最后面加个 sourceImgSection++;​ 也就是 for 循环的最后面。

注意事项

暂不支持 32 位,启动的进程必须是 64 位,注入的程序也必须是 64 位

我注入的 test.exe 是用 nim 写的,只是个弹窗程序

test.exe 编译的时候必须带 --app:gui​ 要不然会报错

源码

写的有点狗屎,就不贴出来了

不过可以提几个注意的点

数组取地址

c++ 的数组直接取值,是会取第一个元素的地址,nim 里就得手动取一下了,虽然也可以写个 converter​ 来自动转换,但只支持 seq​ 而不支持 array

回到代码举例,比方说这一行

1
PIMAGE_DOS_HEADER sourceDosHeader = (PIMAGE_DOS_HEADER)buf;

就可以等价替换成

1
var sourceDosHeader: PIMAGE_DOS_HEADER  = cast[PIMAGE_DOS_HEADER](buf[0].addr)

数组内存运算

跟上面差不多,不过就是将地址转成数字类型而已

1
PVOID sourceSectionLocation = (PVOID)((ULONG_PTR)buf + sourceImgSection->PointerToRawData);
1
sourceSectionLocation: PVOID = cast[PVOID](cast[ULONG_PTR](buf[0].addr) + sourceImgSection.PointerToRawData)

类型问题

源码里有个类型声明很独特

1
2
3
4
typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;

简单讲,就是只占了一个 USHORT​ 类型,但是高低位代表不同的信息。在 nim 没有这种操作,只能通过别的方式实现,下面直接贴源码

1
2
3
4
5
6
7
8
9
type
BASE_RELOCATION_ENTRY* = object
data*: USHORT

proc getOffset*(entry: BASE_RELOCATION_ENTRY): USHORT =
return (entry.data and 0x0FFF)

proc getType*(entry: BASE_RELOCATION_ENTRY): USHORT =
return (entry.data shr 12)

由于这里只牵扯了获取值,所以我们定义一个 USHORT​ 就行,然后获取值就自己位移。

画饼总结

关于 nim 的入门教程,以后可能有机会会写,不过说实话,现在卡巴也开始杀 winim 的那个资源文件了,360 目前不杀 relase​ 版,但是 --app:gui​ 杀完了。之后可能转 go 或者 c++。


Kernel-Process-Hollowing

ProcessHollowing

Process Hollowing and Portable Executable Relocations

ZwProcessHollowing (罪魁祸首)