天天看点

CreateRemoteThread 直接注入代码执行一文拾遗

    Three Ways to Inject Your Code into Another Process 一文中提到成功注入进程后,子类化窗口过程的一些技巧。作者分配了一块堆内存,然后将线程参数和线程函数前后紧挨着拷贝到这片堆内存中,最后CreateRemoteThread运行线程函数。作者的这种方式要获得线程参数只能通过远程线程自定位的方式。我这么懒惰可能直接把需要的参数当做CreateRemoteThread的参数一起传递给远程线程了,当然,虽然没验证过,但是应该可行。

    作者的文章被转载这么多次,他提到的技巧不尝试一下有点暴殄天物的感觉,于是就有了这篇文章。

    本文依然一步步走,一下子写出正确的代码感觉少了点挫败感,同时也比较符合人类进步的规律;再说这同时记录了自己犯下的错误。

    注意,工程设置中需要去掉增量连接和 /Gz选项

1)先上直观的代码,当然是运行出错的!

列表1:

#include <stdafx.h>
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
typedef HANDLE (*open)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
typedef BOOL (*read)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef BOOL (*write)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef DWORD (*func)(void*);
#define ERRERS 1
#define PATHLEN 64
#define PROLOGUE 0x23
#define FUNCOFFSET 0x44
#define STRUCTLEN sizeof(INJCODE)
#define OPENPROCESSPROITY PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ
struct INJCODE
{
  char filePath[PATHLEN];
  func func1; //writeFunc
};

void MinorFunc()
{
  __asm
  {
    int 0x03;
  }
}

void AfterMinorFunc()
{}

DWORD FuncV1(void* args)
{
  INJCODE* injCode;
  injCode = (INJCODE*)MinorFunc;
  injCode--;
        (injCode->func1)();
        return 0;
}

DWORD AfterMajorFunc(void* args)
{
  return 0;
}

INJCODE* injCode;

int main()
{
  printf("FuncV1:%p\n",FuncV1);
  DWORD pid,dwThreadId;
  DWORD writtenNum,readNum;
  INJCODE* injCode;
  FILE* fp = fopen("c:\\pid.txt","r+");
  assert(fp);
  fscanf(fp,"%d",&pid);
  fclose(fp);
  HANDLE remoteProgHd = OpenProcess(OPENPROCESSPROITY,FALSE,pid);

  DWORD funcLen = ((DWORD)AfterMajorFunc-(DWORD)FuncV1) + ((DWORD)AfterMinorFunc-(DWORD)MinorFunc);
  injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
        //本意是想给injCode->filePath赋值的结果:
        //读injCode变量,引起异常的访问方式
  strcpy(injCode->filePath,"abc");
        //同样会引起异常  
<pre name="code" class="cpp">        injCode->func1=FuncV1;      

return 0;}

代码初看没问题吧,调试运行,程序运行到

strcpy(injCode->filePath,"abc");      

必然出错,看windbg输出,C0000005,同时dt injCode分配的内存也有错误的提示:

CreateRemoteThread 直接注入代码执行一文拾遗

排查了很久,才找到根结所在:

injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);      

VirtualAllocEx成功返回,injCode指向的内存区(0x00000ac0)在目标进程中,同样的地址在本进程中可能未分配或者没有相应访问权限,如果访问会导致出错。在这段代码中,程序企图读本进程0ac0处的内存值,从windbg输出来看这片内存应该还未分配,因此出现异常。

记得injCode指向的是远程线程的内存区!因此对任何由VirtualAllocEx(RemoteHandle);分配的内存进行的读写操作只能借助Read/WriteProcessMemory来访问远程结构体变量中的各个成员

姑且称用VirtualAllocEx创建远程进程变量吧,便于理解~

2)上更正main函数版的,用正确的方法给injCode结构各变量赋值:

列表2)

#include <stdafx.h>
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
typedef HANDLE (*open)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
typedef BOOL (*read)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef BOOL (*write)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef DWORD (*func)(void*);
#define ERRERS 1
#define PATHLEN 64
#define PROLOGUE 0x23
#define FUNCOFFSET 0x44
#define STRUCTLEN sizeof(INJCODE)
#define OPENPROCESSPROITY PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ
struct INJCODE
{
  char filePath[PATHLEN];//+0x00
  func func1;//+0x40
  func func2;//+0x44
};

