2

I have the following lines:

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

So, nSize is then used for the call of VirtualAllocEx. At nullsub_1, I have the follwoing(notice I use IDA PRO):

nulsub_1 proc near
retn
nullsub_1 endp

Question 1 : What is that ? Has somebody an idea what this can be ? I would say, it is a pointer to NULL but I am not sure.

At loc_402B58 I have the following(It is the same what I posted in another topic, I thought it would be helpful when I also post this Question about process injection):

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

How can we subtract pieces of codes which is done at sub [ebp+nSize], offset loc_402B58 ? I have never seen a piece of code like nullsub_1 . So I hope somebody can tell me what is it but i understand what the code at loc_402B58 does.

3

Assume you're a C programmer who has written a malware subroutine, and you want to copy your subroutine to somewhere "safe" where it remains after your starter process terminates. Then you'd probably start with something like

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

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

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

Now, how can you copy the compiled code of these functions somewhere, especially to some memory block you got using VirtualAlloc(), and how do you know how much memory you need? That's exactly what your code does. At the end of the (one or more) functions that need to stay "resident" in memory, you place a function that does nothing, just to have a memory address.

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

Then, you know that the memory block from malware() to end_of_malware() contains the code you want to "save". The expression

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

is the number of bytes you need, and which you pass to VirtualAlloc and memcpy() (or whichever function you use to copy the memory). This is just what the mov [somewhere], offset end_of_malware and sub [somewhere], malware does. Except that IDA can't know the original names of the functions.

IDA detects, however, that the end_of_malware function does nothing, so it names it nullsub - a "null subroutine". This helps a bit since, whenever you see a call nullsub_* in your disassembly. you know there's no reason to check what that function does.

| improve this answer | |
  • wow, thank you for your explanation. It helps a lot. – user3097712 Oct 20 '14 at 11:54
0
 ... 
 mov [ebp+nSize], offset nullsub_1
 ...
 sub [ebp+nSize], offset loc_402B58

The code above is effectively: nSize = address_of(nullsub_1) - address_of(loc_402B58)

Assuming the function nullsub_1 is immediately after loc_402B58, nSize is getting set to the length of the code at loc_402B58. This dynamic code length calculation is often used with VirtualAllocEx() so that the right amount of memory can be allocated in a remote process before the code is copied into that process.

| improve this answer | |
  • thank you for your explanation. It is always good to learn new things. – user3097712 Oct 20 '14 at 11:56
0

A bit of Trivia to add to Jason and Guntam's answers.

Ida normally seems to name the function nullsub_XX only when the function body contains a single return and this single return cannot be achieved by normal programming style
if you write a c program like void foo(void){}; this would create a prolog and epilog which Ida Would name sub_XXXXXXX not nullsub_XX most of the malwarion contructs use assembly programming so a single return can be assembled in place.

if you want to think c style you may need to ensure no prolog and epilog are generated by using __declspec(naked) this invariably involves inline assembly and an embedded _retn.

a sample code below will generate nullsub_xx when compiled in msvc with the given commandline

#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);
}

compiled and linked with

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

if optimizations are enabled compiler will discard func2size() because it is unreferenced
compiler will coalesce both the label functions into one single retn opcode because compiler will notice that both the function body are same only the names are differnt which doesnt make any difference in execution

enter image description here

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.