吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 21464|回复: 61
收起左侧

[原创] 【动态Dll注入】160个CrackMe之041

  [复制链接]
whklhh 发表于 2017-10-31 20:12
本帖最后由 whklhh 于 2017-11-2 02:57 编辑

静态注入法:https://www.52pojie.cn/forum.php ... &extra=#pid17678797

在最后添加了Dll注出功能的实现~               
=========================================11.2更新=======================================

dll注入的原理是利用一个进程控制被注入进程执行LoadLibrary函数,将外部dll注入其内存中。
注入以后由于共享内存空间,因此dll也就拥有了操作该进程内存的权限了。

当Dll被注入时会运行DllMain函数,这就是Dll的主函数。

由于之前没有写过Dll注入,因此这次准备先执行一个MessageBox作为Hook成功标志,明天再将前一次得到的成功经验照样通过dll注入来实现。

首先,Dll注入分为3个阶段:
1.外部函数控制被注入程序执行LoadLibrary函数加载外部Dll
2.Dll注入,执行DllMain函数
3.Hook住被注入程序的函数,当条件满足时触发Hook函数

依次完成,首先是外部函数:
[C++] 纯文本查看 复制代码
#include <iostream>
#include <windows.h>
#include <tchar.h>
using namespace std;

BOOL InjectDll(int dwPID, LPCTSTR szDllPath)
{
        HANDLE hProcess = NULL, hThread = NULL;
        HMODULE hMod = NULL;
        LPVOID pRemoteBuf = NULL;
        DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
        LPTHREAD_START_ROUTINE pThreadProc;
        // 获取目标进程句柄
        if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
        {
            cout<<dwPID<<endl;
            cout<<"failed!!!\n";
            return FALSE;
        }
        // 在目标进程内存中分配szDllName大小的内存
        pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
        // 将myhack.dll路径写入分配的内存
        WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
        // 获取LoadLibraryA地址
        hMod = GetModuleHandle("kernel32.dll");
        pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
        // 运行线程
        hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
        //等待该线程执行完毕
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
        CloseHandle(hProcess);
        return TRUE;

}


int main(int argc, TCHAR *argv[])
{
    if(InjectDll(atoi(argv[1]), "F:\\C\\cm41_hoook\\bin\\Debug\\cm41_hoook.dll"))
        cout<<"Success "<<argv[2]<<endl;
    else
        cout<<"Failed"<<endl;

    return 0;

}

参照书上代码,main函数接收被注入程序的PID,从而执行InjectDll函数
InjectDll函数通过PID获取该进程的句柄,然后将参数(外部Dll的路径)和函数(Kernel32.dll中的LoadLibrary函数)准备好,通过CreateRemoteThread函数来控制被注入进程开启一个新线程加载dll

注意这里LoadLibrary函数取的地址事实上是注入函数的,但由于dll共享内存,因此地址可以通用

注意LoadLibrary函数的W和A,刚开始在这里犯错了OTZ
W表示宽字符,A表示ASCII,默认字符串是ASCII的,因此使用A

PID通过任务管理器可以查看到

第二步编写DllMain函数:
[C++] 纯文本查看 复制代码
BOOL NewMenu()
{
    return (MessageBoxA(NULL, "Hook success", "Second", MB_OKCANCEL));

}

extern "C" DLL_EXPORT BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    MessageBoxA(NULL, "Dll inject success", "First", MB_OK);
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            hook_by_code((PROC)NewMenu);
            break;
    }
    return TRUE; // succesful
}

hook_by_code就是修改代码来hook的函数,NewMenu是hook目标函数
hook_by_code取得目标函数的地址作为参数,来修改代码:
[C++] 纯文本查看 复制代码
BOOL hook_by_code(PROC pfnNew)
{
    HANDLE hProcess;
    DWORD* pfnOrg;
    //push的机器码
    byte pBuf[6] = {0x68};
    DWORD oldProtect;
    //Hook地址
    pfnOrg = (DWORD*)0x430535;
    //将hook目标函数的地址送入缓冲区中
    memcpy(&pBuf[1], &pfnNew, 4);
    //ret的机器码
    pBuf[5] = 0xc3;
    //修改内存属性为可写
    VirtualProtect((LPVOID)pfnOrg, 10, PAGE_EXECUTE_READWRITE, &oldProtect);
    //将缓冲区写入被注入程序的内存
    memcpy(pfnOrg, pBuf, 6);
    return TRUE;
}

目前需要程序运行的内容为(汇编):
[Asm] 纯文本查看 复制代码
push 0xaaaaaaaa ;目标函数地址
ret ;call 目标函数地址


刚开始执行的时候花式报错,原来是data节区没有可写属性,因此需要用VirtualProtect函数开启该权限
hook的地址还是老地方:
  

这样注入以后,就能实现弹窗了