void MinorFunc()
{
  __asm
  {
    int 0x03;
  }
}

void AfterMinorFunc()
{}

DWORD MajorFunc(void* args)
{
  INJCODE* injCode;
  injCode = (INJCODE*)MajorFunc;
  injCode--;
  (injCode->func2)(NULL);
  return 0;
}

#if 0
DWORD FuncV2(void* args)
{
  INJCODE* injCode;
  __asm
  {
    call CurEip;
CurEip:
    pop eax;
    //指向writeFunc的起始
    sub eax,PROLOGUE;
    //指向堆分配的INJCODE起始
    sub eax,FUNCOFFSET;
    mov injCode,eax;
  }
  return 0;
}
#endif

DWORD AfterMajorFunc(void* args)
{
  return 0;
}

INJCODE* injCode;

int main()
{
  DWORD pid,dwThreadId;
  DWORD writtenNum,readNum;
  INJCODE* injCode;
  FILE* fp = fopen("c:\\pid.txt","r+");
  assert(fp);
  fscanf(fp,"%d",&pid);
  fclose(fp);
  HANDLE remoteProgHd = OpenProcess(OPENPROCESSPROITY,FALSE,pid);
  //主函数和被调用函数的长度,首先存放MajorFunc,后面紧跟着MinorFunc
  DWORD funcLen = ((DWORD)AfterMajorFunc-(DWORD)MajorFunc) + ((DWORD)AfterMinorFunc-(DWORD)MinorFunc);
  injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
  //INJCODE!+0x40 func1所在,存放主函数指针 
  DWORD Func1Addr = ((DWORD)((char*)injCode)+0x40);
  INJCODE!+0x44 func2所在,存放被调函数指针 
  DWORD Func2Addr = ((DWORD)((char*)injCode)+0x44);
  DWORD MajorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE));
  DWORD MinorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE)+((DWORD)AfterMajorFunc-(DWORD)MajorFunc));
  WriteProcessMemory(remoteProgHd,((char*)injCode)+0x00,"c:\\1.txt",strlen("c:\\1.txt"),&writtenNum);
  //injCode!func1=MajorFunc
  WriteProcessMemory(remoteProgHd,(char*)Func1Addr,&MajorFuncBase,sizeof(DWORD),&writtenNum);
  //injCode!func2=MinorFunc
  WriteProcessMemory(remoteProgHd,(char*)Func2Addr,&MinorFuncBase,sizeof(DWORD),&writtenNum);
  //复制函数到远程进程
  WriteProcessMemory(remoteProgHd,(char*)MajorFuncBase,MajorFunc,((DWORD)AfterMajorFunc-(DWORD)MajorFunc),&writtenNum);
  WriteProcessMemory(remoteProgHd,(char*)MinorFuncBase,MinorFunc,((DWORD)AfterMinorFunc-(DWORD)MinorFunc),&writtenNum);
  HANDLE remoteThread = CreateRemoteThread(remoteProgHd,NULL,0,(LPTHREAD_START_ROUTINE)((char*)MajorFuncBase),NULL,0,&dwThreadId);
  WaitForSingleObject(remoteThread, INFINITE);
  return 0; 
}      

编译运行,injCode变量中各个成员的值已经成功更新到目标进程了:

CreateRemoteThread 直接注入代码执行一文拾遗

可以看到b50000 +40 +44处分别是两个函数的地址值,再看下这两个地址上的函数:

CreateRemoteThread 直接注入代码执行一文拾遗
CreateRemoteThread 直接注入代码执行一文拾遗

可是运行CreateRemoteThread后,被注入的进程运行到call ecx后还是出错,why?

CreateRemoteThread 直接注入代码执行一文拾遗

回到代码MajorFunc中:

<pre name="code" class="cpp"> INJCODE* injCode;
  injCode = (INJCODE*)MajorFunc;
  injCode--;      

对应的Opcode为:

列表3)

mov     dword ptr [ebp-4],401030h
mov     eax,dword ptr [ebp-4]      

