逆向学习笔记之dll注入

阅读量    125902 | 评论 5

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

概念

DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载用户指定的DLL文件。DLL注入与一般DLL加载的区别在于,加载的目标进程是自身或其他进程。

可以简单理解为把你想执行的代码写在dll文件里,然后注入目标进程执行代码。

 

具体场景

dll的应用场景有很多,比如改善功能与修复bug,消息钩取,API钩取,恶意代码等场景,这里我将dll注入用于黑盒测试,方便理解dll注入的功能。

在一些比较复杂的程序里,特别是一些进行大量代码混淆的程序里,我们静态分析往往是很困难的,动态调试多种不同输入又十分耗时耗力,这种情况下黑盒测试就成了分析函数的好办法。

这里方便理解,我编写了一个特别简单的验证输入的程序:

main.c:

// clang -c main.c -o main.o
#include <stdio.h>
#include <stdlib.h>

int check(char n);

int main()
{
    char string[10];
    int answer[4] = {1, 2, 3, 4};
    puts("plz input something:");
    scanf("%s", string);
    int i;
    int result = 0;
    for (i = 0; i < 4; i++)
    {
        if (check(string[i]) == answer[i])
        {
            continue;
        }
        else
        {
            result = 1;
            break;
        }
    }
    if (result)
    {
        puts("wrong!");
    }
    else
    {
        puts("right");
    }
    system("pause");
    return 0;
}

check.c:

// clang -c -mllvm -fla check.c -o check.o
int check(char n){
    if(n=='a'){
        return 3;
    }else if(n=='b'){
        return 123;
    }else if(n=='c'){
        return 456;
    }else if(n=='d'){
        return 789;
    }else if(n=='f'){
        return 1;
    }else if(n=='l'){
        return 2;
    }else if(n=='g'){
        return 4;
    }else{
        return -1;
    }    
}

链接生成main.exe

clang main.o check.o -o main.exe

通过源码我们可以清晰的看出我们需要输入的字符串为flag,check函数依次验证字符f,l,a,g,返回1,2,3,4,与answer数组做比较后验证成功,输出right。

但是check函数进行了控制流平坦化混淆,我们用ida64打开最后生成的main.exe文件看一下。

主函数main:

可以看到主函数逻辑还是非常清晰正确的,我们接下来跟进check函数。

验证函数check:

—分割线—-

check函数变得特别长,上面是我随意截的两张图,可以看到由于平坦化的处理,此时的check函数原本的验证字符功能已经很难分析出来,但是我们通过check函数的参数和返回值猜测这就是一个验证字符的函数。

至于具体的功能我们就需要进行黑盒测试,也就是通过dll注入来搞清楚了。

 

dll注入

跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。当使用LoadLibrary函数加载DLL时,系统会调用DLL的入口点函数。

函数定义如下:

BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL, // 指向自身的句柄
  _In_ DWORD fdwReason, // 调用原因
  _In_ LPVOID lpvReserved // 隐式加载和显式加载
);

接下来我们编写要注入的dll。

inject.c:

#include "pch.h"
#include <windows.h>
#include <stdio.h>

typedef int (*FUN)(char);

void printcheck()
{  
    // 获得基地址
    HMODULE baseaddr = GetModuleHandle(NULL);
    // 获得check函数地址
    FUN check = (FUN)((uintptr_t)baseaddr + 0x16A0);
    // printf("%llxn", (uintptr_t)baseaddr);
    // 进行黑盒测试
    int i;
    for (i = 0; i < 256; i++)
    {
        printf("(%d, %d)n", i, check(i));
    }
}

BOOL WINAPI DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved)
{
    puts("infect success!!");
    printcheck();
    return TRUE;
}

我这里是用vs2019编译的release版本x64程序。

当要注入dll被目标进程成功加载后,会调用DllMain,首先输出提示信息,然后调用printcheck函数。

在printcheck函数中,首先调用GetModuleHandle函数返回本模块的句柄,其实就是我们熟知的默认加载地址0x400000。

然后加上函数偏移,获得check函数的地址。函数偏移可以在ida的汇编窗口中查看:

最后就是一个256次循环对check函数进行黑盒测试。

注入

在网上下载dll注入工具进行注入,我用的是Xenos,这里附上GitHub链接。

可以看到注入后的结果:

我们可以获得一张check函数的传入参数,返回值的映射表,往下翻即可找到返回1,2,3,4所对应的输入:

前面的数字为正确字符flag对应的ASCII码。

至此,我们就通过dll注入实现了对check验证函数的黑盒测试,帮助我们快速的分析清楚了check函数的功能。

验证结果

我们重新运行程序,输入flag查看输出结果:

可以看到验证通过,输出right。

 

原理

实现dll注入的方法有很多,比如创建远程线程,使用注册表,消息钩取,替换原dll等。

这里简单介绍一下最常用的方法,通过创建远程线程,即使用CreateRemoteThread函数对运行中的进程注入dll。

大致的流程如下:

获取目标进程句柄

HANDLE hProcess = NULL;
//使用dwPID进程id获取目标进程句柄(然后控制进程)
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);

将dll写入目标进程分配的内存中

HANDLE hProcess;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize;
// 在目标进程内存中分配dwBufSize大小的内存
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);//返回值为分配所得缓冲区的地址(目标进程内存地址)
// 将dll写入分配的内存中
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

获取LoadLibraryW() API的地址

HMODULE hMod = NULL;
hMod = GetModuleHandle(L"kernel32.dll");
LPTHREAD_START_ROUTINE pThreadProc;
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

在进程中运行LoadLibraryW线程

HANDLE hProcess = NULL, hThread = NULL;
// 在进程中运行LoadLibraryW线程
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);    //等待hThread事件执行完毕

通常情况下利用好工具就可以实现我们想要的dll注入,不过学习原理能够帮助我们理解,同时一些重要的函数也是逆向学习过程中一定要积累的。

 

参考:

《逆向工程核心原理》书籍

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多