您的位置:首页 > 编程语言

动态修改其它进程的代码实现DLL注入

2012-02-06 15:49 369 查看
 
传统的远程进程控制方式有利用HOOK技术注入DLL,和利用远线程在目标进程中建立新的执行线程的方式.

远线程不被win9x所支持,而hook技术会对目标进程性能造成一定的影响.并具可以通过枚举消息链的方式发现.

本文给出一种动态修改目标进程代码,注入DLL到目标进程的方法,效率高,不需要额外线程.缺点是使用难度大于上面二种办法,并且修改目标代码的方法,受到编译器的影响.使用不同的编译器时,需要根据编译器调整动态代码修改方式.

动态修改目标进程代码,使其加载自已的DLL的思路如下:

1. 得到目标进程句柄.

2. 在目标进程中分配一块可执行可读写的内存.

3. 写入加载DLL的代码到分配出来的内存中.

4. 修正动态写入的代码中的地址(LoadLibrary地址,DLL名字地址).

5. 修改目标进程本身的代码,使其执行到被修改代码时,跳到加载DLL的代码处.

6. 加载完DLL后,修正目标进程原来的代码,并跳回去继续执行.

这个过程中需要注意的地方是. 对寄存器的保护,堆栈的平衡.

其中第5步需要修改一块目标进程一定会执行的代码,消息循环是个不错的地方.这里以记事本为例.

打开ollydbg,在TranslateMessage函数下断点,中断后如下:

010029FC   |. /75 14              |jnz short notepad.01002A12

010029FE   |. |8D45 E0            |lea eax,dword ptr ss:[ebp-20]

01002A01   |. |50                 |push eax                             ; /pMsg

