[2019红帽杯]childRE

64位,无壳

image-20250216145937992

动态调试一下,发现没用,inputString是根据我们的输入形成的,但是错的(全0),没用;

有点绕,幸亏学了些数论,密码学,不然可能不会往这方面想

1
2
3
4
5
6
7
8
9
10
s[a[i] % 23] = b[i]
s[a[i] / 23] = c[i]

a[i] % 23 ==> x(商) ==>index(b[i])
a[i] / 23 ==> y(余数)==>index(c[i])

a[i] / 23 = a[i] % 23 + d(余数)
==> a[i] = (a[i] % 23) * 23 + d
==> a[i] = index(b[i]) * 23 + index(c[i])

把inputString[v14]推出来:

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
31
32
33
34
35
36
37
38
39
40
41
a123 = [
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
0x2D, 0x3D, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A,
0x28, 0x29, 0x5F, 0x2B, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79,
0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x51, 0x57, 0x45, 0x52,
0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0x61, 0x73,
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x41,
0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22,
0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F,
0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F,
0x00
]

a555 = [
0x35, 0x35, 0x35, 0x36, 0x35, 0x36, 0x35, 0x33, 0x32, 0x35,
0x35, 0x35, 0x35, 0x32, 0x32, 0x32, 0x35, 0x35, 0x36, 0x35,
0x35, 0x36, 0x35, 0x35, 0x35, 0x35, 0x32, 0x34, 0x33, 0x34,
0x36, 0x36, 0x33, 0x33, 0x34, 0x36, 0x35, 0x33, 0x36, 0x36,
0x33, 0x35, 0x34, 0x34, 0x34, 0x32, 0x36, 0x35, 0x36, 0x35,
0x35, 0x35, 0x35, 0x35, 0x32, 0x35, 0x35, 0x35, 0x35, 0x32,
0x32, 0x32, 0x00
]

a462 = [
0x28, 0x5F, 0x40, 0x34, 0x36, 0x32, 0x30, 0x21, 0x30, 0x38,
0x21, 0x36, 0x5F, 0x30, 0x2A, 0x30, 0x34, 0x34, 0x32, 0x21,
0x40, 0x31, 0x38, 0x36, 0x25, 0x25, 0x30, 0x40, 0x33, 0x3D,
0x36, 0x36, 0x21, 0x21, 0x39, 0x37, 0x34, 0x2A, 0x33, 0x32,
0x33, 0x34, 0x3D, 0x26, 0x30, 0x5E, 0x33, 0x26, 0x31, 0x40,
0x3D, 0x26, 0x30, 0x39, 0x30, 0x38, 0x21, 0x36, 0x5F, 0x30,
0x2A, 0x26, 0x00
]


inputString = ''

for i in range(62):
inputString += chr(a123.index(a555[i])*23 + a123.index(a462[i]))

print(inputString, end='')
# private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)

private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)不知道有什么用,继续逆推看inputString怎么来的

AI了一下UnDecorateSymbolName(v5, outputString, 0x100u, 0);,还有微软的解释[unDecorateSymbolName 函数 (dbghelp.h)](unDecorateSymbolName 函数 (dbghelp.h) - Win32 apps | Microsoft Learn)

  • v5,输入

  • outinputString,输出

  • 100,长度

  • 0x0000 对应UNDNAME_COMPLETE,启用完全取消评分

UnDecorateSymbolName 是一个用于取消修饰 C++ 符号名称的函数,属于 Windows 的 DbgHelp 库。它的主要作用是将经过编译器修饰的符号名称还原为更直观、更易读的形式。

函数原型

1
2
3
4
5
6
DWORD WINAPI UnDecorateSymbolName(
_In_ PCTSTR DecoratedName,
_Out_ PTSTR UnDecoratedName,
_In_ DWORD UnDecoratedLength,
_In_ DWORD Flags
);

参数说明

  1. DecoratedName
    输入参数,表示经过修饰的 C++ 符号名称。修饰的符号通常以问号(?)开头。
  2. UnDecoratedName
    输出参数,指向一个字符串缓冲区,用于存储取消修饰后的符号名称。
  3. UnDecoratedLength
    指定 UnDecoratedName 缓冲区的大小(以字符为单位),必须足够大以容纳取消修饰后的名称。
  4. Flags
    用于控制取消修饰的行为。常见的标志包括:
    • UNDNAME_COMPLETE(默认):完全取消修饰。
    • UNDNAME_NAME_ONLY:仅取消修饰主声明的名称。
    • UNDNAME_NO_ACCESS_SPECIFIERS:禁用成员的访问说明符。