MajorFunc被翻译为401030,绝对地址,更重要的,401030是在注入进程的地址空间中,目标进程的地址空间中未必有,因此会导致访问出错。代码运行在其他进程空间中,而且地址不确定,怎么才能获得injCode的地址?这就用到线程自定位了,远程线程获得当前指令的内存地址,减去偏移量,最终定位到injCode的起始地址。因为线程运行的函数位于injCode分配的内存空间中,且紧挨着injCode变量,修改修改偏移找个起始地址还不简单。

列表4) 终结版:

// redirect.cpp : Defines the entry point for the console application.
//
#include <stdafx.h>
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
typedef HANDLE (*open)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
typedef BOOL (*read)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef BOOL (*write)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef DWORD (*func)(void*);
#define ERRERS 1
#define PATHLEN 64
#define PROLOGUE 0x0e
#define FUNCOFFSET 0x48
#define STRUCTLEN sizeof(INJCODE)
#define OPENPROCESSPROITY PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ
struct INJCODE
{
  char filePath[PATHLEN];//+0x00
  func func1;//+0x40
  func func2;//+0x44
};

void MinorFunc()
{
  __asm
  {
    int 0x03;
  }
}

void AfterMinorFunc()
{}

DWORD MajorFunc(void* args)
{
  INJCODE* injCode;
  __asm
  {
    call CurEip;
CurEip:
    pop eax;
    //指向writeFunc的起始
    sub eax,PROLOGUE;
    //指向堆分配的INJCODE起始
    sub eax,FUNCOFFSET;
    mov injCode,eax;
  }
  (injCode->func2)(NULL);
  return 0;
}

DWORD AfterMajorFunc(void* args)
{
  return 0;
}

INJCODE* injCode;

int main()
{
  DWORD pid,dwThreadId;
  DWORD writtenNum,readNum;
  INJCODE* injCode;
  FILE* fp = fopen("c:\\pid.txt","r+");
  assert(fp);
  fscanf(fp,"%d",&pid);
  fclose(fp);
  HANDLE remoteProgHd = OpenProcess(OPENPROCESSPROITY,FALSE,pid);
  //主函数和被调用函数的长度,首先存放MajorFunc,后面紧跟着MinorFunc
  DWORD funcLen = ((DWORD)AfterMajorFunc-(DWORD)MajorFunc) + ((DWORD)AfterMinorFunc-(DWORD)MinorFunc);
  injCode = (INJCODE*)VirtualAllocEx(remoteProgHd,NULL,sizeof(INJCODE)+funcLen,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
  //INJCODE!+0x40 func1所在,存放主函数指针 
  DWORD Func1Addr = ((DWORD)((char*)injCode)+0x40);
  INJCODE!+0x44 func2所在,存放被调函数指针 
  DWORD Func2Addr = ((DWORD)((char*)injCode)+0x44);
  DWORD MajorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE));
  DWORD MinorFuncBase = ((DWORD)((char*)injCode)+sizeof(INJCODE)+((DWORD)AfterMajorFunc-(DWORD)MajorFunc));
  WriteProcessMemory(remoteProgHd,((char*)injCode)+0x00,"c:\\1.txt",strlen("c:\\1.txt"),&writtenNum);
  //injCode!func1=MajorFunc
  WriteProcessMemory(remoteProgHd,(char*)Func1Addr,&MajorFuncBase,sizeof(DWORD),&writtenNum);
  //injCode!func2=MinorFunc
  WriteProcessMemory(remoteProgHd,(char*)Func2Addr,&MinorFuncBase,sizeof(DWORD),&writtenNum);
  //复制函数到远程进程
  WriteProcessMemory(remoteProgHd,(char*)MajorFuncBase,MajorFunc,((DWORD)AfterMajorFunc-(DWORD)MajorFunc),&writtenNum);
  WriteProcessMemory(remoteProgHd,(char*)MinorFuncBase,MinorFunc,((DWORD)AfterMinorFunc-(DWORD)MinorFunc),&writtenNum);
  HANDLE remoteThread = CreateRemoteThread(remoteProgHd,NULL,0,(LPTHREAD_START_ROUTINE)((char*)MajorFuncBase),NULL,0,&dwThreadId);
  WaitForSingleObject(remoteThread, INFINITE);
  return 0; 
}      

注入后运行MinorFunc结果:

CreateRemoteThread 直接注入代码执行一文拾遗

跳转到0xb50078后:

CreateRemoteThread 直接注入代码执行一文拾遗

顺利的运行到MinorFunc中。