大神论坛

找回密码
快速注册
查看: 194 | 回复: 0

[原创] 逆向分析EXE程序实现调用自己写的DLL功能教程

主题

帖子

0

积分

初入江湖

UID
668
积分
0
精华
威望
0 点
违规
大神币
68 枚
注册时间
2023-10-14 10:49
发表于 2023-12-17 16:04
本帖最后由 弑神者91511 于 2023-12-17 16:04 编辑

新手第一次写逆向和改造的过程,难免有错误或走弯路的地方,请大神们多多指正。
本文旨在探讨程序编写技术,相关程序均为自已编写代码测试,请勿用于非法目的。

【情境介绍】注:为避免引起歧义,以下为虚拟情境,如有雷同,纯属巧合。

最近拿到某办公程序(x86):victim.exe,自动和服务器联网。在服务器下达通知消息时,会自动跳出提示,播放声音,几秒后停止。
要求播放的声音需要从功放输出,保证范围内所有人能听见。一般来说功放需要24小时不间断开机,但长时间设备容易老化损坏。
需要改造成在有提示声音播放时再自动打开功放电源,播放完毕自动断开功放电源。在程序源代码中增加这个功能也非常容易。
但本文从另一个角度来探讨:如果不修改源代码,能否实现增加功能呢?

【改造思路】
1、逆向分析EXE文件,找出提示开始和结束的代码;
2、编写自己的DLL文件,以供EXE调用实现功能;
3、改造EXE文件(添加DLL导入函数、打补丁调用函数)。

【知识准备】
1、大致了解EXE文件的结构、加载到内存的方式等
2、反汇编与调试
3、DLL的代码编写
4、EXE修改


【使用工具】
查壳工具:DiE
调试工具:OD
编程工具:vs2010(很老了……)
修改工具:StudyPE+、010Editor
记录工具:Programmer's Notepad
抓包工具:Wireshark


【过程记录】
一、逆向分析EXE文件,找出提示开始和结束的代码
1、上手用DiE查了一下,VC编译win32,无壳,studype查为动态基址。这一步并不是很难,如果是遇到加密加壳的,像我这种新手就无能为力了。
2、直接拖入OD,停在OEP(012BFCCC) 如果没有在OEP点一下小人运行到用户代码就行。接着点一下Az搜索字符串,因为字符串包含大量提示信息,很可能找到我们要的东西

3、果然,很快找到VoiceXXX::stop()和VoiceXXX::OnReceiveWarning()之类的信息,应该是写日志用的,字面一看这不就是声音的结束和开始吗,就从这里下手吧。
双击点进去,把地址记录下来,准备打补丁。

4、下面就是刚才那两处的地址
可以看出,push了一个字符串,然后call某个函数。
这种就很好打补丁,因为跳转的机器码和push字符串的机器码一样长(后面再说)。
到这里,第一步分析完成。

