-
-
[原创]ty123's Reverseme1.exe的详细解答
-
发表于: 2021-1-15 15:25 5122
-
hint:Import Address Table
小白第一次发帖,请各位老师傅多多指教。这是一道很老的题目了,写的比较详细,方便小白看懂。
我们先打开Reverseme1.exe:
要求分为两部分:
1.窗体中有一个隐藏按钮,将它显示出来并起作用;
2.按F7功能键,显示出这个消息框。
(1)、窗体中有一个隐藏按钮,将它显示出来并起作用
Reverseme1.exe是基于win 32的GUI编程软件,
IDA打开只能看到调用的API函数和大致过程:
所以我们用OD打开程序,查找字符串到主模块(Reversem):
查看当前主模块(Reversem)中调用的函数:
当前模块(Reversem)中调用的函数:
函数名称 | 函数作用 |
---|---|
user32.DialogBoxIndirectParamA | 该函数根据内存中对话框模板创建一个模态的对话框。 |
user32. Endialog | 该函数清除一个模态对话框,并使系统中止对对话框的任何处理 |
kernel32.ExitProcess | 中止一个进程 |
user32.GetDlgItem | 该函数检索指定的对话框中的控制句柄 |
kerne132.GetModuleHandleA | 获取一个应用程序或动态链接库的模块句柄 |
kerne132.GlobalAlloc | 分配一块内存,该函数会返回分配的内存句柄 |
kernel32.GlobalFree | 释放指定内存块 |
user32.LoadIconA | 从指定的模块或应用程序实例中载入一个图标 |
user32.MessageBoxA | 该函数创建、显示、和操作一个消息框 |
<ModuleEntryPoint> | 模块入口点,就是一般你用od载入时停在那个地址 |
kernel32.MultiByteToWideChar | 映射一个字符串到一个宽字符(unicode)的字符串 |
user32.SendMessageA | 调用一个窗口的窗口函数,将一条消息发给那个窗口 |
user32.ShowWindow | 控制窗口的可见性 |
其中重要的就是ShowWindow函数,这个函数的作用就是控制窗口的可见性,用来隐藏或者显示一个按钮。
所以我们找到ShowWindow函数,再看一下前面的代码:
我们发现有一个je short 004011BD 和jne short 004011BD连起来的代码:
汇编指令 | 对应的机器码 | 作用 |
---|---|---|
JZ/JE | 74 | Z=1,为零/等于则跳转 |
JNZ/JNE | 75 | Z=0 ,不为零/不等于则跳转 |
JE指令功能是在ZF标志位等于1时进行跳转,也就是(test)结果为0(或者相等)的时候跳转。
jnz指令功能是在zf标志为0转移,就是结果不为零(或不相等)则转移。
所以不论相等还是不相等都会进行跳转,所以这两句话连起来的意思就是jmp 004011BD:
那么中间的push edi到db 21这一段代码,就不会被执行,所以就相当于执行
1 2 3 4 5 6 | push 0x0 , push dword ptr ds:[ 0x403178 ] jmp 004011BD mov eax,Reversem. 00401258 ; | sub eax, 0x8 ; | call eax ; \ShowWindow |
而ShowWindow的函数原型是:BOOL ShowWindow(HWND hWnd,int nCmdShow),这里的参数是(00060EE2,0),也就是隐藏了一个窗口,应该就是隐藏了“退出”按钮。
我们把参数0改成1:
这时候“退出”按钮已经显示出来了:
但是现在点击它还没有作用,让它起作用应该就是调用Endialog函数清除这个模态对话框,并使系统中止对对话框的任何处理,关闭对话框。
所以我们继续看代码:
根据堆栈 ss:[ebp+0x10]的值来判断是否点击了第1个“About”和第2个“退出”按钮:
当点击了第1个“About”按钮,这时候堆栈 ss:[ebp+0x10]的值为1,就进入MessageBoxA函数创建一个新的模态消息框:
再退出了这个消息框之后,再跳转回原来的位置等待下一次点击;
当点击了第2个“退出”按钮,这时候堆栈 ss:[ebp+0x10]的值为2,就运行到了:
1 2 3 | 004011F2 . / 75 14 jnz short Reversem. 00401208 ; 跳转到等待下一次点击 004011F4 . |EB 00 jmp short Reversem. 004011F6 004011F6 > |EB 10 jmp short Reversem. 00401208 |
但是第二个跳转jmp short Reversem.00401208会让我们跳过Endialog函数,所以我们把它改为jmp short Reversem.004011FE(Endialog函数参数入栈的位置,且跳过ss:[ebp+0xC]的判断跳转):
点击退出按钮,继续运行程序:
程序终止,退出对话框。
(2)、按F7功能键,显示出这个消息框。
要实现这个功能,首先要注册所需要的键值F7,要向系统注册一个全局热键,需要用到RegisterHotKey,函数用法如下(MSDN):
函数功能:该函数定义一个系统范围的热键。
函数原型:BOOL RegisterHotKey(HWND hWnd,int id,UINT fsModifiers,UINT vk);
参数:
hWnd:接收热键产生WM_HOTKEY消息的窗口句柄。这里的窗口句柄是堆栈 ss:[ebp+0x8]。
id:定义热键的唯一标识符。目的是防止和其他热键冲突,我们就把它设为0.
fsModifoers:组合辅助按键,Ctrl(2)、Alt(1)、Shift(4)、等按键的组合;这里不用,值就是0。
vk:定义热键的虚拟键码。这里F7的虚拟键码是76。
所以注册热键的代码就是RegisterHotKey(ss:[ebp+0x8],0,0,76)。之后我们要按F7功能键,显示出这个消息框,步骤可以分为3步:
1、在输入表中添加RegisterHotKey函数;
2、在exe的合适位置添加代码:RegisterHotKey(ss:[ebp+0x8],0,0,76)来注册F7功能键;
3、当收到按下F7功能键的Windwos消息是,跳转到显示出这个消息框的代码。
步骤1:输入表添加RegisterHotKey
手动修改输入表更为精准但比较复杂,我们这里为了方便就直接用loadPE、StudyPE这些工具来向输入表中添加RegisterHotKey函数,但是每个工具修改输入表的原理和把添加的函数放在的具体位置位置都会有些许差异,会影响到后面调用添加的RegisterHotKey函数
先查看Reverseme1.exe的输入表:
输入表里已经有之前我们在exe中看到的那些函数了,接下来我们添加导入user32.dll的RegisterHotKey函数:
再查看文件的输入表,已经成功导入了user32.dll的RegisterHotKey函数:
步骤2:RegisterHotKey(ss:[ebp+0x8],0,0,76)注册F7热键
我们再用OD打开修改输入表后的exe,首先要找一块能够把注册的代码RegisterHotKey(ss:[ebp+0x8],0,0,76)写进去的地方,只要能够插入代码,且不会影响程序正常运行的位置都行:
1、可以写在这个函数里,第一问中不会被执行的代码处;
2、也可以像第(3)步中写在函数外,没有代码00401256之后的空白区域。
这里我们选择在第一问中不会被执行的代码处,作为我们写入注册热键的位置:
为了不影响showwindwos函数的运行,我们把
1 2 3 | 004011BD . B8 58124000 mov eax,Reversem. 00401258 ; | 004011C2 . 83E8 08 sub eax, 0x8 ; | 004011C5 . FFD0 call eax ; \ShowWindow |
这三行移到这两行代码下面,让它们连成一个完整的函数
1 2 | 004011A1 . 6A 00 push 0x0 004011A3 . FF35 78314000 push dword ptr ds:[ 0x403178 ] |
修改后的汇编代码如下图:
接下来写入注册热键RegisterHotKey(ss:[ebp+0x8],0,0,76)的汇编代码:
1 2 3 4 5 | push 76 ; / / F7的虚拟键码 push 0 ;唯一标识符 push 0 ;不用辅助按键 push dword ptr ss:[ebp + 0x8 ] ;窗口句柄 call dword ptr ss:[ 0x404464 ] ; / / ss:[ 0x404464 ]是user32.RegisterHotKey在文件的地址 |
修改之后的汇编代码:
步骤3:按下F7显示消息框
按下F7快捷键之后,就会产生一个Windows消息WM_HOTKEY=0x312:当用户按下由RegisterHotKey()注册的热键时产生此消息。
在文件中已经有了3次对Windows消息的判断:判断0x110,然后判断0x111,最后判断0x10
1、
1 2 | 00401174 . 817D 0C 10010000 cmp dword ptr ss:[ebp + 0xC ], 0x110 ; 判断是否是在被显示前发送的消息 0x110 0040117B . 75 4C jnz short Reversem. 004011C9 ; 不是就跳转 |
2、
1 2 | 004011C9 > \ 817D 0C 11010000 cmp dword ptr ss:[ebp + 0xC ], 0x111 ; 判断是不是选择控件时发送的消息 0x111 ,给它的父窗口或按下快捷键时产生此消息 004011D0 . 75 26 jnz short Reversem. 004011F8 ; 不是就跳转 |
3、
1 2 | 004011F8 > \ 837D 0C 10 cmp dword ptr ss:[ebp + 0xC ], 0x10 ; 判断是不是窗口或应用程序要关闭时发送的信号 0x10 004011FC . 75 0A jnz short Reversem. 00401208 ; 不是就跳转 |
所以我们只要加上一次判断0x312,把它放在任意两次判断之间都可以,这里我们考虑到先后顺序就把放在0x110之后、0x111之前:
1 2 3 | cmp dword ptr ss:[ebp + 0xC ], 0x312 ; 判断是不是按下RegisterHotKey()注册的热键产生的消息 0x312 jnz short Reversem. 004011C9 ; 不是就跳转,继续去判断 0x111 jmp short Reversem. 004011D8 ; 是就跳转到MessageBoxA函数 |
但是因为之前的没用的那段代码的空间已经只剩下4个nop了,但是加入的判断代码大于4个字节,所以我们接下来有两种办法:
1、继续放在这个函数里,但是扩大这4个字节的空间,让空间足够放下判断的代码;
2、把判断代码放在函数外一段没有代码的空白区域,利用JNZ来跳转。
方法1、扩大这4个字节的空间
为了腾出空间,我们先把上面的这个调用的汇编代码缩短:
1 2 3 | 0040119C . A3 78314000 mov dword ptr ds:[ 0x403178 ],eax ; Reversem. 00401171 004011A1 6A 01 push 0x1 004011A3 . FF35 78314000 push dword ptr ds:[ 0x403178 ] |
改成
1 2 | 0040119C |. 6A 01 push 0x1 ; | / ShowState = SW_SHOWNORMAL 0040119E |. 50 push eax ; ||hWnd = 0019FFCC |
修改之后的汇编代码:
这样我们就腾出了10个字节的空间,再把下面的汇编代码往上移,让10个字节的空间和之前的4个字节连在一起组成14个字节,修改之后:
上面的每一步,我们修改之后,Reverseme1.exe都是依旧可以运行的,且没有影响到第一问的“About”、“退出”按钮的功能。接下来我们在把判断加上去:
1 2 3 | 004011BB 817D 0C 12030 cmp dword ptr ss:[ebp + 0xC ], 0x312 ; 判断是不是按下RegisterHotKey()注册的热键产生的消息 0x312 004011C2 75 05 jnz short Reversem. 004011C9 ; 不是就跳转,继续去判断 0x111 004011C4 EB 12 jmp short Reversem. 004011D8 ; 是就跳转到MessageBoxA函数 |
别忘了把判断0x111后的JNZ跳转的地址004011C9改为0x312的地址004011BB:
这样判断windwos消息的顺序就变成了:判断0x110,之后判断0x312,然后判断0x111,最后判断0x10。
到此,第(2)问也做完了:
按下F7,成功显示第二个消息框。
方法2、把判断放在一段没有代码的空白区域00401256
因为jnz short的跳转范围是-128~127,超过这个范围的就要使用jnz(jnz long),而且它们对应的汇编代码也完全不同:jnz short 某个地址只用两个字节,jnz(jnz long)某个地址却要6个字节
(i)假如我们把判断windows消息的顺序改为:判断0x110,之后判断0x312,然后判断0x111,最后判断0x10
那么从0x111的代码到空白区域00401256肯定超过127字节,就要使用JNZ和JMP指令,先把0x111的cmp dword ptr ss:[ebp+0xC],0x111改为jmp 00401256:
再把判断0x312的代码放在空白区域00401256:
1 2 3 4 5 6 | 00401256 > \ 817D 0C 12030 > cmp dword ptr ss:[ebp + 0xC ], 0x312 ; 判断是不是按下RegisterHotKey()注册的热键产生的消息 0x312 0040125D .^ 0F84 75FFFFFF je Reversem. 004011D8 ; 是就跳转到MessageBoxA函数 00401263 817D 0C 11010 > cmp dword ptr ss:[ebp + 0xC ], 0x111 ; 不是就先判断 0x111 ,再跳转回去 0040126A ^ 75 8F jnz short Reversem. 004011F8 ; 不是 0x111 就跳转,继续判断 0x10 0040126C ^ E9 61FFFFFF jmp Reversem. 004011D2 ; 是 0x111 就跳转,到判断按钮 |
保存到可执行文件,运行:
按下F7,成功显示第二个消息框。
(ii)假如我们把判断windows消息的顺序改为:判断0x110,之后判断0x111,然后判断0x10,最后判断0x312
那么从0x10的代码到空白区域00401256就在127字节之内,就可以使用jnz short和jmp short指令,先把0x10判断后的跳转jnz short 00401208改为jnz short 00401256
再把判断0x312的代码放在空白区域00401256
1 2 3 | 00401256 817D 0C 12030 > cmp dword ptr ss:[ebp + 0xC ], 0x312 ; 判断是不是按下RegisterHotKey()注册的热键产生的消息 0x312 0040125D ^ 75 A9 jnz short Reversem. 00401208 ; 不是就跳转,到结束retn 0040125F ^ E9 74FFFFFF jmp Reversem. 004011D8 ; 是就跳转到MessageBoxA函数 |
保存到可执行文件,运行:
按下F7,显示出第二个消息框。
第一次发帖,写的有些太详细了,主要是希望大家都能看懂,有什么问题会及时更新,ty123's Reverseme1.exe在附件,有兴趣的可以试试。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)