问题:识别 VirtualAllocEx 的大小

逆向工程 部件
2021-06-26 22:15:21

我有以下几行:

 ... 
 mov [ebp+nSize], offset nullsub_1
 ...
 sub [ebp+nSize], offset loc_402B58

因此,nSize 然后用于 VirtualAllocEx 的调用。在 nullsub_1,我有以下内容(注意我使用 IDA PRO):

nulsub_1 proc near
retn
nullsub_1 endp

问题1:那是什么?有人知道这是什么吗?我会说,它是一个指向 NULL 的指针,但我不确定。

在 loc_402B58 我有以下内容(这与我在另一个主题中发布的内容相同,我认为当我也发布这个关于进程注入的问题时会有所帮助):

loc_402B58:
  push esi
  mov esi, [esp+8]
  lea eax, [esi+14h]
  push eax
  call dword ptr[esi]
  test eax, eax
  mov [esi+10h], eax
  jz short loc_402B80
  lea ecx, [esi+46h]
  push ecx
  push eax
  call dword ptr [esi+4]
  test eax, eax
  jz short_loc402B80
  call eax
  push 0
  call eax
  push 0
  call dword ptr [esi+8]

  loc_402B80:     
   xor eax, eax
   pop esi
   retn 4

我们如何减去在sub [ebp+nSize], offset loc_402B58处完成的代码片段我从未见过像nullsub_1这样的代码所以我希望有人能告诉我它是什么,但我明白 loc_402B58 的代码是做什么的。

3个回答

假设您是一名编写了恶意软件子程序的 C 程序员,并且您想将您的子程序复制到某个“安全”的地方,在您的启动进程终止后它仍然存在。那么你可能会从类似的东西开始

void malware(void) {
    ......
    helper1(...);
    ......
    helper2(...);
    ......
}

void helper1(...) {
    .....
}

void helper2(...) {
    .....
}

现在,您如何将这些函数的编译代码复制到某个地方,尤其是您使用 VirtualAlloc() 获得的某个内存块中,您如何知道需要多少内存?这正是您的代码所做的。在需要“常驻”在内存中的(一个或多个)函数的末尾,放置一个什么都不做的函数,只是为了获得一个内存地址。

void end_of_malware(void) { /* do nothing, just return */ }

然后,您知道来自malware()to的内存块end_of_malware()包含您要“保存”的代码。表达方式

size_t size=((char *) end_of_malware - (char *)malware)

是您需要的字节数,以及您传递给VirtualAllocmemcpy()(或您用来复制内存的任何函数)的字节数这正是mov [somewhere], offset end_of_malwareandsub [somewhere], malware所做的。除了 IDA 无法知道函数的原始名称。

然而,IDA 检测到该end_of_malware函数不执行任何操作,因此将其命名nullsub为“空子例程”。这会有所帮助,因为每当您call nullsub_*在反汇编中看到 a时。您知道没有理由检查该函数的作用。

 ... 
 mov [ebp+nSize], offset nullsub_1
 ...
 sub [ebp+nSize], offset loc_402B58

上面的代码是有效的: nSize = address_of(nullsub_1) - address_of(loc_402B58)

假设该函数nullsub_1紧接在 之后loc_402B58nSize则设置为 处的代码长度loc_402B58经常使用这种动态代码长度计算,VirtualAllocEx()以便在将代码复制到远程进程之前,可以在远程进程中分配适当数量的内存。

一些琐事要添加到 Jason 和 Guntam 的答案中。

Ida normally似乎命名功能nullsub_XX仅在function body contains a single return这一个返回不能被实现的normal programming风格
,如果你喜欢写c程序void foo(void){};,这将创建一个prolog and epilog该井田将名称sub_XXXXXXX 不是 nullsub_XX大多数的malwarion contructs使用汇编编程所以单回路可以装配到位。

if you want to think c style您可能需要no prolog and epilog are generated通过使用__declspec(naked)来确保始终涉及inline assembly and an embedded _retn.

当使用给定的命令行在 msvc 中编译时,下面的示例代码将生成 nullsub_xx

#include <windows.h>
char info[] = {
    "this is the func whose block size needs to be calculated since "
    "the function is between two nullsub subtracting the address of "
    "them gets the size of block but be aware compilers are clever  "
    "and may do away all of this if compiled with optimsation /OXXX "
};
char buff[0x200] ={0};
__declspec(naked) void startlabel(void) {__asm{retn}};
void func2size(void) {
    wsprintf(buff,"%s\n",info);
    MessageBox(0,buff,"whats nullsub demo",0);
    return;
}
__declspec(naked) void endlabel(void) {__asm{retn}};
int WINAPI WinMain(HINSTANCE hi,HINSTANCE hpi,LPSTR cmd,int show) {
    wsprintf(buff,"blocksiz\t0x%08x\n",((char *)endlabel-(char *)startlabel));
    MessageBoxA(0,buff,"whats nullsub demo",0);
    ExitProcess(0);
}

编译并链接到

cl /nologo dynsize.c user32.lib kernel32.lib /link /ENTRY:WinMain /SUBSYSTEM:Windows dynsize.c

如果optimizations are enabled编译器将丢弃 func2size() ,因为它是unreferenced
编译器会将两个标签函数合并为一个 retn 操作码,因为编译器会注意到两个函数体相同,只是名称不同,这对执行没有任何影响

在此处输入图片说明