roger 发表于 2020-9-1 11:05:43

【windows代码注入教程】第六课 代码注入(汇编语言)

  这节课的目标是把上节课的ThreadProc函数通过纯汇编语言注入到notepad.exe进程
  等会要用到内联汇编,将汇编指令插入到C语言代码中,使用的工具可以是MASM,这里为了方便起见,我使用OllyDbg的汇编命令编写汇编代码
  首先随便拿一个程序当做我们写汇编代码的载体,这里使用asmtest.exe当载体,载入od后,goto到401000写代码
  把401000设为eip(下一条执行的指令)
  取消“使用nop填充的功能”,如果选中的话,输入代码的长度短于已有代码时,剩余长度会填充为nop指令(No Operation),以整体对齐代码
  下面使用汇编语言写ThreadProc()函数,与上节课使用C语言编写的ThreadProc()函数相比,其不同之处在于,需要的字符串包含在Code中(参数都在代码段),这里要注意,代码段一般情况下只有可读权限,如果强制写数据,需要通过PE头修改代码段的权限。
  以上就是完整的代码,现在保存到可执行文件(asmtest_patch.exe)
  下面将刚写的汇编代码通过C语言的混合注入到notepad.exe中
  然后保存到文件
  把每个16进制前面加上0x 后面加上 逗号
  用Word打开txt文件,然后用Ctrl+H打开替换对话框,勾选使用通配符,在查找中输入{2},替换为中输入0x^&, 单击全部替换即可。
  转换完成了
  下面看一下注入程序的源码

#include "windows.h"

#include "stdio.h"



typedef struct _THREAD_PARAM

{

    FARPROC pFunc;               // LoadLibraryA(), GetProcAddress()

} THREAD_PARAM, *PTHREAD_PARAM;



BYTE g_InjectionCode[] =

{

0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00, 0x00, 0x68, 0x33, 0x32,

0x2E, 0x64, 0x68, 0x75, 0x73, 0x65, 0x72, 0x54, 0xFF, 0x16, 0x68, 0x6F, 0x78, 0x41,

0x00, 0x68, 0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73, 0x73, 0x54, 0x50, 0xFF,

0x56, 0x04, 0x6A, 0x00, 0xE8, 0x0C, 0x00, 0x00, 0x00, 0x58, 0x75, 0x65, 0x6E, 0x69,

0x78, 0x69, 0x61, 0x6E, 0x67, 0x00, 0x00, 0xE8, 0x14, 0x00, 0x00, 0x00, 0x77, 0x77,

0x77, 0x2E, 0x78, 0x75, 0x65, 0x6E, 0x69, 0x78, 0x69, 0x61, 0x6E, 0x67, 0x2E, 0x63,

0x6F, 0x6D, 0x20, 0x00, 0x6A, 0x00, 0xFF, 0xD0, 0x33, 0xC0, 0x8B, 0xE5, 0x5D, 0xC3

};



BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)

{

    TOKEN_PRIVILEGES tp;

    HANDLE hToken;

    LUID luid;



    if( !OpenProcessToken(GetCurrentProcess(),

                        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,

                        &hToken) )

    {

      printf("OpenProcessToken error: %u\n", GetLastError());

      return FALSE;

    }



    if( !LookupPrivilegeValue(NULL,         // lookup privilege on local system

                              lpszPrivilege,// privilege to lookup

                              &luid) )      // receives LUID of privilege

    {

      printf("LookupPrivilegeValue error: %u\n", GetLastError() );

      return FALSE;

    }



    tp.PrivilegeCount = 1;

    tp.Privileges.Luid = luid;

    if( bEnablePrivilege )

      tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;

    else

      tp.Privileges.Attributes = 0;



    // Enable the privilege or disable all privileges.

    if( !AdjustTokenPrivileges(hToken,

                               FALSE,

                               &tp,

                               sizeof(TOKEN_PRIVILEGES),

                               (PTOKEN_PRIVILEGES) NULL,

                               (PDWORD) NULL) )

    {

      printf("AdjustTokenPrivileges error: %u\n", GetLastError() );

      return FALSE;

    }



    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )

    {

      printf("The token does not have the specified privilege. \n");

      return FALSE;

    }



    return TRUE;

}



BOOL InjectCode(DWORD dwPID)

