- 定位到关键函数并不困难
signed __int64 sub_140001610() { signed __int64 v0; // rax _QWORD *v1; // rax const CHAR *v2; // r11 __int64 v3; // r10 __int64 v4; // r9 const CHAR *v5; // r10 signed __int64 v6; // rcx __int64 v7; // rax signed __int64 result; // rax unsigned int v9; // ecx __int64 v10; // r9 int v11; // er10 __int64 v12; // r8 __int128 input; // [rsp+20h] [rbp-38h] __int128 v14; // [rsp+30h] [rbp-28h] input = 0i64; v14 = 0i64; sub_140001080("%s", &input); v0 = -1i64; do ++v0; while ( *(&input + v0) ); if ( v0 != 31 ) { while ( 1 ) Sleep(1000u); } v1 = sub_140001280(&input); // 二叉树保存 v2 = name; if ( v1 ) // 递归算法 { sub_1400015C0(v1[1]); sub_1400015C0(*(v3 + 16)); v4 = dword_1400057E0; v2[v4] = *v5; dword_1400057E0 = v4 + 1; } UnDecorateSymbolName(v2, outputString, 0x100u, 0);// ? v6 = -1i64; do ++v6; while ( outputString[v6] ); if ( v6 == 62 ) { v9 = 0; v10 = 0i64; do { v11 = outputString[v10]; // private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *) v12 = v11 % 23; if ( str1[v12] != *(v10 + 0x140003478i64) )// str2 (_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*& _exit(v9); if ( str1[v11 / 23] != *(v10 + 0x140003438i64) )// str3 // 55565653255552225565565555243466334653663544426565555525555222 _exit(v9 * v9); ++v9; ++v10; } while ( v9 < 62 ); sub_140001020("flag{MD5(your input)} ", v11 / 23, v12, v10); result = 0i64; } else { v7 = sub_1400018A0(std::cout); std::basic_ostream<char,std::char_traits<char>>::operator<<(v7, sub_140001A60); result = 0xFFFFFFFFi64; } return result; }
输出长度是32,然后最后处理之后的是62位置。
一开始我想正向去处理,但是中间的二叉树保存和中序遍历复现实在有点难度,那就从后面往前。
可以解出outstring,为private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
str1=r'1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'+chr(0x27) str2=r'(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&' str3=r'55565653255552225565565555243466334653663544426565555525555222' outstring="" for i in range(62): a=str1.find(str2[i]) b=str1.find(str3[i]) outstring+=chr(b*23+a) print(outstring)
outstring是函数 UnDecorateSymbolName(v2, outputString, 0x100u, 0);// ?处理结果,这是一个C++的名字修饰函数,这一块有两个方法处理得到v2:
(参考前辈文章)
方法一:
参考资料1
可以知道第二个参数为未修饰的名字,第三个参数为长度,第四个参数为0表示完全修饰,第一个参数为输出地址
参考资料2
c++函数名的修饰更为复杂,提供的信息也更为丰富。
无论 __cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字。再后面是参数表的开始标识和依照参数类型代号拼出的参数表。
v2 = ?My_Aut0_PWN
对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字 符引导的类名。
v2 = ?My_Aut0_PWN@R0Pxx
其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是 “@@IAE”,私有(private)成员函数的标识是“@@AAE”,假设函数声明使用了constkeyword,则对应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。
因为函数为private,私有成员
所以v2 =?My_Aut0_PWN@R0Pxx@@AAE
后面就是添加参数了,先加入函数返回值参数,函数的返回值类型为char *
参数表的拼写代号如下:
X–void
D–char
E–unsigned char
F–short
H–int
I–unsigned int
J–long
K–unsigned long(DWORD)
M–float
N–double
_N–bool
U–struct
…
指针的方式有些特别。用PA表示指针,用PB表示const类型的指针。
char *也就是PAD
所以v2 = ?My_Aut0_PWN@R0Pxx@@AAEPAD
然后是参数的类型unsigned char *,也就是PAE
所以v2 = ?My_Aut0_PWN@R0Pxx@@AAEPADPAE
参数表后以“@Z”标识整个名字的结束。假设该函数无参数,则以“Z”标识结束。
所以最终v2 = ?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z
方法二:
#include<iostream> using namespace std; class ROPxx { public: ROPxx(){ unsigned char a; My_Aut0_PWN(&a); } private: char My_Aut0_PWN(unsigned char*) { printf("%s", __FUNCDNAME__); return '0'; } }; int main() { new ROPxx(); getchar(); return 0; }
v2是变换之后的字符串,获得输入字符串还需要克服二叉树保存那块,也就是知道这个二叉树保存的方式和 读出(前序或者后序)方式,好在动态调试可以获得替换的索引表:
0x50, 0x51, 0x48, 0x52, 0x53, 0x49, 0x44, 0x54, 0x55, 0x4a, 0x56, 0x57, 0x4b, 0x45, 0x42, 0x58, 0x59, 0x4c, 0x5a, 0x5b, 0x4d, 0x46, 0x5c, 0x5d, 0x4e, 0x5e, 0x5f, 0x4f, 0x47, 0x43, 65
2.keygen:
for i in range(62): a=str1.find(str2[i]) b=str1.find(str3[i]) outstring+=chr(b*23+a) print(outstring) str1 = '?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z' result = [''] * 31 index3 = [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] print(len(str1)) for i in range(len(str1)): result[index3[i]] += str1[i] ss = ''.join(i for i in result) print(ss)