[DDCTF]Windows_Reverse1

先脱UPX壳
img
IDA32 打开,主函数并不复杂,sub_401000加密输入v6
比较v4与DDCTF{reverseME}
img
看看sub_401000里v6和v4什么关系,看着这个v1很奇怪
img
没关系,我们倒回去重新看看主函数汇编代码
img
[esp+82Ch+var_404]是v6,[esp+830h+var_804]是v4
v6传给了eax寄存器,v4传给了ecx寄存器
而我们查看sub_401000的汇编代码可以看到调用了ecx
img
可以得出结论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,一开始很懵都是问号
img
但同时又注意到下面有一串字符
再观察一下地址402FF8~403018刚好地址差值为32
而ASCII码的前32位刚好是不可见字符,所以403018即为我们要的编码表

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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文件
img
ida 打开task9_x86_64查看主函数
img
要求输入key,然后check_key
如果key正确就执行 interesting_function来加密v7
check_key函数如下,感觉不好破解
img
interesting_function如下是flag的构造函数
img

法一

考虑用动态调试
将IDA目录下\dbgsrv里的linux_server64拷到虚拟机中
IDA中选择Remote Linux Debugger
Debugger菜单下Process options设置一下网络端口
img
linux下用ifconfig查看网络,并运行linux_server64
img
在check_key处下断点
img
选择Local Windows debugger,F9运行
随便输入什么
img
一直F8运行到 test eax eax
img
可以看到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
img
修改后F9运行,得到flag
img

法二

考虑用gdb调试
先用IDA查看一下主函数调用check_key()的地址
img
用gdb运行 task_x86_64
输入命令,然后随便输入一串字符

1
2
b *0x4008DD
r

img
可以看到gdb已经停在了主函数调用check_key()的地方
img
输入n步过,程序运行到test eax eax 处
输入set $eax=1将eax置为1
img
然后一直输入n步过最后得到flag
img

flag

flag_is_you_know_cracking!!!

[RedHat2019]childRE

无壳64位直接定位到主函数,从后往前分析
img
据此我们可以先求出outputString

1
2
3
4
5
6
7
8
9
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官方文档
img
img
所以就是完全取消v5的符号修饰存入outputString
那么把private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)修饰一下就能得到v5

求解v5

法一

维基百科-C++函数修饰

C++函数修饰组成

  1. 问号前缀;
  2. 函数名称或不包括类名的方法名称。构造、析构函、运算符重载等具有特定的函数名;
  3. 如果不是特殊函数名,那么加一个分隔符@;
  4. 如果是类的方法,C++的类成员函数(其调用方式是thiscall)那么由所属类开始依次加上类名和父类名,每个类名后面跟一个@符号,所有类名加好后,再加上@Q或者@S(静态方法)。如果不是类的方法,那么直接加上@Y;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。
  5. 调用约定代码。对于不属于任何类的函数,C调用约定(cdecl)的代码为A,fastcall约定的代码为I,__stdcall 的代码为G,对于类方法,调用约定前会加一个字符A,this调用的代码为E.
  6. 返回值编码。
  7. 参数列表编码,以@符号结束。
  8. 后缀Z。

C++函数修饰组成规律

  1. 都是以?开始,以字符Z结束,中间由@符号分割为多个部分。整个名称的长度最长为2048个字节。
  2. 类的成员函数,其基本结构为:?方法名@类名@@调用约定 返回类型 参数列表 Z。
  3. 非类的成员函数,其基本结构: ?函数名@@Y调用约定 返回类型 参数列表Z。
  4. 特殊函数名
特殊函数名 编码
构造函数 ?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
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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++写出一个上面函数的例子,将修饰后的函数输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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,然后进行一系列置换
img
看看sub_1400015C0
img
不好直接逆,考虑用动态调试获取name
看看主函数这部分对应的汇编代码,v5应该是从al寄存器中获取内容
img
并且调试过程中发现随便输入31位字符串取值是固定的
那么为了方便我们就输入ASCII码65-95的字符ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
img
下完断点后F9运行,得到name
img
写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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}

后记

学习了一下别人的想法,才意识到是个满二叉树,
img
v5即为二叉树后序遍历生成的,构造出二叉树如下
img
先序遍历这个二叉树就可以得到原来的字符,也就是 Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP,然后 md5 加密即可

no-string-attach

32位无壳,ida打开查看主函数,比较简单
img
加密过程也比较清晰,经decrypt函数解密s和dword_8048A90储存在s2
然后从stdin流中读取0x2000个字符给ws,ws再和s2比较
img
decrypt函数如下,应该能模拟
img

法一

提取s和dword_8048A90

法一(idaPython)

1
2
3
4
5
6
7
8
9
10
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)

img

法二(手动)

点进去看s和dowrd_8048A90都是db(byte)手动将其转换成dd(double)
每一行db按3下D键转换成dd
img
img

模拟decrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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处下断
img
gdb调试
b *0x8048725表示在0x8048725处下断点
r 运行
img
n 单步执行
img
x/6sw $eax x查看寄存器内容,查看6行数据,s表示以字符串形式查看,w表示数据是word(4字节),$eax表示查看的是eax寄存器内容
img

flag

9447{you_are_an_international_mystery}