{

    HMODULE         hMod            = NULL;

    THREAD_PARAM    param         = {0,};

    HANDLE          hProcess      = NULL;

    HANDLE          hThread         = NULL;

    LPVOID          pRemoteBuf   = {0,};



    hMod = GetModuleHandleA("kernel32.dll");



    // set THREAD_PARAM

    param.pFunc = GetProcAddress(hMod, "LoadLibraryA");

    param.pFunc = GetProcAddress(hMod, "GetProcAddress");



    // Open Process

    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS,               // dwDesiredAccess

                                  FALSE,                            // bInheritHandle

                                  dwPID)) )                         // dwProcessId

    {

      printf("OpenProcess() fail : err_code = %d\n", GetLastError());

      return FALSE;

    }



    // Allocation for THREAD_PARAM

    if( !(pRemoteBuf = VirtualAllocEx(hProcess,                  // hProcess

                                       NULL,                      // lpAddress

                                       sizeof(THREAD_PARAM),      // dwSize

                                       MEM_COMMIT,                // flAllocationType

                                       PAGE_READWRITE)) )         // flProtect

    {

      printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());

      return FALSE;

    }



    if( !WriteProcessMemory(hProcess,                               // hProcess

                            pRemoteBuf,                        // lpBaseAddress

                            (LPVOID)&param,                         // lpBuffer

                            sizeof(THREAD_PARAM),                   // nSize

                            NULL) )                                 // lpNumberOfBytesWritten

    {

      printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());

      return FALSE;

    }



    // Allocation for ThreadProc()

    if( !(pRemoteBuf = VirtualAllocEx(hProcess,                  // hProcess

                                       NULL,                      // lpAddress

                                       sizeof(g_InjectionCode),   // dwSize

                                       MEM_COMMIT,                // flAllocationType

                                       PAGE_EXECUTE_READWRITE)) ) // flProtect

    {

      printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());

      return FALSE;

    }



    if( !WriteProcessMemory(hProcess,                               // hProcess

                            pRemoteBuf,                        // lpBaseAddress

                            (LPVOID)&g_InjectionCode,               // lpBuffer

                            sizeof(g_InjectionCode),                // nSize

                            NULL) )                                 // lpNumberOfBytesWritten

    {

      printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());

      return FALSE;

    }



    if( !(hThread = CreateRemoteThread(hProcess,                  // hProcess

                                       NULL,                        // lpThreadAttributes

                                       0,                           // dwStackSize

                                       (LPTHREAD_START_ROUTINE)pRemoteBuf,

                                       pRemoteBuf,               // lpParameter

                                       0,                           // dwCreationFlags

                                       NULL)) )                     // lpThreadId

    {

      printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());

      return FALSE;

    }



    WaitForSingleObject(hThread, INFINITE);



    CloseHandle(hThread);

    CloseHandle(hProcess);



    return TRUE;

}



int main(int argc, char *argv[])

{

    DWORD dwPID   = 0;



    if( argc != 2 )

    {

      printf("\n USAGE: %s <pid>\n", argv);

      return 1;

    }



    // change privilege

    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )

      return 1;



    // code injection

    dwPID = (DWORD)atol(argv);

    InjectCode(dwPID);



    return 0;

}  输入注入指令 成功的给notepad.exe添加了一个弹窗
  下面对注入过程进行逆向分析:
  首先用OD载入notepad.exe 并且运行起来
  然后OD选项-调试设置-中断于新线程
  现在注入代码,输入注入指令后,OD会中断
  如果出现下面这种情况,不用担心,只是OD把数据当成指令翻译了~~~~
  这时候只要删除分析就可以了
  现在开始逐行分析最关键的线程注入的过程
  1.生成栈帧(保证参数读取正确,函数结束时也方便清理堆栈空间)
  2.THREAD_PARAM结构体指针
  查看结体构指针在notepad.exe进程内存空间中分配的内存缓冲区地址
  3.拼接出user32.dll字符串

[*]压栈user32.dll字符串首地址(调用字符串)
  结构体定义如下

[*]调用LoadLibraryA(“user32.dll”)
  取esi中4个字节的数据,然后压栈下一条指令的地址,jmp到,最后再retn esp
  查看notepad.exe的user32.dll加载地址,发现和LoadLibraryA获取到的完全一致
  6.拼接MessageBoxA字符串

[*]调用GetProcAddress(hMod,“MessageBoxA”)
  Push esp 将MessageBoxA字符串压栈
  Push eax 将刚才获取到的user32.dll的加载地址压栈
  就是GetProcAddress的地址

[*]压入MessageBoxA的第四个参数

[*]压入MessageBoxA函数的第三个参数(xuenixiang)
  下面介绍 使用call指令将包含在代码间的字符串数据地址压入栈 的技术(非常巧妙)
  大家都知道call xx指令=push下一条指令地址+jmp xx
  首先,执行A2002E处的指令使得MessageBoxA的第四个参数0入栈,然后call A2003F,这时候call的下一行指令的地址A20033就会被压入栈顶,但A20033地址下的内容并不是指令,而是我们预先存储的字符串首地址(字符串指针),这个字符串如图所示为Xuenixiang(此时第三个参数也进栈了),然后jmp到A2003F。
  9.压入MessageBoxA函数的第二个参数(www.xuenixiang.com)
  和上面类似,利用call指令把A20044-A20056处的字符串指针A20044(第二个参数)压栈,然后jmp到A20058

[*]压入MessageBoxA函数的第一个参数
  将第一个参数压栈,最后执行call eax (eax是前面获取到的MessageBoxA函数地址)

[*]设置ThreadProc函数的返回值

[*]删除栈帧及函数返回
  写到这里我又一次感受到栈帧的强大之处(始终保持堆栈的变量读取平衡)
  DLL注入的全部教程到此结束,接下来会开始讲解API HOOK
            
页: [1]
查看完整版本: 【windows代码注入教程】第六课 代码注入(汇编语言)