01002A02   |. |FF15 98120001      |call dword ptr ds:[<&USER32.Translat>; /TranslateMessage

01002A08   |. |8D45 E0            |lea eax,dword ptr ss:[ebp-20]

01002A0B   |. |50                 |push eax                             ; /pMsg

01002A0C   |. |FF15 94120001      |call dword ptr ds:[<&USER32.Dispatch>; /DispatchMessageW

01002A12   |> /56                  push esi

01002A13   |.  56                 |push esi

01002A14   |.  8D45 E0            |lea eax,dword ptr ss:[ebp-20]

01002A17   |.  56                 |push esi

01002A18   |.  50                 |push eax

01002A19   |.  FFD7               |call edi

0x01002A02处是个不错的地方,可以动态修改它. 改成

mov eax, 加载dll的代码地址

jmp eax

需要注意的是,此程序基址在不同的系统上有可能不一样,可以从PE头中得到映像基址.所以此处的0x1002A02也许在你的机器上是另一个地址,请使用OD自行确定.(我是winxp sp2)

找到修改点后,然后就是分配可执行可读写内存,写入下面一段代码进去

    pusha

    //eax中是LoadLibrary函数地址,暂时为0xffffffff

    //由程序动态改成真实函数地址

    mov eax, 0xffffffff

    //要加载的DLL的名字地址,暂时为0xffffffff

    //由程序动态改成真实地址

    mov ebx, 0xffffffff

    push ebx

    //调用LoadLibrary,加载自已的DLL

    //这样DLL就注入到目标进程了

    call eax           

    popa

    //恢复 0x1002A02处的代码,并跳回去执行

    //选执eax寄存器是因为0x1002A02后面的代码没有直接使用到eax

    mov eax, CODE_OFFSET

    mov dword ptr [eax], 0x129815FF

    mov dword ptr [eax+4], 0x458D0100  

    jmp eax

下面的代码是在加载DLL后,修正目标进程原有的代码

    mov eax, CODE_OFFSET

    mov dword ptr [eax], 0x129815FF

    mov dword ptr [eax+4], 0x458D0100 

0x129815FF 0x458D0100 即为原有代码(字节顺序是倒过来的, 0x129815FF 倒过来即是 FF159812, 0x458D0100倒过来即是00018D45,请注意和下面代码对比

01002A02   |. |FF15 98120001      |call dword ptr ds:[<&USER32.Translat>; /TranslateMessage

01002A08   |. |8D45 E0            |lea eax,dword ptr ss:[ebp-20]

其它基本上就是编码了,例子如下,测试时,请自行编写一个 Test.dll ,放在记事本同一个目录下,不同的编译器,不同的系统请自行修正一些细节:(我是使用的Release模式,Debug模式代码会不同的)

#include <iostream>

#include <windows.h>

#include <psapi.h>

using std::cout;

using std::cin;

using std::endl;

//记事本的映象基址,不同的系统可能不一样

//这个值可以从PE头中读取

#define NOTEPAD_IMAGE_BASE     0x1000000;

#define CODE_OFFSET         0x1002A02;

//用来测试的DLL名

const char *dllname = "Test.dll";

void loaddll()

{

    __asm

    {

        pusha

        //eax中是LoadLibrary函数地址,暂时为0xffffffff

        //由程序动态改成真实函数地址

        mov eax, 0xffffffff

        //要加载的DLL的名字地址,暂时为0xffffffff

        //由程序动态改成真实地址

        mov ebx, 0xffffffff

        push ebx

        //调用LoadLibrary,加载自已的DLL

        //这样DLL就注入到目标进程了

        call eax           

        popa

        //恢复 0x1002A02处的代码,并跳回去执行

        //选执eax寄存器是因为0x1002A02后面的代码没有直接使用到eax

        mov eax, CODE_OFFSET

        mov dword ptr [eax], 0x129815FF

        mov dword ptr [eax+4], 0x458D0100  

        jmp eax

    }

}

#pragma pack(push) //保存对齐状态

#pragma pack(1)    //设定为1字节对齐

struct JmpCode

{

    byte op;

    char* address;

    WORD jmp;

};

#pragma pack(pop)

int main()

{

    //找记事本主窗口句柄

    HWND wnd = FindWindow(NULL, "无标题 - 记事本");

    if (!IsWindow(wnd))

    {

        cout << "记事窗口没找到" << std::endl;

        return 1;

    }

    //读取进程ID,和进程句柄

    DWORD pid;

    HANDLE ph;

    GetWindowThreadProcessId(wnd, &pid);

    ph = OpenProcess(PROCESS_ALL_ACCESS, false, pid);

    //在记事本进程中分配一页可读写可执行的虚拟内存

    void *address = VirtualAllocEx(ph, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    //写代码的地址(写在字符串后面)

    char *code = (char*)address + strlen(dllname) + 1;

    if (NULL == address)

    {

        cout << "分配远端内存失败" << std::endl;

        return 1;

    }

    DWORD ws;

    //把代码写到内存页中

    WriteProcessMemory(ph, code, &loaddll, 4000, &ws);

    //把DLL名写到内存页中

    WriteProcessMemory(ph, address, dllname, strlen(dllname), &ws);

    //把loadlibrary的地址写到eax 0xfffffff处,把0xffffffff替换为真正的地址

    //code+9为eax 0xffffffff中0xffffffff处的偏移,通过反汇编工具查看+ 9是为了跳过

    //编译器给loadll函数生成的框架

    HMODULE module = LoadLibrary("kernel32.dll");

    FARPROC pro = GetProcAddress(module, "LoadLibraryA");

    WriteProcessMemory(ph, code + 9, &pro, 4, &ws);

    //把dllname的地址写到ebx 0xffffffff处,把dllname作为参数

    WriteProcessMemory(ph, code + 14, &address, 4, &ws);

    //修改记事本消息循环处的代码.

    JmpCode jmp;

    jmp.op = 0xB8;

    jmp.address = code + 6;                //这段代码是 mov eax, 地址

    jmp.jmp = 0xE0FF;                    //jmp eax

    address = (void*)CODE_OFFSET;

    //让代码段可读写

    VirtualProtectEx(ph, address, 4096, PAGE_EXECUTE_READWRITE, &ws);

    //改写目标处的代码,让其跳到自已的代码处执行

    WriteProcessMemory(ph, address, &jmp, 8, &ws);

    return 0;

}

运行,测试,OK! Test.dll被正确加载,目标进程现在是你的了,随便玩吧.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息