查到PID以后通过命令行执行程序:


看到弹出注入成功的弹窗,说明DllMain已经开始执行,Dll成功加载入该程序中


点击二级菜单后弹出Hook成功的弹窗,说明代码成功被Hook

虽然点完确定以后就会出现内存错误的警报,但这是预料之中的—因为我们只设置了Hook,没有令它返回嘛

以上是一天完成的内容,因此还有一些一步一步和实验性的东西
我觉得程序应该从小写起,从最基本的功能往上一点一点加,这样方便修BUG~

所以下面第二天内容,正式Hook:
首先因为编写代码语言是C++(其实用内联汇编代码写起来费事点,但是处理乱七八糟的堆栈和参数传递要省心的多),因此要注意参数问题
其次是Hook点

Hook函数的执行次序为
1.png

Hook函数应该尽可能地降低影响,来避免内存访问错误

是否要执行原函数由具体情况决定,为了降低对原程序的影响程度,最好执行
因为原函数内可能会执行一些对其他地方有影响的操作,不止是堆栈和寄存器的操作,还有一些全局变量操作等等

代码编写起来比较简单,主要是代码的改变,和hook函数的代码

为了在hook函数中执行原函数,有两种解决办法:
DLL调用exe里的函数,如果知道函数地址的话 2种方法
1 dll里用函数指针指向exe里的函数
比如exe里有个函数 int SetHook(HWND, HWND);
DLL里就这样
typedef int (CALLBACK* sthook)(HWND, HWND);
sthook SetHook;

SetHook = (sthook)0x00XXXXXX;//0x00XXXXXX是 SetHook的地址
然后DLL里就能用SetHook函数了

2 内联汇编
比如exe里有个函数 int SetHook(HWND, HWND); , 函数地址是0x00XXXXXX
DLL中
__asm {
push eax
mov eax, hwnd_arg2
push eax
mov eax, hwnd_arg1
push eax
mov eax, 0x00XXXXXX
call eax
add esp, 8
pop eax
}

写好代码以后原函数调用总是失败
发现原函数的参数传递是使用寄存器eax,ebx
  
而C++编译的函数是通过调用约定来提取参数的,存在于eax或堆栈中
也就是说C语言写的函数提不到ebx的值,即使是写在函数最顶端的内联汇编也由于编译而拿不到原始的ebx

如果Hook函数不需要判断的话,参数倒是无所谓。
但是本次Hook由于需要判断被点击的按钮是否为”Exit”来决定弹窗,因此必须拿到参数

最后没办法,想到了令被Hook函数在跳转前将参数保存到全局变量中的方法
即在push func_addr; retn之前 mov par_addr, eax
然后判断par即可

调试发现dll中使用C->函数指针的方法调用函数,实际上会占用寄存器:
  
这样的话eax参数就不可能送进去了╮(╯_╰)╭只能使用内联汇编的方法了
我使用的Code::Blocks编译器支持的是gcc格式的内联汇编,是AT&T记法,用起来比较麻烦
有空赶紧换VS编译器_(:з」∠)_VC格式的内联汇编(INTEL记法)才比较熟悉

[Asm] 纯文本查看 复制代码
    asm("movl %%eax, %0\n"
        "movl %%ebx, %1\n"
        "movl %%ecx, %2\n"
        "call %%ecx"
        :
        :"a"(eax),"b"(ebx),"c"(hook_addr)
        );


这样就将之前存储在变量中的值分别送入eax,ebx,ecx中了,然后call ecx就调用了原函数

我们的目标是为Exit按钮添加事件,事件内容很简单,关键在于如何判断Exit按钮
之前静态patch时使用的方法是Hook二级菜单的事件,判断edx(控件下标),由于这次Hook的是窗体事件的函数,控件下标edx已经丢失了,所以只能思考别的办法
eax中存储的是该控件结构体的指针,因此只要在其中找到Caption即可判断按钮。
内存中下断分析以后发现是0xe8的位置开始("&Exit"),因此只需要判断eax+0xe9的值是否为’E’即可

Dll代码如下:
[C++] 纯文本查看 复制代码
#include "main.h"

#define hook_addr 0x42f3c0

//mov edx, dl
//push addr
//ret
byte pBuf[20] = {0x89, 0x5,0,0,0,0,0x89, 0x1d, 0,0,0,0, 0x68};
byte OrgBytes[20];
int ebx;
int eax;
int* ebx_p;
int* eax_p;

