暑假第六周
[DDCTF]Windows_Reverse1
先脱UPX壳
IDA32 打开,主函数并不复杂,sub_401000加密输入v6
比较v4与DDCTF{reverseME}
看看sub_401000里v6和v4什么关系,看着这个v1很奇怪
没关系,我们倒回去重新看看主函数汇编代码
[esp+82Ch+var_404]是v6,[esp+830h+var_804]是v4
v6传给了eax寄存器,v4传给了ecx寄存器
而我们查看sub_401000的汇编代码可以看到调用了ecx
可以得出结论v1就是主函数的v4
回到sub_401000,v4被赋值成a1和v1地址的差值
然后v1的内容被赋值成byte_402FF8[(char)v1 [v4]]
v4怎么能作为索引呢?其实v1[v4]通过下标调用v1的方式等价于通过地址调用的方式即v1+v4
而v1+v4 = a1,而a1即为传入的参数v6
那么整理一下,在主函数中v4 = byte_402FF8[v6]
查看一下 byte_402FF8,一开始很懵都是问号
但同时又注意到下面有一串字符
再观察一下地址402FF8~403018刚好地址差值为32
而ASCII码的前32位刚好是不可见字符,所以403018即为我们要的编码表
exp
1 | a = [ |
flag
flag{ZZ[JX#,9(9,+9QY!}
simple-check-100
这题exe文件调试出来flag输出是乱码,所以我们调试elf文件
ida 打开task9_x86_64查看主函数
要求输入key,然后check_key
如果key正确就执行 interesting_function来加密v7
check_key函数如下,感觉不好破解
interesting_function如下是flag的构造函数
法一
考虑用动态调试
将IDA目录下\dbgsrv里的linux_server64拷到虚拟机中
IDA中选择Remote Linux Debugger
Debugger菜单下Process options设置一下网络端口
linux下用ifconfig查看网络,并运行linux_server64
在check_key处下断点
选择Local Windows debugger,F9运行
随便输入什么
一直F8运行到 test eax eax
可以看到check_key完之后有一个 test eax eax 指令
若 test eax eax 结果为1 则ZF为0, jz short loc_4008F4将不执行
程序将跳转到左边的4015A8处
若 test eax eax 结果为0 则ZF为1, jz short loc_4008F4将执行
程序将跳转到右边的4008F4处
很明显右边的就将会输出"Wrong"所以我们应该让程序跳转至左边的4008E6处来调用interesting_function函数
要让 test eax eax 结果为1,则应该将eax寄存器内容修改为1
修改后F9运行,得到flag
法二
考虑用gdb调试
先用IDA查看一下主函数调用check_key()的地址
用gdb运行 task_x86_64
输入命令,然后随便输入一串字符
1 | b *0x4008DD |
可以看到gdb已经停在了主函数调用check_key()的地方
输入n步过,程序运行到test eax eax 处
输入set $eax=1
将eax置为1
然后一直输入n步过最后得到flag
flag
flag_is_you_know_cracking!!!
[RedHat2019]childRE
无壳64位直接定位到主函数,从后往前分析
据此我们可以先求出outputString
1 | str1 = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&" |
往上是一个UnDecorateSymbolName函数,不懂翻翻Windows官方文档
所以就是完全取消v5的符号修饰存入outputString
那么把private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
修饰一下就能得到v5
求解v5
法一
C++函数修饰组成
- 问号前缀;
- 函数名称或不包括类名的方法名称。构造、析构函、运算符重载等具有特定的函数名;
- 如果不是特殊函数名,那么加一个分隔符@;
- 如果是类的方法,C++的类成员函数(其调用方式是thiscall)那么由所属类开始依次加上类名和父类名,每个类名后面跟一个@符号,所有类名加好后,再加上@Q或者@S(静态方法)。如果不是类的方法,那么直接加上@Y;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。
- 调用约定代码。对于不属于任何类的函数,C调用约定(cdecl)的代码为A,fastcall约定的代码为I,__stdcall 的代码为G,对于类方法,调用约定前会加一个字符A,this调用的代码为E.
- 返回值编码。
- 参数列表编码,以@符号结束。
- 后缀Z。
C++函数修饰组成规律
- 都是以?开始,以字符Z结束,中间由@符号分割为多个部分。整个名称的长度最长为2048个字节。
- 类的成员函数,其基本结构为:?方法名@类名@@调用约定 返回类型 参数列表 Z。
- 非类的成员函数,其基本结构: ?函数名@@Y调用约定 返回类型 参数列表Z。
- 特殊函数名
特殊函数名 | 编码 |
---|---|
构造函数 | ?0 |
析构函数 | ?1 |
重载 new | ?2 |
重载 delete | ?3 |
C++函数修饰参数表
编码 | 数据类型 |
---|---|
A | Type modifier (reference) |
B | Type modifier (volatile reference) |
D | char |
E | unsigned char |
F | short |
G | unsigned short |
H | int |
I | unsigned int |
J | long |
K | unsigned long |
M | float |
N | double bool |
O | long double Array |
P | Type modifier (pointer) |
Q | Type modifier (const pointer) |
R | Type modifier (volatile pointer) |
S | Type modifier (const volatile pointer) |
T | union |
U | struct |
V | class |
W | enum |
X | void |
简单了解函数修饰之后,我们就来考虑手动修饰一下private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
首先是函数名My_Aut0_PWN则 v5=?My_Aut0_PWN
因为是类的成员函数则加上类名 v5=?My_Aut0_PWN@R0PXX
因为是private私有成员所以加上@@AAE v5=?My_Aut0_PWN@ROPXX@@AAE
因为函数返回值参数是char *指针所以加上PAD v5=?My_Aut0_PWN@ROPXX@AAEPAD
因为函数参数类型是unsigned char *指针所以加上PAE v5 = ?My_Aut0_PWN@R0Pxx@@AAEPADPAE
最后加上@Z结束 v5 = ?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z
法二
GCC/Clang 以及微软都提供了预定义宏,用于获取源码中的方法名。
1 | // GCC 支持: __PRETTY_FUNCTION__ 和 __FUNCTION__ |
用C++写出一个上面函数的例子,将修饰后的函数输出
1 |
|
解决了v5接着往上看,v5=name,然后进行一系列置换
看看sub_1400015C0
不好直接逆,考虑用动态调试获取name
看看主函数这部分对应的汇编代码,v5应该是从al寄存器中获取内容
并且调试过程中发现随便输入31位字符串取值是固定的
那么为了方便我们就输入ASCII码65-95的字符ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
下完断点后F9运行,得到name
写脚本
1 | import hashlib |
exp
1 | str1 = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&" |
flag
flag{63b148e750fed3a33419168ac58083f5}
后记
学习了一下别人的想法,才意识到是个满二叉树,
v5即为二叉树后序遍历生成的,构造出二叉树如下
先序遍历这个二叉树就可以得到原来的字符,也就是 Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP,然后 md5 加密即可
no-string-attach
32位无壳,ida打开查看主函数,比较简单
加密过程也比较清晰,经decrypt函数解密s和dword_8048A90储存在s2
然后从stdin流中读取0x2000个字符给ws,ws再和s2比较
decrypt函数如下,应该能模拟
法一
提取s和dword_8048A90
法一(idaPython)
1 | addr=0x08048AA8 #s数组的地址 |
法二(手动)
点进去看s和dowrd_8048A90都是db(byte)手动将其转换成dd(double)
每一行db按3下D键转换成dd
模拟decrypt
1 | s = [ |
法二
考虑用gdb调试,通过查看authenticate汇编代码可知
s2的值是通过eax寄存器传递的,在8048725处下断
gdb调试
b *0x8048725
表示在0x8048725处下断点
r
运行
n
单步执行
x/6sw $eax x
查看寄存器内容,查看6行数据,s表示以字符串形式查看,w表示数据是word(4字节),$eax
表示查看的是eax寄存器内容
flag
9447{you_are_an_international_mystery}