返回值

  • 如果函数成功,返回值是 UnDecoratedName 缓冲区中的字符数(不包括 NULL 终止符)。
  • 如果函数失败,返回值为零,可以通过调用 GetLastError 获取更多错误信息。

以下是一个简单的使用示例:

1
2
3
4
5
6
7
8
9
10
#include <DbgHelp.h>
#include <iostream>
#pragma comment(lib, "Dbghelp.lib")

int main() {
char str[100] = "?getArgumentTypes@UnDecorator@@CG?AVDName@@XZ";
UnDecorateSymbolName(str, str, sizeof(str), UNDNAME_COMPLETE);
std::cout << str << std::endl;
return 0;
}

在这个例子中,UnDecorateSymbolName 将修饰后的符号名称 ?getArgumentTypes@UnDecorator@@CG?AVDName@@XZ 转换为更易读的形式

关于C++名称修饰的解析 - 简书

意思是UnDecorateSymbolName?getArgumentTypes@UnDecorator@@CG?AVDName@@XZ(C++ 装饰符号,表示一个函数名)

会将其取消修饰为更易读的形式。类似于我们推出的private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)

搜了一下,C++ 名称修饰的符号规则,有很多不同规则,装饰名称 |Microsoft 学习

文章最后提到可以使用 undname.exe 将修饰的名称转换为未修饰的形式。此示例显示了它的工作原理:

1
2
3
4
5
6
C:\>undname ?func1@a@@AAEXH@Z
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.

Undecoration of :- "?func1@a@@AAEXH@Z"
is :- "private: void __thiscall a::func1(int)"

C++ 编译器的函数名修饰规则 - yxysuanfa - 博客园

根据 Microsoft Visual C++ 的名称修饰规则,将 private: char* __thiscall R0Pxx::My_Aut0_PWN(unsigned char*) 转换为修饰后的状态,可以按照以下步骤进行:

1. 类名和成员函数

  • 类名:R0Pxx
  • 成员函数:My_Aut0_PWN
  • 访问修饰符:private
  • 调用约定:__thiscall(默认的类成员函数调用约定)

2. 参数类型

  • 参数:unsigned char*
  • 在 Microsoft Visual C++ 的修饰规则中,unsigned char 通常表示为 E,指针用 PA 表示。

3. 返回类型

  • 返回类型:char*
  • char 表示为 D,指针用 PA 表示。

4. 修饰规则

  • 私有成员函数的修饰符为 @@AAE
  • 参数列表以 @Z 结尾。

5. 完整修饰后的名称

根据上述规则,private: char* __thiscall R0Pxx::My_Aut0_PWN(unsigned char*) 的修饰后名称为:

1
>?My_Aut0_PWN@R0Pxx@@AAEPADPEAE@Z

解释:

  • ?My_Aut0_PWN@R0Pxx@@:表示类 R0Pxx 的私有成员函数 My_Aut0_PWN
  • AAE:表示私有成员函数的修饰符。
  • PAD:表示返回类型为 char*
  • PEAE:表示参数类型为 unsigned char*
  • @Z:表示参数列表结束。

如果需要进一步确认或解析修饰后的名称,可以使用工具如 undnameUnDecorateSymbolName API


?My_Aut0_PWN@R0Pxx@@AAEPADPEAE@Z

到这里就没头绪了,看了大佬的题解

2019 红帽杯 Re WP - Hk_Mayfly - 博客园

弱在了动态调试上,没发现result的变化,按照大佬的方法实际操作了一下

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
from hashlib import md5

str1 = 'abcdefghijklmnopqrstuvwxyz12345'
dec1 = '7071687273696474756A76776B656278796C7A316D6632336E34356F676361'.decode('hex')
serial = []

print dec1

for i in dec1:
serial.append(str1.index(i))

print serial

name = '?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z'
enc = [''] * 31

for i in range(31):
enc[serial[i]] = name[i]
enc = ''.join(enc)

print enc

print md5(enc).hexdigest()

# flag{63b148e750fed3a33419168ac58083f5}