BOOL hook_by_code(PROC pfnNew)
{
    DWORD* pfnOrg;
    DWORD oldProtect;

    pfnOrg = (DWORD*)hook_addr;
    ebx_p = &ebx;
    eax_p = &eax;

    memcpy(OrgBytes, pfnOrg, 18);
    memcpy(&pBuf[2], &eax_p, 4);
    memcpy(&pBuf[8], &ebx_p, 4);
    memcpy(&pBuf[13], &pfnNew, 4);
    //ret
    pBuf[17] = 0xc3;
    //修改内存属性为可写
    VirtualProtect((LPVOID)pfnOrg, 10, PAGE_EXECUTE_READWRITE, &oldProtect);

    memcpy(pfnOrg, pBuf, 18);
    return TRUE;
}

BOOL New_42f3c0()
{
    /*函数指针调用方法,由于eax传参而废弃
    typedef int (CALLBACK* func)();
    org_func = (func)another_func_addr;
    func org_func;
    org_func();
    */
    DWORD* pfnOrg;


    org_func = (func)hook_addr;
    byte oldBuf[7] = {0x53, 0x8b, 0xd8, 0x80, 0x7b, 0x2d, };
    pfnOrg = (DWORD*)hook_addr;
    //脱钩
    memcpy(pfnOrg, OrgBytes, 18);
    //送入参数并执行原函数
    asm("movl %%eax, %0\n"
        "movl %%ebx, %1\n"
        "movl %%ecx, %2\n"
        "call %%ecx"
        :
        :"a"(eax),"b"(ebx),"c"(hook_addr)
        );

    //check
    if(*(byte*)(eax + 0xe9)=='E')
        if(MessageBoxA(NULL, "Do you fickbirne really want to quit?", "Exit", MB_YESNO)==6)
            exit(0);
        else
            ;
    //再次挂钩
    memcpy(pfnOrg, pBuf, 18);
    return 0;

}

extern "C" DLL_EXPORT BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    //MessageBoxA(NULL, "Dll inject success", "First", MB_OK);
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            hook_by_code((PROC)New_42f3c0);
            break;
    }
    return TRUE; 
}

这样就可以通过Dll注入的方法来外部添加事件了,
相比起静态Patch而言这种方法要更加灵活
如果Hook的是API,参数也乖乖按照调用约定通过堆栈传递的话,C的代码写起来要比汇编方便许多

PS:
中间我也试过许多其他的方法,例如像静态Patch的方法一样Hook二级菜单的函数,也吃了不少教训。
例如说Hook点最好在函数开头,参数与该函数一致,这样可以尽可能地保持堆栈平衡
像静态Patch那样直接掐下来二级菜单的函数,通过id判断来执行两个事件而不执行原函数的方法对于该程序也是可以的,并且相对而言要简单不少
只不过方法来讲不是太完善,因为完全跳过了原函数,如果其中还有什么其他处理的话就会造成程序不稳定。
因此标准起见,应该在Hook函数内执行原函数。

明天再来继续完善这个程序,例如说可以注出Dll,还原patch;通过进程名直接注入defiler.1,而不用像现在这样每次都要检测pid;检测是否被注入过,进行错误处理,等等
附件中包含了原题、注入程序和Dll,为了方便实用需要自行写入pid和【完整】Dll路径

使用方法: 1.png


