[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/

posted @ 2022-01-16 17:53  Moominn  阅读(319)  评论(0编辑  收藏  举报