1. 程式人生 > 其它 >[2019紅帽杯]childRE

[2019紅帽杯]childRE

查殼,沒殼,64 位,IDA 分析一下

第一步

從後往前看,先看最後的輸出部分

對 outputString 與23取餘和除數,在 a1234567890Qwer 陣列中找到位置,再與另外兩個陣列進行明文比較,所以可以根據邏輯直接得到 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,./'
tmp = ''

for i in range(62):
	tmp += chr(str3.index(str1[i]) + str3.index(str2[i]) * 23)
print(tmp)
#private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)

得到:

private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)

第二步

關鍵是 UnDecorateSymbolName 函式,作用是讓函式容易識別,也就是C++在編譯函式的時候,會把一下關鍵字用些特殊的符號代替,所以需要還原

方法一

然後看了一些師傅的部落格(參考部落格),要這麼搞:

參考資料1
第二個引數為未修飾的名字,第三個引數為長度,第四個引數為0表示完全修飾,第一個引數為輸出地址

參考材料2
c++函式名的修飾更為複雜,提供的資訊也更為豐富。
無論 __cdecl,__fastcall還是__stdcall呼叫方式,函式修飾都是以一個“?”開始,後面緊跟函式的名字。再後面是引數表的開始標識和依照引數型別代號拼出的引數表。

v5 = ?My_Aut0_PWN

對於C++的類成員函式(其呼叫方式是thiscall),函式的名字修飾與非成員的C++函式稍有不同,首先就是在函式名字和引數表之間插入以“@”字 符引導的類名

v5 = ?My_Aut0_PWN@R0Pxx

其次是引數表的開始標識不同,公有(public)成員函式的標識是“@@QAE”,保護(protected)成員函式的標識是 “@@IAE”,私有(private)成員函式的標識是“@@AAE”,假設函式宣告使用了constkeyword,則對應的標識應分別為“@@QBE”,“@@IBE”和“@@ABE”。

因為函式為 private,私有成員
所以 v5 = ?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
所以 v5 = ?My_Aut0_PWN@R0Pxx@@AAEPAD
然後是引數的型別 unsigned char *,也就是PAE
所以 v5 = ?My_Aut0_PWN@R0Pxx@@AAEPADPAE

引數表後以“@Z”標識整個名字的結束。假設該函式無引數,則以“Z”標識結束。

所以最終v5 = ?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z

方法二

另外一種方法參考 Hk_Mayfly 師傅的部落格:

#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

第三步

然後往上去看那個置換的操作

方法一

在這裡下斷點,啟動動調,輸入31個字元,這裡為了方便選擇輸入 65 ~ 95 這 31 個字元也就是 "ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_",執行一下看看

然後注意到這個位置有一個對於 name 的賦值,根據之前的程式碼知道 name 就是 v5,也就是我們剛才求得的字串,所以主要關注一下這個賦值操作

對應的彙編程式碼為

然後這裡的 al 值就是 result 的值,執行幾輪記錄一下 al 的值,結果如下

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

然後又因為我們輸入的內容是 A ~ _,所以減 A(65) 就可以得到它對應的下標,然後按照這個得到置換前的字串,然後進行 MD5 加密即可

from hashlib import md5

a = "?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z"
b = 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, 0x41 
tmp = list(a)

def encrypt_md5(s):
    new_md5 = md5()
    new_md5.update(s.encode(encoding='utf-8'))
    return new_md5.hexdigest()

for i in range(0,len(a)):
    tmp[b[i] - 65] = a[i]
flag = ''.join(tmp)
print(flag)

if __name__ == '__main__':
	 print(encrypt_md5(flag))
#Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP
#63b148e750fed3a33419168ac58083f5

得到 flag

flag{63b148e750fed3a33419168ac58083f5}

方法二

根據其他師傅的說法,這實際上是一個滿二叉樹

所以我們之前求出的字串即為二叉樹後序遍歷生成的,可以構造出二叉樹如下

先序遍歷這個二叉樹就可以得到原來的字元,也就是 Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP,然後 md5 加密即可

參考部落格:
https://blog.csdn.net/qq_41858371/article/details/103111366
https://www.52pojie.cn/thread-1548065-1-1.html
https://blog.csdn.net/weixin_43876357/article/details/108087660
https://www.cnblogs.com/Mayfly-nymph/p/11869959.html
https://www.freesion.com/article/6515734088/