======================================================注出更新================================
今天主要需要完成的是根据进程名自动搜索pid、错误检查和注出Dll的功能首先是Pid的搜索功能
查了一下貌似没有什么比较方便的方法,只能暴力地遍历句柄取得进程名来对比给定的进程名了
比起暴力地遍历所有句柄来说,相对优雅一点的方式是获取进程快照,来一一对比:
(代码来自《逆向工程核心原理》“卸载Dll”一节和http://blog.csdn.net/jfkidear/article/details/27056955指导)

[C++] 纯文本查看 复制代码
int GetProcessIdByName(const char*ProcessName)
{
    PROCESSENTRY32 stProcess;
    HANDLE hProcessShot;
    stProcess.dwSize=sizeof(PROCESSENTRY32);
    //创建进程快照
    hProcessShot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
    //首个项
    Process32First(hProcessShot,&stProcess);
    //遍历快照,对比进程名和给定字符串
    do
    {
        if(!strcmp(ProcessName,stProcess.szExeFile))
            return stProcess.th32ProcessID;
    }while(Process32Next(hProcessShot,&stProcess));
    //若未找到进程则返回0,否则返回Pid
    CloseHandle(hProcessShot);
    return 0;
}


有了这个进程以后,我们的注入程序就不再需要自己去查看任务管理器来取得Pid了
内置进程名defiler.1.exe就可以直接找到Pid,虽然限制了可用性,不过本来Dll就是专用的~
所以为了提升便捷性而牺牲自由性是当然的嘛

错误检查主要就是各个函数的错误返回值加上log,没什么好说的,依次注意就好

本来按书上的走法,DllHookApi开头之前先要进行检查是否已经Hook过
不过自己写的代码检查总是失败,太晚了很困调试不动了囧
再加上,本来以为检查的原因是如果注入两次Dll造成重复Hook的话就会引起错误;但是在调试过程中突然想起,同一个Dll被一个进程装载以后,不会再装载第二次了。
因此这个担忧是不必要的~

于是就放弃Dll的自检查了,而是直接通过注入程序来增加注出功能:
(代码同样参考书上)
[C++] 纯文本查看 复制代码
BOOL Deject(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = {sizeof(me)};
    LPTHREAD_START_ROUTINE pThreadProc;
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for(;bMore ; bMore = Module32Next(hSnapshot, &me))
    {
        if(!_tcsicmp((LPCTSTR)me.szModule, szDllName)||
           !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
        {
            bFound = TRUE;
            break;
        }
    }
    if(!bFound)
    {
        cout<<"dll not found!\n";
        CloseHandle(hSnapshot);
        return FALSE;
    }
    if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        cout<<"Open process failed!"<<endl;
        return FALSE;
    }
    hModule = GetModuleHandle("kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);
    return TRUE;

}

大体上来说与Inject函数类似,只是多了一步查找进程快照中的Dll来取得句柄,其他都是同样的:
申请空间,创建Proc,令其开启线程执行FreeLibrary函数释放Dl

l虽然这个程序其实不太需要注出功能,不过学习嘛,经验要紧~

另外一边,Dll的Detech选项也需要添加脱钩的功能
也就一行,很简单,在DllMain函数中的Switch内增加:
[C++] 纯文本查看 复制代码
 case DLL_PROCESS_DETACH:
            // detach from process
            //脱钩
            memcpy((DWORD*)hook_addr, OrgBytes, 18);
            break;


将之前复制的原字节复制回去,即脱钩
(附件已更新,仍然需要提供完整Dll路径,注出时需要加上-d参数)

使用方法:
1.png
(从上到下分别为注入时未开启程序、注入成功、注出成功、注出时程序中未加载dll、注出时未开启程序)

到此Dll注入的全套都练习了一遍啦~
基本过程和经验都得到了不少长进,果然还是要实际操作才能有用嘛

之后有好几场CTF,加油~

dll_defiler.1.rar

376.64 KB, 下载次数: 152, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 22吾爱币 +21 热心值 +20 收起 理由
LAOLAO1 + 1 谢谢@Thanks!
S_Q_Q + 1 + 1 热心回复!
li_cn + 1 + 1 谢谢@Thanks!
843120 + 1 + 1 谢谢@Thanks!
flatcc + 1 + 1 很不错!过来学习一下。
幽夜寒香 + 1 + 1 我很赞同!
smallpox + 1 + 1 我很赞同!
linzi0713 + 1 + 1 热心回复!
曹大哥丶 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zhou1758818844 + 1 我很赞同!
pk8900 + 1 + 1 有时间得好好学习学习,正好在学习C++
qaz003 + 1 + 1 用心讨论,共获提升!
mashan2014 + 1 + 1 我很赞同!
试试去爱你 + 1 + 1 学习了。多多发教程,原理
wisoft + 1 谢谢@Thanks!
cakowh + 1 + 1 谢谢@Thanks!
netle8 + 1 我很赞同!
courageous + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Toky_TM + 1 + 1 热心回复!
zbnysjwsnd8 + 2 + 1 dalao
dj1149 + 1 + 1 我很赞同!
黑的思想 + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| whklhh 发表于 2017-11-4 07:58
searchjack 发表于 2017-11-4 05:59
win 10  x64  + 毒霸  , 没有反应  不知道什么鬼

哪里没有反应0.0?我也是win x64 倒是没有毒霸 不过火绒没有报毒来着 理论上来说dll注入应该算病毒行为,但是我进行过很多次了都没有报毒。而且没反应也不是杀软的反应吧~
 楼主| whklhh 发表于 2017-11-22 00:03
csmimiyy 发表于 2017-11-21 23:08
vc++dll注入有什么好的书籍推荐

Windows核心编程里有Dll注入的相关章节,我还没看,但想来应该是非常偏内核的内容;本帖是在学习《逆向工程核心原理》中的相关部分时做的。我觉得讲的已经很清楚了。
xyz1125 发表于 2017-10-31 20:35
大黑屋 发表于 2017-10-31 20:58
这个  这个  这个有点困难啊  我慢慢来吧
3683057 发表于 2017-11-1 00:34
学习了 努力中
courageous 发表于 2017-11-1 08:32
不错,谢谢分享。
xiawan 发表于 2017-11-1 08:58
楼主高人~高产,非常感谢
yaojin666666 发表于 2017-11-1 09:07
努力学习,谢谢大神

netle8 发表于 2017-11-1 09:29
谢谢分享!学习了!
林先生的一天 发表于 2017-11-1 09:47
谢谢大佬分享!
xie83544109 发表于 2017-11-1 10:05
{:1_914:}
多谢楼主分享哟
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-2 11:27

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表