暑假第六周
[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
a = [
0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, 0x75,
0x74, 0x73, 0x72, 0x71, 0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B,
0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61,
0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57,
0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4F, 0x4E, 0x4D,
0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43,
0x42, 0x41, 0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39,
0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x2F,
0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25,
0x24, 0x23, 0x22, 0x21, 0x20, 0x00
]
b = 'DDCTF{reverseME}'
flag = 'flag{'
for i in range(len(b)):
flag += chr(a.index(ord(b[i])) + 32)
flag += '}'
print(flag)
#flag{ZZ[JX#,9(9,+9QY!}
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处下断点
选择Remote Linux 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
输入命令,然后随便输入一串字符
b *0x4008DD
r
可以看到gdb已经停在了主函数调用check_key()的地方
输入n步过,程序运行到test eax eax 处
输入set $eax=1
将eax置为1
然后一直输入n步过最后得到flag
flag
flag_is_you_know_cracking!!!
[RedHat2019]childRE
无壳64位直接定位到主函数,从后往前分析
据此我们可以先求出outputString
str1 = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&"
str2 = "55565653255552225565565555243466334653663544426565555525555222"
str3 = '1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;,ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'
OutputString = ''
for i in range(62):
OutputString += chr(str3.index(str1[i]) + str3.index(str2[i]) * 23)
print(OutputString)
#private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
往上是一个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 以及微软都提供了预定义宏,用于获取源码中的方法名。
// GCC 支持: __PRETTY_FUNCTION__ 和 __FUNCTION__
// MSVC 支持: __FUNCTION__ 和 __FUNCDNAME__ 和 __FUNCSIG__
void exampleFunction()
{
printf("Function name: %s\n", __FUNCTION__);
printf("Decorated function name: %s\n", __FUNCDNAME__);
printf("Function signature: %s\n", __FUNCSIG__);
// 输出为:
// -------------------------------------------------
// Function name: exampleFunction
// Decorated function name: ?exampleFunction@@YAXXZ
// Function signature: void __cdecl exampleFunction(void)
}
用C++写出一个上面函数的例子,将修饰后的函数输出
#include <iostream>
class R0Pxx {
public:
R0Pxx() {
My_Aut0_PWN((unsigned char*)"hello");
}
private:
char* __thiscall My_Aut0_PWN(unsigned char*);
};
char* __thiscall R0Pxx::My_Aut0_PWN(unsigned char*) {
std::cout << __FUNCDNAME__ << std::endl;
return 0;
}
int main(){
R0Pxx A;
system("PAUSE");
return 0;
}
//?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z
解决了v5接着往上看,v5=name,然后进行一系列置换
看看sub_1400015C0
不好直接逆,考虑用动态调试获取name
看看主函数这部分对应的汇编代码,v5应该是从al寄存器中获取内容
并且调试过程中发现随便输入31位字符串取值是固定的
那么为了方便我们就输入ASCII码65-95的字符ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
下完断点后F9运行,得到name
写脚本
import hashlib
str1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'
name = bytes.fromhex('5051485253494454554A56574B454258594C5A5B4D465C5D4E5E5F4F474341')
ind = []
print(name)
for i in name:
ind.append(str1.index(chr(i)))
print(ind)
v5 = '?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z'
flag = [''] * 31
for i in range(31):
flag[ind[i]] = v5[i]
flag = ''.join(flag)
print(flag)
print(hashlib.md5(flag.encode()).hexdigest())
exp
str1 = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&"
str2 = "55565653255552225565565555243466334653663544426565555525555222"
str3 = '1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;,ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'
OutputString = ''
for i in range(62):
OutputString += chr(str3.index(str1[i]) + str3.index(str2[i]) * 23)
print(OutputString)
import hashlib
str1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'
name = bytes.fromhex('5051485253494454554A56574B454258594C5A5B4D465C5D4E5E5F4F474341')
ind = []
print(name)
for i in name:
ind.append(str1.index(chr(i)))
print(ind)
v5 = '?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z'
flag = [''] * 31
for i in range(31):
flag[ind[i]] = v5[i]
flag = ''.join(flag)
print(flag)
print(hashlib.md5(flag.encode()).hexdigest())
#private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
#b'PQHRSIDTUJVWKEBXYLZ[MF\\]N^_OGCA'
#[15, 16, 7, 17, 18, 8, 3, 19, 20, 9, 21, 22, 10, 4, 1, 23, 24, 11, 25, 26, 12, 5, 27, 28, 13, 29, 30, 14, 6, 2, 0]
#Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP
#63b148e750fed3a33419168ac58083f5
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)
addr=0x08048AA8 #s数组的地址
arr = []
for i in range(39): #数组的个数
arr.append(hex(Dword(addr+4* i)))
print(arr)
addr=0x08048A90 #dword_8048A90数组的地址
arr = []
for i in range(6): #数组的个数
arr.append(hex(Dword(addr+4* i)))
print(arr)
法二(手动)
点进去看s和dowrd_8048A90都是db(byte)手动将其转换成dd(double)
每一行db按3下D键转换成dd
模拟decrypt
s = [
0x143a, 0x1436, 0x1437, 0x143b, 0x1480, 0x147a, 0x1471, 0x1478, 0x1463, 0x1466, 0x1473, 0x1467, 0x1462, 0x1465, 0x1473, 0x1460, 0x146b, 0x1471, 0x1478, 0x146a, 0x1473, 0x1470, 0x1464, 0x1478, 0x146e, 0x1470, 0x1470, 0x1464, 0x1470, 0x1464, 0x146e, 0x147b, 0x1476, 0x1478, 0x146a, 0x1473, 0x147b, 0x1480]
a2 = [0x1401, 0x1402, 0x1403, 0x1404, 0x1405]
v6 = len(s)
v7 = len(a2)
v2 = len(s)
dest = s
v4 = 0
flag = ''
while v4<v6:
for i in range(0,5):
if(i<v7 and v4<v6):
dest[v4] -= a2[i]
flag += chr(dest[v4])
v4 += 1
else:
break
print(flag)
#9447{you_are_an_international_mystery}
法二
考虑用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}
本文来自博客园,作者:{Tree_24},转载请注明原文链接:{https://www.cnblogs.com/Tree-24/}