012BAE83 | 68 9CEE2C01              | push victim.12CEE9C                                                    | 12CEE9C:"VoiceXXX::onReceivedWarning() type:"
012BAE88 | FF15 08A62C01 | call dword ptr ds:[<&public: class QDebug & __thiscall QDebug::operato |
......
012BA8A1 | 68 68EE2C01 | push victim.12CEE68 | 12CEE68:"VoiceXXX::stop()"
012BA8A6 | FF15 08A62C01 | call dword ptr ds:[<&public: class QDebug & __thiscall QDebug::operato |

二、编写自己的DLL文件,以供EXE调用实现功能
这个可以用你自己喜欢的编写DLL的工具,写两个可以静态调用的函数,为了简单,没有参数,也没返回值。
我用vs2010向导建立了WIN32dll项目,取名叫customsg,过程中最好要选一下“导出符号”以便导出函数
我写了两个导出函数:MsgStart和MsgStop, 在函数里就可以做很多事情了,
比如发送系统消息、给串口发数据、访问某个网址等都行。
这里我采用了插入一个串口继电器,给串口发数据控制电源通断的方式。

//用于导出的函数
// 开始消息,设置dwMsg = 1
CUSTOMSG_API void MsgStart(void)
{
wMsg = 1;
PostMessage(HWND_BROADCAST, nPostMsgID, wMsg, dwMsgVar);

SendSerialData(TRUE); //发送命令打开继电器

PostData(strPostUrl, strPostDataStart);
}

// 停止消息,设置dwMsg = 0
CUSTOMSG_API void MsgStop(void)
{
wMsg = 0;
PostMessage(HWND_BROADCAST, nPostMsgID, wMsg, dwMsgVar);

SendSerialData(FALSE);//发送命令关闭继电器

PostData(strPostUrl, strPostDataStop);
}

//其它函数及具体代码自己实现

然后在项目中添加一个def文件用于导出规范名称的函数,要不然函数名是乱码的

LIBRARY "customsg"
EXPORTS
MsgStart @1
MsgStop @2

编译成功,你就会得到一堆文件。我们只需要找到.dll这个:
customsg.dll

在Debug和Release目录都有,选一个复制到刚才那个victim.exe的目录中,这一步宣告完成。

三、改造EXE文件(添加DLL导入函数、打补丁调用函数)
这一步总体来说有点难度,我也是走了很多弯路才成功,遇到问题多百度可以解决掉大部分
关键在于细致

1、添加DLL导入函数
用StudyPE+打开我的victim.exe,点击“导入”,出现导入dll列表,右键点击任意一个,点击“添加导入函数”

浏览找到自己编写的custmsg.dll,从左侧选取要导入的函数(MsgStart、MsgStop),加到右侧,点击“添加”。

完成后,点击“文件-另存为”,保存为"victim2.exe",DLL导入函数添加完毕。

2、打补丁调用函数
这里最好稍微有点汇编的知识,百度学习一下也行
(1)再次启动OD,打开导入DLL函数的“victim2.exe",运行到用户代码,直接去我们在第一部分找到的地址处 按F2下个断点 方便找
【这里有个问题,就是动态基址,每次程序加载后地址可能会不一样。如果图简单,可以在刚才StudyPE+里顺便点一下“固定基址”就OK,就不会变了】
我这里就以动态基址的方式来改,也没问题,当作一次学习
这里我们把刚才的地址,和刚才记的OEP地址(012BFCCC)减一下  再和现在的OEP地址(00F6FCCC)减一下,就能算出来了
012BAE83 :    012BFCCC-012BAE83=4E49,  00F6FCCC-4E49=00F6AE83
......
012BA8A1:    012BFCCC-012BA8A1=542B,  00F6FCCC-542B=00F6A8A1

(2)在CPU窗口 按Ctrl+G输入要找的地址直接过去

00F6AE83 | 68 9CEEF700              | push victim2.F7EE9C                                                    | F7EE9C:"VoiceXXX::onReceivedWarning() type:"
00F6AE88 | FF15 08A6F700 | call dword ptr ds:[<&public: class QDebug & __thiscall QDebug::operato |
......
00F6A8A1 | 68 68EEF700 | push victim2.F7EE68 | F7EE68:"VoiceXXX::stop()"
00F6A8A6 | FF15 08A6F700 | call dword ptr ds:[<&public: class QDebug & __thiscall QDebug::operato |

(3)在代码前后附近找一些空闲空间,放补丁代码
因为程序编译留了许多int3的调试指令,我们需要寻找一大片连续的CC字节 最好在20个以上
按Ctrl+B搜索匹配特征,在十六进制处输入24个连续的CC,按“确定”

运气好,搜索出来了在00F72EB2处有,双击点进去

(4)果然一大片。我们留一两个CC再开始,我从00F72EB5开始写调用MsgStart的代码
右键,二进制 编辑,
前8字节改为
60 FF 15 CC CC CC CC 61

00F72EB5 | 60                       | pushad                                                                 |
00F72EB6 | FF15 CCCCCCCC | call dword ptr ds:[CCCCCCCC] |
00F72EBC | 61 | popad |

这段代码就可以完成调用某个函数的功能了,CCCCCCCC暂时不管,等会儿改成我们自己的函数地址

接着把00F6AE83的5个字节写过来

00F72EBD | 68 9CEEF700              | push victim2.F7EE9C                                                    | F7EE9C:"VoiceXXX::onReceivedWarning() type:"

最后加个跳转指令,跳回原来的call那里
这里直接按空格键,输入"jmp 00F6AE88"就行

然后双击jmp那条指令,跳到上面的call,将前面那条指令改成跳转到我们补丁地址 "jmp F72EB5"

这样,就完成了EXE对我的DLL中MsgStart函数的调用框架
梳理一下思路:
在空闲区编写调用DLL函数的代码 -》在EXE中修改一条PUSH指令为JMP到空闲区 -》 空闲区调用DLL函数结束,执行原来的PUSH指令,JMP回去原来地址继续运行

用同样的方法,完成MsgStop函数的调用框架编写

(5)修复调用的DLL函数的地址
首先,到OD中,点击“符号”,选中左侧第一个"victim2.exe",在右边会出现许多它的导入函数。由于我们添加的DLL在最后,因此我们拉到最下面,会找到导入的两个函数:

记住这两个地址,并分别填写到刚才的CCCCCCCC处【注意低字节在前】
02A04C30 =customsg.MsgStart
02A04C34 =customsg.MsgStop
同时记下左上角黑色显示的那个基址【 00F40000】,下面要用到

可以看到,call的函数名称显示正确了
可是不要高兴得太早,因为这是动态基址,下一次运行还会变

(6)我们要计算出它的正常基址,让它再怎么变都能找到。
方法:用函数地址减去上面的基址【00F40000】,再加上正常基址【EXE一般为400000】,用StudyPE+可以查看ImageBase值。
02A04C30-00F40000+400000 =【01EC4C30】    //customsg.MsgStart
02A04C34-00F40000+400000 =【01EC4C34】    //customsg.MsgStop
同样的,上面我们PUSH的那个参数,也是动态地址,也需要减去上面的基址【00F40000】,再加上正常基址【EXE一般为400000】
68 9CEEF700 -》68 【9CEE4300】
68 68EEF700 -》68 【68EE4300】

在OD中,把这4个值对应修改进去

(7)记录数据偏移地址
OD里修改好了,等会儿还要在EXE文件里去修改。在动态基址的情况下,这几个值程序在加载的时候会重新计算,因此要找出这几个值在EXE文件加载时的偏移地址。
方法:数据地址减去基址【00F40000】

将这几个地方都算出来

32EB6                 //00F72EB6 | FF15 304CEC01            | call dword ptr ds:[1EC4C30]                                            |
32EBD //00F72EBD | 68 9CEE4300 | push 43EE9C |
32ECA //00F72ECA | FF15 344CEC01 | call dword ptr ds:[1EC4C34] |
32ED1 //00F72ED1 | 68 68EE4300 | push 43EE68 |

由于每个指令前面是操作码FF15或68,后面才是数据,因此要加1到2字节,算出数据的地址:

32EB8
32EBE
32ECC
32ED2

留着备用。

(8)应用补丁到文件
在OD中点右键,点击“补丁”,在出现的对话框中点击“全选” -》 “修补文件”,另存为"victim3.exe"
另存的时候可能会有个提示 “你的补丁和重定向区域重叠”,点YES就行

(9)为我们的补丁增加重定向项目
下载一个最新的010 Editor,打开victim3.exe,会提示你安装EXE模板,点“安装”。
完成后打开的victim3.exe会变得五颜六色,下面也会多出来分析的目录树:

在目录树里找到 RelocTable, 可以看到尺寸大小是78D0H

向下滑动到下面,找到RelocTable的最后一项点一下。可以看到最后一项后面有许多0,这里可以增加我们自己的重定位项。

重定位项的结构可以百度一下。
第1个四字节:VirtualAddress,可以理解为EXE展开到内存中的地址。这个要进行4K对齐,也就是以十六进制1000H为整数倍。
  找到第(7)步记录的偏移地址,看到我们的数据都在32EB8到32ED2,因此第1个VirtualAddress填32000H 【00 20 03 00】
第2个四字节:BlockSize,就是这个重定位块的长度。每个数据2字节 乘以四=8字节,再加上VirtualAddress和BlockSize,一共16字节,因此这里填10H 【10 00 00 00】

接下来是具体的重定位项,就是相对VirtualAddress的偏移量 【数据地址减去VirtualAddress】
根据重定位项定义,最高4个二进位值设置为“3”,表示这是个相对地址,加载时需要重新计算

以第一个数据的重定位项为例:数据地址 - VirtualAddress = 32EB8 - 32000 = EB8
最高4个二进制位设为3,合起来就是 3EB8H  【B8 3E】
同理,可算出其它3个数据的重定位项:【BE 3E】【CC 3E】【D2 3E】
紧挨着上面的数据,依次填充进去 :

最后,修改一下重定位表大小,确保我们添加的才能有效
方法:目录树上滑到开头,依次展开 NtHeader -》OptionalHeader -》DataDirArray,
找到一个“BaseRelocationTable”展开,点一下“Size”,看到是【D0 78 00 00】,也就是刚刚我们看到的78D0H
给它加上我们增加的块大小10H,得到78E0H ,所以将上面的数据修改为:【E0 78 00 00】,保存文件或另存为“victim4.exe"

(10)大结局
至此,EXE文件的改造工作全部结束。
再次用StudyPE+打开我们改造过后的EXE,查看导入表和重定位表,确认都没有问题。

运行测试,EXE程序工作正常,串口数据发送正常,电源控制正常。

由于技术生疏,边学边改,以上程序前后经历了一周多才完成。
因此记录下来,希望能给初接触EXE文件修改的新手们一点参考。


注:若转载请注明大神论坛来源(本贴地址)与作者信息。

返回顶部