preface
I've been doing C + + reverse in my spare time to attack and defend the world. I recently brushed master pipixia's blog. I feel that as a binary, reverse and pwn should not be separated. Moreover, I've done reverse for half a year and found that my code ability has been improved unconsciously (I found it when I wrote the code recently). It's estimated that being able to read the decompiled pseudo code can also train the code power, Moreover, it has been safe to enter the pit for one year, and I gradually realize that reverse is a very interesting thing. In the past, I was always very impatient when doing complex reverse. Now I know that patience is the most important thing to engage in technology
Analysis process
Drag directly into IDA64 (IDA32). The pop-up prompt ELFx86-64, and you should get used to checking the shell before IDA
See the main function
int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v3; // rax __int64 v4; // rax __int64 v5; // rax __int64 v6; // rax __int64 v7; // rax __int64 v8; // rax __int64 v9; // rax __int64 v10; // rax __int64 v11; // rax __int64 v12; // rax __int64 v13; // rax __int64 v14; // rax __int64 v15; // rax __int64 v16; // rax char v18[32]; // [rsp+10h] [rbp-130h] BYREF char v19[32]; // [rsp+30h] [rbp-110h] BYREF char v20[32]; // [rsp+50h] [rbp-F0h] BYREF char v21[32]; // [rsp+70h] [rbp-D0h] BYREF char v22[32]; // [rsp+90h] [rbp-B0h] BYREF char v23[120]; // [rsp+B0h] [rbp-90h] BYREF unsigned __int64 v24; // [rsp+128h] [rbp-18h] v24 = __readfsqword(0x28u); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v18, argv, envp); std::operator>><char>(&std::cin, v18); v3 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------"); std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>); v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Quote from people's champ"); std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>); v5 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------"); std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>); v6 = std::operator<<<std::char_traits<char>>( &std::cout, "*My goal was never to be the loudest or the craziest. It was to be the most entertaining."); std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>); v7 = std::operator<<<std::char_traits<char>>(&std::cout, "*Wrestling was like stand-up comedy for me."); std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>); v8 = std::operator<<<std::char_traits<char>>( &std::cout, "*I like to use the hard times in the past to motivate me today."); std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>); v9 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------"); std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>); HighTemplar::HighTemplar((DarkTemplar *)v23, (__int64)v18); v10 = std::operator<<<std::char_traits<char>>(&std::cout, "Checking...."); std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v19, v18); func1(v20, v19); func2(v21, v20); func3(v21, 0LL); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v21); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v20); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v19); HighTemplar::calculate((HighTemplar *)v23); if ( !(unsigned int)HighTemplar::getSerial((HighTemplar *)v23) ) { v11 = std::operator<<<std::char_traits<char>>(&std::cout, "/"); std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>); v12 = std::operator<<<std::char_traits<char>>(&std::cout, "Do not be angry. Happy Hacking :)"); std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>); v13 = std::operator<<<std::char_traits<char>>(&std::cout, "/"); std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>); HighTemplar::getFlag[abi:cxx11](v22, v23); v14 = std::operator<<<std::char_traits<char>>(&std::cout, "flag{"); v15 = std::operator<<<char>(v14, v22); v16 = std::operator<<<std::char_traits<char>>(v15, "}"); std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v22); } HighTemplar::~HighTemplar((HighTemplar *)v23); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v18); return 0; }
From the backward analysis of flag string, there are possible key functions such as getserial, calculate and hightemple, and then locate them
STD:: operator > > < char > (& STD:: CIN, v18); know that v18 is the input string
Hightemple:: hightemple ((darktemple *) V23, (_int64) v18); the input string is processed here
Analyze hightemple
unsigned __int64 __fastcall HighTemplar::HighTemplar(DarkTemplar *a1, __int64 a2) { char v3; // [rsp+17h] [rbp-19h] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-18h] v4 = __readfsqword(0x28u); DarkTemplar::DarkTemplar(a1); *(_QWORD *)a1 = &off_401EA0; *((_DWORD *)a1 + 3) = 0; std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string((char *)a1 + 16, a2); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string((char *)a1 + 48, a2); std::allocator<char>::allocator(&v3); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string( (char *)a1 + 80, "327a6c4304ad5938eaf0efb6cc3e53dc", &v3); std::allocator<char>::~allocator(&v3); return __readfsqword(0x28u) ^ v4; }
Guess 327a6c4304ad5938eaf0efb6cc3e53dc is a string of key strings. Although the function is not clear from a direct view, let's analyze it first
It is found that v23 appears in all possible parameter positions of key functions, and then analyze calculate
bool __fastcall HighTemplar::calculate(HighTemplar *this) { __int64 v1; // rax _BYTE *v2; // rbx bool result; // al _BYTE *v4; // rbx int i; // [rsp+18h] [rbp-18h] int j; // [rsp+1Ch] [rbp-14h] if ( std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16) != 32 ) { v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Too short or too long"); std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>); exit(-1); } for ( i = 0; i <= (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16); ++i ) { v2 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]( (char *)this + 16, i); *v2 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]( (char *)this + 16, i) ^ 0x50) + 23; } for ( j = 0; ; ++j ) { result = j <= (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16); if ( !result ) break; v4 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]( (char *)this + 16, j); *v4 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]( (char *)this + 16, j) ^ 0x13) + 11; } return result; }
The pseudo code here is ugly, but it is probably an if and two for. The string length is required to be = = 32, and then for each character ^ 0x50 + 23, and then ^ 0x13 + 11
Then look at getserial
__int64 __fastcall HighTemplar::getSerial(HighTemplar *this) { char v1; // bl __int64 v2; // rax __int64 v3; // rax __int64 v4; // rax __int64 v5; // rax unsigned int i; // [rsp+1Ch] [rbp-14h] for ( i = 0; (int)i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16); ++i ) { v1 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]( (char *)this + 80, (int)i); if ( v1 != *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[]( (char *)this + 16, (int)i) ) { v4 = std::operator<<<std::char_traits<char>>(&std::cout, "You did not pass "); v5 = std::ostream::operator<<(v4, i); std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>); *((_DWORD *)this + 3) = 1; return *((unsigned int *)this + 3); } v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Pass "); v3 = std::ostream::operator<<(v2, i); std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>); } return *((unsigned int *)this + 3); }
Compare the string of this + 16 with that of this + 80
Problem solving script
In fact, you don't need to analyze it too clearly. You can already guess the pulse of the program. Generally speaking, it's relatively simple
exp
serial = "327a6c4304ad5938eaf0efb6cc3e53dc" flag = "" for ch in serial: flag += chr((ord(ch) - 11 ^ 0x13) - 23 ^ 0x50) print("flag{" + flag + "}")
summary
The decompiled pseudo code of cpp is very ugly, but you don't need to care about a long string of function names. Just look at the key positions, such as cin, cout, etc
Reverse is still more inclined to analysis, and the problem-solving script is often much simpler than pwn
Reverse is more a top-down analysis, grasp the program logic from the macro, ignore the cumbersome code details, guess an idea, and then verify it, so as to speed up the reverse speed
Of course, unless PWN is called, PWN's reverse analysis is to dig out all the details of the vulnerability point
(in fact, there's no need to use a bunch of great principles. Brush more questions and you'll be done
There are too many things to do recently. After this period, I will continue to update the wp of BUU PWN and open a new pit of BUU reverse
About playing the game... Next year, I really don't have a long time to play the game. I can only brush the questions in fragments of time to make a living(