源于对某软件的广告删除分析,发现其为易语言程序,特此写一篇关于易语言程序的分析思路,而此篇所讲述的为修改易语言编译后程序的标题、宽度和高度。(大佬勿喷,自己分析的思路记录)
我们首先安装易语言较新的版本,并安装破解补丁以至于能够进行编译程序,推荐下载地址:https://www.bilibili.com/video/av205300986/
安装完成后我们新建一个.e程序并打开,我们对_启动窗口设置一些初始值(宽度、高度、标题),如下图所示:
设置好后我们点击上方的 编译 – 静态编译 ,编译到桌面,命名为test.exe,双击打开查看是否能正常运行,能正常运行的话我们关闭程序,使用X32dbg载入程序,因为编译的程序是32位的。
我们首先需要明确一点我们的高度和宽度分别为:300、600,转换成对应的十六进制分别为:12C、258,这里我们先实现对程序的宽度和高度进行修改,载入后,我们先按一下F9来到程序的入口,如下图所示:
这里我们讲两个思路定位到关键代码处:
我们先讲第一个方法,也是逆向分析最通用的方法,我们再下方输入:
bp CreateWindowExA
然后按下回车,发现提示下断点成功,我们F9直接来到CreateWindowExA处,这里经过反复调试,发现第一断下来的不是我们想要分析的地方,而第二次为我们想要分析的函数,于是我们再次按下F9得到如图所示:
此时我们不能直接修改该内存的值,原因是我们现在的CreateWindowExA函数为系统API,是最后调用的时候加载的原程序的参数,因此我们需要找到是谁调用了该系统API,我们看一下调用堆栈的数据,找到如下图所示位置,双击返回至上一层:
这里讲一下上一层也就是调用刚刚的API函数的函数层,我们往上划一下,因为返回来第一个选中的汇编指令为执行完函数后指向的下一条指令,因此上方的CALL函数为我们的上一层函数,此时的push压栈参数完全与我们上方查看的API参数一一对应了。
判定依据为第二次断下地方加载了我们的窗口名称,通过查询CreateWindowExA的调用方法如下:
HWND CreateWindowExW(
[in] DWORD dwExStyle,
[in, optional] LPCWSTR lpClassName,
[in, optional] LPCWSTR lpWindowName,
[in] DWORD dwStyle,
[in] int X,
[in] int Y,
[in] int nWidth,
[in] int nHeight,
[in, optional] HWND hWndParent,
[in, optional] HMENU hMenu,
[in, optional] HINSTANCE hInstance,
[in, optional] LPVOID lpParam
);
我们可以看到与宽度对应的寄存器为:dword ptr ss:[ebp-0x20],与高度对应的寄存器为:dword ptr ss:[ebp-0x1C],因此我们查看一下是谁修改了这两个寄存器的值:
此时我们发现直接修改此处的值再修补文件是没法运行的,因此,再往上层进行分析,我们在此处下断(此处地址为0x41147c),Ctrl+F2重新运行,F9来到此处,查看调用堆栈的第二个test模块的调用堆栈,来到更上一层,我们往上看一下有没有大跳跳过此函数的地方,发现没有大跳,那么我们来到上方的第一个ret下面下断,重新运行F9过来:
此时我们F8单步步过往下注意观察寄存器和堆栈值得变化,我们可以发现好几处都进行了宽高的赋值,但到底哪一处才是准确的参数赋值呢?我们将对应的汇编前后三行都复制下来观察一下:
#第一处
0041135A | 8B7C24 | mov edi,dword ptr ss:[esp+4C] |
0041135E | 8B5C24 | mov ebx,dword ptr ss:[esp+50] |
00411362 | 3BCD | cmp ecx,ebp |
00411364 | 8B6C24 | mov ebp,dword ptr ss:[esp+54] | 单步执行到这发现EBP的值变成了258
00411368 | 75 54 | jne test.4113BE |
0041136A | 8B46 7 | mov eax,dword ptr ds:[esi+70] |
0041136D | 48 | dec eax |
#第二处
004113A7 | 8BF8 | mov edi,eax |
004113A9 | D1FF | sar edi,1 |
004113AB | FFD3 | call ebx |
004113AD | 8B5424 | mov edx,dword ptr ss:[esp+58] | 单步执行到这,发现edx的值变成了12C
004113B1 | 8B4C24 | mov ecx,dword ptr ss:[esp+68] |
004113B5 | 2BC2 | sub eax,edx |
004113B7 | 99 | cdq |
#第三处
004113B8 | 2BC2 | sub eax,edx |
004113BA | 8BD8 | mov ebx,eax |
004113BC | D1FB | sar ebx,1 |
004113BE | 8B5424 | mov edx,dword ptr ss:[esp+58] | 单步执行到这,发现edx的值变成了12C
004113C2 | 03EF | add ebp,edi |
004113C4 | 85C9 | test ecx,ecx |
004113C6 | 8D0413 | lea eax,dword ptr ds:[ebx+edx] |
#第四处
0041144A | 8B4E 4 | mov ecx,dword ptr ds:[esi+40] |
0041144D | 52 | push edx |
0041144E | 8B5424 | mov edx,dword ptr ss:[esp+70] |
00411452 | 2BC3 | sub eax,ebx | 单步到这里的时候,发现eax变成了12C,与我们的高度对应
00411454 | 2BEF | sub ebp,edi | 单步执行到这,发现ebp的值变成了258
00411456 | 52 | push edx |
00411457 | 50 | push eax |
我们可以发现前面三处距离我们的断点处0x41147c较远,而第四处距离较近,并且我们发现第四处都是对宽和高的赋值,因此我们大胆猜测一下第四处和第五处就是我们需要修改的宽和高的值,第四处截图如下:
这里需要明白一个知识点:调用函数时,若需要对函数进行传参,那么传参的值将会以入栈的形式体现,类似push eax之类的,而函数执行结束的返回值将会存储在eax里,对上图仔细分析我们可以更加有把握的猜测就是我们要找的宽度和高度的汇编指令处了,这里我们贴一下分析的过程:
0041144E | 8B5424 | mov edx,dword ptr ss:[esp+70] |
00411452 | 2BC3 | sub eax,ebx | 单步到这里的时候,发现eax变成了12C,与我们的高度对应
00411454 | 2BEF | sub ebp,edi | 单步执行到这,发现ebp的值变成了258
00411456 | 52 | push edx |
00411457 | 50 | push eax |我们发现这里将eax压栈,也就是压入了高度
00411458 | 894424 | mov dword ptr ss:[esp+68],eax |
0041145C | 8B4424 | mov eax,dword ptr ss:[esp+4C] |
00411460 | 55 | push ebp | 我们发现这里将ebp进行压栈,也就是压入了宽度
00411461 | 53 | push ebx |
00411462 | 57 | push edi |
00411463 | 50 | push eax |
00411464 | 8B86 C | mov eax,dword ptr ds:[esi+CC] |
0041146A | 51 | push ecx | ecx:“helloworld”
0041146B | 50 | push eax |
0041146C | E8 4F0 | call test.4115C0 |
00411471 | 8B4C24 | mov ecx,dword ptr ss:[esp+60] |
00411475 | 83C4 0 | add esp,4 |
00411478 | 50 | push eax | 我们发现这里将eax进行了压栈,也就是压入了0x41146c的函数返回值
00411479 | 51 | push ecx | 将该返回值作为参数传入我们的CreateWindowExA的调用函数
0041147A | 8BCE | mov ecx,esi | ecx:“helloworld”, esi:“打G"world”
0041147C | E8 91C | call test.46D612 | 这里为我们的CreateWindowExA的调用函数
我们可以发现使用sub指令得到的宽和高,因此我们需要对宽或高的被减数进行更改值,即可得到新的宽或高,但是这个方法太麻烦,还得往上翻,还得考虑栈平衡,因此我们直接修改对应的eax和ebp寄存器的值即可(这里将高和宽的值置换),F9运行起来得到如图所示:
接下来我们修改窗口标题,同样的思路就能找到窗口标题的修改处,由于都是启动窗口的基本属性,故标题的赋值地址也在附近,这里经调试直接给出:
然后我们F9直接运行起来看看效果:
我们清除所有断点,Ctrl+F2重启按下F9加载到入口处,右键搜索特征命令:
这里讲一下,易语言的启动窗口特征为(前提是没有特殊情况),或者我们通过易语言的载入函数进行分析,也可以得到窗口的ID:
push 0x52010001
这里贴两篇大佬分析易语言的思路以及特征:
https://fjqisba.github.io/categories/%E6%98%93%E8%AF%AD%E8%A8%80%E9%80%86%E5%90%91/
https://www.52pojie.cn/forum.php?mod=viewthread&tid=483527&highlight=%D2%D7%D3%EF%D1%D4
如何找到载入函数呢,我们这里可以使用IDA静态加载,这里需要用到一个易语言分析模块,贴上一个大佬的模块地址:
https://www.52pojie.cn/thread-1684608-1-1.html
之后怎么通过IDA找载入函数就不用我多说了吧,上面第二篇文章的大佬已经讲得很清楚了
看到这,我们的分析过程就结束了,有人可能想问了,那么我们如何进行将修改过后的值保存到程序里,这样每次打开都是我们修改好的内容呢?
1、 改汇编代码,然后打补丁patch,这个方法及其不推荐,如果改了之后不影响程序的正常运行,那么我们可以这么干,但是通常修改后会影响栈平衡或寄存器赋值不对,导致函数错误,甚至程序崩溃,因此及其不推荐该方法。
2、 HOOK(推荐)