前言
个人的一些逆向小练习和解题思路
每天一道
课后小程序下载地址
链接:https://pan.baidu.com/s/1U-LK9lZf4CjVjUCuSSFIlQ
提取码:k5pf
第一课课后作业
软件使用
MessageBoxA API断点
通过信息框,猜测有MessageBox
API被调用,ctrl+g
找到调用处,直接下断点
点击按钮后让他在此处断下。
看见右下方堆栈顶端显示了CALL的返回地址,右键跟随反汇编窗口。
这里跟随到的是返回地址,这样我们就可以找到这个MessageBoxA
的返回地址了。
在此处下断点,继续运行,让他断到这里来。往上看了下,好像并没有看见什么明显的判断条件,继续F8。
往上面找,但发现这个我们猜测的成功的call并没有命令跳过它。继续往上看,发现又有2个这样的call,同时有个je跳过了这2个call
基本可以确定是这个跳转的问题了。
直接nop掉。
交叉引用查找法
先是在OD里字符串搜索,发现找不到字符串“注册失败” - OD里的各种插件可能会导致这个问题。于是转用IDA。
视图-》子视图-》字符串, 搜索注册失败
搜索字符串还是搜索不到?
在快捷方式处添加
-dCULTURE=all
找到后双击跟过去
上面的JE就是关键跳了。NOP掉即可
内存查找法
我们知道软件在运行的时候,函数的数据是会保存在内存里的
如果我们运行软件后将他暂停,再看内存窗口的话,可以查找到字符串
内存断点或硬件断点即可。
注意,如果硬件断点不起作用,可能是OD的问题,它各种反调试插件、汉化等等都可能导致这个问题,我们可以使用x64dbg.
栈回溯 ALT+K暂停法
软件再执行call的时候,会把一些参数都压入堆栈,最后执行完后再pop出来,我们可以在弹出信息框的时候给他暂停,这样参数都会保留在stack中,这时我们再去OD的K窗口(栈回溯窗口),就能看到function的信息了。
GetWindowTextA API断点
对GetWindowTextA
API进行断点,执行程序后被断下,一直F8。
发现在做比较,正确的字符串已经获取到了。
后续就不分析了。
总结
之前一直以为从断下的地方跳一层就会回到主函数下面,导致这么简单都没搞出来,事实上跳N层都是有可能的。
第二课课后作业
软件使用
栈溯源法 ALT+K暂停法
加载程序后运行,点击按钮让他弹出信息框,然后暂停。
在窗口K中找到堆栈信息,双击
发现有跳转,直接enter进入
在此处断点
再让程序跑一次,找到最终返回的地方。
此处跳过了成功消息,将JNZ改为NOP
交叉引用断点
在OD处修改即可。
如何在IDA修改?
选项-》常规-》操作数字节-》16
编辑-》修补程序-》汇编
nop两次把这两个都填充
编辑-》修补程序-》修补程序到输入文件
EA起始为你的基址
生成即可
补丁使用
第三课课后作业
软件使用
MessageBoxA API断点
发现带壳,之后得用补丁。
先让程序运行起来,下断点MessageBoxA
找到关键跳,需要修改JE为nop
补丁使用
第四课课后作业
E语言写内存补丁
E语言写劫持补丁
检测数据是否被壳还原 -》 检测004010A9地址的数据是否为0x55(85)
C语言写内存补丁
这里主要是用OpenProcess()
和WriteProcessMemory()
来完成内存写入操作的。
第五课课后作业
前言
这一课涉及到了很多知识点,我花了两三天的事件才慢慢把各个知识点理清楚。 主要涉及到了硬件断点、硬件HOOK和线程方面的知识,虽然老师只讲了一节课,但是自己真正弄清楚并实践还是得费不少时间。另外我使用的VS2022版本给我带来了不少麻烦,各种不兼容,还好最后解决了,之后可能会出一些解决VS问题的合集。
该课后作业的要求是不修改代码从而实现破解。
软件使用
SetWindowTextA 寻找关键跳
OD载入发现有壳
我们将它运行起来 - 加壳的程序必须运行起来,才会被恢复源码。
注册失败的信息显示在了标题上,那么可以猜测它使用了SetWindowTextA函数,下一个断点。
断下来了,开始F8。现在走到了一个可疑的地方,附近有大跳,我们用Immlabel插件标记一下,发现上面也有一处一样的call,那么猜测可能是成功的call。
观察发现,上面有一个jnz跳转,跳过了成功的call,那么我们可以确定关键跳就在这里了0x401053。
破解方法一:进程挂起+修改ZF标志位
在之前得知的关键跳0x401053处下一个CC断点
奇怪的是,程序会被终止。CC断点是将地址的首字节改为0xCC,因此我们判断该程序使用了某种校验方法(可能是CRC检测)来检测固定的某段代码段是否在运行过程中被修改。此处我们可以修改JNZ为NOP完成破解,但题目要求是不修改代码破解,所以我们还需另寻他法。
一个软件为了保证整个程序流程正常运行,不太可能将CRC检测(while loop形式)写在主线程(单一线程),不然后面的代码将无法执行,所以猜测可能是有新的线程建立。
在OD的T窗口查看线程情况
发现了一个异常活跃的线程,基本可以确定是用来检测的线程 - 直接将线程挂起。
再次将0X00401053下断,发现程序正常,成功过掉CRC检测。
JNZ在ZF标志位为1的时候不跳转
破解成功
破解方法二:硬件HOOK
已知检测程序是校验代码位数,从而判断代码是否被修改,但硬件断点不会被断下,因为它是通过操纵Dr寄存器来完成的下断。关于硬件断点的讲解见浅析硬件断点和内存断点
硬件断点会触发**STATUS_SINGLE_STEP(单步异常)**,异常会交给程序的异常处理函数管理,若程序的异常处理函数不处理,才会交给调试器。
我们现在可以利用以下思路:
配合DLL劫持,让程序在JNZ跳转0x401053处触发硬件断点,同时给程序写一个异常处理函数 - 异常处理函数里是我们的恶意代码 - 将EIP+6,这样能够直接绕过JNZ判断。
WIN10代码(我的环境): winspool.drv,使用第四课的C语言劫持补丁代码进行改写。
注意:win10在设置硬件断点的时候必须先挂起目标线程,否则无法断点,所以我们需要创建一个新的线程,再在这个线程中暂停主线程从而保存修改后的寄存器内容。
1 | DWORD g_dwBreakPoint = 0x401053; |
下面是win7代码:
1 | DWORD g_dwBreakpoint = 0x401053; // 关键指令,希望跳过这条指令 |
破解成功。
第六课课后作业
软件使用
此处注册码并没有任何用处,随意输入也会提示注册成功
破解方法
运行起来转到代码段401000 - 从模块中删除分析
字符串搜索
我们看见有一个\License.key,双击过去,在段开头断点
我们猜测该程序是在启动的时候判断是否注册,而关键点就在于这个License.key
记录一下下断点的地址004016D6
现在重新运行软件,正常的话,程序应该在这里断下来,但现在并没有断下,发现断点被禁用了。
程序带壳,刚开始运行时,下断的地址没有被恢复,所以断点会被禁止。
因此我们需要在程序代码恢复后再给004016D6地址下断。
转到CreateWindowExW下断 - 大部分加壳程序代码在此处都已还原。
在004016D6下断
运行后被断下,一直F8
在此处修改jnz为nop即可
第七课课后作业
软件使用
作业要求:逆出算法,不能爆破,不能修改代码。
逆向流程
直接载入OD,找关键点
在IDA中发现左侧第一个就是401000的函数,我们点击以后按F5,让它直接转变为代码
分析a1应该是我们输入的key,下面的messageBox乱码,怎么解决呢?双击乱码的地方过去
这种情况通常是因为IDA将其识别为Unicode,而unicode不能正确表示我们的字符串。
菜单栏->选项->字符串文本->修改为UTF-16LE
发现已经改回来了。
再回到之前的代码区,按F5
代码成功恢复
接下来我们对一些变量名做一些修改,方便理解
C语言逆算法
直接把代码从IDA上copy&paste,然后做一些修改。
1 |
|
算出来正确的注册码是17
第八课课后作业
软件使用
去花指令
载入OD后在字符串中查找,发现并没有注册失败或成功,跟进请输入注册码
,发现后面代码加了花指令
我选择先去除花指令。
花指令一般是由恒成立的跳转组成的,我们只需要nop掉永远不会访问的无效指令,就能去除
例如
在数据窗口中去除
将所有花指令去除后,复制保存到可执行文件
分析算法
因为程序比较小,我们可以直接拖入IDA,在前几个函数中能找到我们的关键位置
我们做一些注释
其中一个call中的**&byte_442578引起了我的注意,我有些困惑这是什么,根据上下文猜测,感觉后面的v4像是我们输入的key,&byte_442578**又是什么呢?
双击进去看看,此时它为数据类型
我们将他转换成字符串,发现它变成了%s
返回刚才的地方F5,显而易见,这是一个**scanf()**方法
我们进入**algrithms()**继续分析算法
一顿分析后,我们发现下方有一个*(_BYTE *)
,通常这种pointer前方又有一个星号的情况,都是因为IDA识别错误。
我们发现括号后面finalResult,和inputKey的类型是错误的
修改一下参数类型
显示正常。
同样我们再修改一下以下位置
对应的值为**’bcdaren’**
C语言逆算法
将算法从IDA复制到C
观察下面的算法,我们发现再第二个for循环中,i一直没变,不停的再给同一个数重新赋值新的aBacdaren_1[j]^xxx,所以我们可以看出来,其实这两个for循环是在干一件事,就是把最后一个字母n和(key中的每一位+13)做异或运算。
1 | for ( i = 0; ; ++i ) |
化简以后:
1 | for (int i = 0; i < keyLen; i++) { |
于是我们可以分析出,该程序的算法如下:
1 | char* algrithms(char* inputKey, char* finalResult, unsigned int maxLen) |
最后finalResult的值应该等于((++**--,,//..QQPP
我们再写个逆运算的算法,注意,异或运算中任意两个数异或可以得到第三个数,例如:
a^b=c
a^c=b
b^c=a
最终逆向算法:
1 | char* getCorrectKey(const char* keyToBeConverte, char* finalResult, unsigned int maxLen) { |