一次关于省赛逆向的笔记,有什么不会不懂的都可以问我,qq:162099475
现在我们进入到程序中,可以发现左上角的主函数名中第一个为main函数。
Flag:0x0000000140001000
程序在进行判断的时候要么是正确的回显要么是错误的回显,类似c语言的if语句的判断。在PE01.exe程序中,输入字符出现了"wrong…"的错误信息
按空格键来到汇编代码窗口中
我们在这里点击wrong左上角的主函数,可以发现上面jnz指令跳转的为这个黄色主函数。call指令为回调地址,也就是说jnz跳转主函数后回调地址sub_1400016A0
按F5键查看伪代码,由于ida并不是能完整的复现伪代码的所有真实数据,在这里可以发现if语句对sub_1400016A0函数进行判断。
我们继续双击sub_1400016A0函数,来到了另一个伪代码中,在这里可以发现这是一些算法加上for循环语句进行if判断,这更加证明了sub_1400016A0函数就是检查许可证的关键函数
点击汇编代码窗口,双击sub_1400016A0回调函数,可以看见它的入口地址为:00000001400016A0
Flag:0x00000001400016A0
我们根据题目他提示继续分析Flag可能会出现的地方,双击main的函数,并在当前页面下按F5查看伪代码。
在这里我们可以看见有两个函数地址,分别是sub_140001250和sub_140001920,双击sub_140001920函数,可以发现这里有些字符串的提示语句:“你好!也许秘密就藏在背景图片中”
根据提示,联想到exe程序中默认是可以进行解压的,那么PE01.exe程序里的背景也是可以通过这种方式进行解压出来,我们将程序解压到PE01文件夹中,在.rsrc\2052\BITMAP的路径中发现了这张图片。
将图片拖入到HxD工具中,在图片十六进制的末尾可以发现有一些可以可疑的十六进制值,将0去掉尝试猜解一些字符串,验证想法是正确的,编写一个脚本获取flag值
脚本如下:
import re
str ='04 06 06 0C 06 01 06 07 03 01 07 0B 06 03 04 00 04 0E 07 04 05 0F 03 05 06 05 05 0F 06 0D 04 05 07 0D'
result = str.replace(' ','')
len_r = len(result)
flag = ""
shiliu = ""
for j in range(1,len_r,2):
shiliu += (result[j])
result1 = re.findall('.{2}',shiliu)
for i in result1:
flag += (chr(int(i,16)))
print(flag)
Flag:Flag1{c@Nt_5e_mE}
我们在第2题得知sub_1400016A0函数就是检查许可证的关键函数,那么我们返回到这个函数的伪代码进行分析。
__int64 __fastcall sub_1400016A0(const WCHAR *a1)
{
int i; // [rsp+20h] [rbp-28h]
int v3; // [rsp+24h] [rbp-24h]
_DWORD *v4; // [rsp+28h] [rbp-20h]
v3 = lstrlenW(a1);
v4 = malloc(saturated_mul(v3, 4ui64));
for ( i = 0; i < v3 && i < 17; ++i )
{
v4[i] = (char)sub_1400017B0(LOBYTE(a1[i]));
v4[i] = -v4[i];
v4[i] ^= i + dword_140005044;
if ( dword_140005000[i] != v4[i] )
return 0i64;
}
return 1i64;
}
通过观察可以发现,许可证必须的等于v4[i]才能回显正确,那么我们求v4[i]的值等于求许可证的值,所以现在第一个目标是求v4[i],进行反编写脚本获取它的许可证码。
可以发现求许可证的核心关键是for循环,那么我们只要关注for循环里的内容就好了,既然是逆向,逆向的脚本应该也是逆着来的,我们从后面开的函数分析。双击dword_140005000。
在这里可以发现这个变量里的数组有17个值。
这个我们先不管,返回到伪代码窗口,继续向上分析,双击该变量名,跳转到该地址的数据段,在这里我们可以看见类似上面变量名的xxh这是汇编语言中的十六进制的表示符。
现在我们知道这个变量名的数据段的值为那些十六进制,为了方便认识,按n键修改变量名字,将dword_140005044修改为key,v4修改为num。
其中为了求出许可证我们知道num[i]必须恒等于dword_140005000[i],所以dword_140005000数据段里的17个十六进制值要等于num的变量名中的数组里的值。我们继续点击dword_140005000变量来到数据段里分析其中的十六进制值,为了方便认识,把它们都转为十进制。
这里涉及到了十六进制转负数的知识点,如果直接进行进制转换可以发现有些数值是很大的,这不符合了十进制和ascii直之间的转换(0-127)。我们要判断十六进制是否为负数,只需要知道16进制首位是否大于8,大于8为负数,小于8为正数。
例如,在上图中dword_14000500变量里的数据段第一个十六进制值0FFFFFF9Ch,把它转为负数该如何进行操作?
在汇编语言中为了避免将16进制数误认为是指令,需要在前面添加一个0
由于我们知道16进制首位大于8为负数,为了简单运算只留下一个F就够了
F9C 的二进制位为1111 1001 1100,这里由涉及了一个知识点十六进制转负数,简单来说就是求该二进制的补码就行。
简单一个实验案例十六进制转负数
1111 1001 1100 原码:为十六进制转为二进制的值
0000 0110 0011 反码就是把原码的0和1改为相反数(原码求补码的过程要经历反码)
1000 0110 0100 补码在最高位加1,证明它是负数,并且在最低为加1
所以 F9C等于1000 0110 0100 (前面的1只是证明负数,运算的时候不理)
-> 0110 0100 (转为十进制,注意添加符号,前面证明了是负数)-> -100
现在我们了解了那么多的信息,用python定义两个了变量
num =[-100,72,-98,-103,78,-118,61,-91,83,-74,-128,6,39,-93,117,-103,-65]
key = 65
继续分析上面的伪代码,其中-num[i]不理,后面编写逆向照着写就好了。在伪代码的左边可以发现chr函数,它的意义是转为ASCII值,num变量最后会经历ASCII的转码,这个时候ASCII输出的值也就是flag。我们双击sub_1400017B0,在这其中可以发现返回了一些算法的信息。其中a1是什么变量?根据伪代码a1[i]可以猜测出a1=num,其中0xFu中在十六进制不存在,所以编写脚本的时候应该改为0xF即可。
我们来到分析最后一行关键代码,可以看见是一个for循环,这有两种理解方式,一个是ida自动生成的伪代码并不标准,中间不可能出现与运算。二是因为我们知道num的数组有17位,那么for循环从零开始到16,遍历17次,所以这行代码应该理解为:
for ( i = 0; i < 17; ++i )
最终用python脚本获取flag:
num =[-100,72,-98,-103,78,-118,61,-91,83,-74,-128,6,39,-93,117,-103,-65]
key = 65
flag = ""
for i in range (len(num)):
num[i] ^= (i + key)
num[i] = -num[i]
num[i] = chr(16 * (num[i] & 15) | (num[i] >> 4) & 15)
flag += num[i]
print(flag)
Flag:2o22_Ch1n@Ski1ls!
关于第四题篇幅和操作过长,可以参考我的另一篇教程:
https://blog.csdn.net/weixin_53912233/article/details/127282287
有什么不懂的话,可以评论区留言或者私信,我看到都会解答的