分析如下
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned __int64 index; // rbx
__int64 userInputLen_and_count; // rax
char *Malloc_61uLL; // rax
char *ptr_user_input_index1; // r10
__int64 v7; // r11
char *ptr_Malloc_61ull; // r8
_BYTE *res; // r9
unsigned __int8 user_input_first_element; // dl
unsigned __int64 v11; // rax
int v12; // ecx
__int64 v13; // rax
unsigned int v14; // edx
__int64 v15; // rdx
int toXorIndex; // r10d
__int64 v17; // rax
unsigned __int64 check_count; // rax
const char *v19; // rdx
char str_arry[64]; // [rsp+20h] [rbp-E0h] BYREF
char user_input[256]; // [rsp+60h] [rbp-A0h] BYREF
strcpy(str_arry, "M@ASL3MF`uL3ICT2IhUgKSD2IeDsICH7Hd26HhQgKSQhNCX7TVL3UFMeHi2?");
Out_Microsoft("input your flag:", argv, envp);
scanf_Microsoft_0("%s", user_input);
index = -1LL;
userInputLen_and_count = -1LL;
do
++userInputLen_and_count;
while ( user_input[userInputLen_and_count] );
if ( userInputLen_and_count == 44 )
{
Malloc_61uLL = (char *)malloc(61uLL);
ptr_user_input_index1 = &user_input[1];
v7 = 15LL;
ptr_Malloc_61ull = Malloc_61uLL;
Malloc_61uLL[60] = 0;
res = Malloc_61uLL + 2;
do
{
user_input_first_element = *(ptr_user_input_index1 - 1);
res += 4;
ptr_user_input_index1 += 3;
*(res - 6) = base64table[(unsigned __int64)user_input_first_element >> 2];
v11 = (unsigned __int8)*(ptr_user_input_index1 - 3);
v12 = v11 & 0xF;
v13 = (16LL * (user_input_first_element & 3)) | (v11 >> 4);
v14 = (unsigned __int8)*(ptr_user_input_index1 - 2);
*(res - 5) = base64table[v13]; // 换表的base64
*(res - 4) = base64table[((unsigned __int64)v14 >> 6) | (unsigned int)(4 * v12)];
*(res - 3) = base64table[v14 & 0x3F];
--v7;
}
while ( v7 );
v15 = 0LL;
toXorIndex = 0;
ptr_Malloc_61ull[59] = 61;
v17 = -1LL;
do
++v17;
while ( ptr_Malloc_61ull[v17] );
if ( v17 )
{
res = ptr_Malloc_61ull;
do
{
*res++ ^= 2u; // 变种base64后,每个字符和2异或
++toXorIndex;
check_count = -1LL;
do
++check_count;
while ( ptr_Malloc_61ull[check_count] );// 检测是否处理完全部字符
}
while ( toXorIndex < check_count );
}
LOBYTE(res) = 0; // 把0赋值给res的低字节
do
++index; // index从0开始
while ( str_arry[index] );
if ( index )
{
while ( ptr_Malloc_61ull[v15] == (unsigned __int8)str_arry[v15] )// v15从零开始,这里也是验证加密后的成果
{
LOBYTE(res) = (_BYTE)res + 1; // 逐字节比较
v15 = (char)res;
if ( (char)res >= index ) // 上面有个while,index自动停止到,strarry读取结束,成立跳转
goto LABEL_19;
}
v19 = "\nWrong! Try again!";
}
else
{
LABEL_19:
v19 = "\nYes you are right"; // 所以思路就很简单了,异或+base64换表
}
sub_7FF628A41330(
std::cout,
v19,
ptr_Malloc_61ull,
res,
*(_QWORD *)str_arry,
*(_QWORD *)&str_arry[8],
*(_QWORD *)&str_arry[16],
*(_QWORD *)&str_arry[24],
*(_QWORD *)&str_arry[32],
*(_QWORD *)&str_arry[40],
*(_QWORD *)&str_arry[48],
*(_QWORD *)&str_arry[56]);
}
else
{
Out_Microsoft("\nLength wrong. Must be 44");
}
return 0;
}
我们从上往下看,如下,说明str_arry是一个char array数组类型,所以我们重新命名str_arry的类型为char star_arrt[64]
strcpy(str_arry, "M@ASL3MF`uL3ICT2IhUgKSD2IeDsICH7Hd26HhQgKSQhNCX7TVL3UFMeHi2?");
Out_Microsoft("input your flag:", argv, envp);
如上,我们跟进这个函数
int Out_Microsoft(const char *const Format, ...)
{
FILE *stream; // rbx
unsigned __int64 *options; // rax
va_list va; // [rsp+58h] [rbp+10h] BYREF
va_start(va, Format);
stream = _acrt_iob_func(1u);
options = (unsigned __int64 *)get_options();
return _stdio_common_vfprintf(*options, stream, Format, 0LL, va);
}
这里面主要设计的是微软对printf函数的底层封装,详细内容见下面
### 获取标准输出流的文件指针(微软)
`_acrt_iob_func`:通常出现在 Windows 平台(尤其是使用 MSVC 编译器)的 C/C++ 程序中,**获取标准 I/O 流(stdin、stdout、stderr)对应的 `FILE` 结构体指针**。
其参数如下:
* `0u`:对应标准输入流(`stdin`)
* `1u`:对应标准输出流(`stdout`)
* `2u`:对应标准错误流(`stderr`)
其返回值就是`FILE` 结构体指针
**示例应用**:
获取 `v2`(标准输出流指针)后,通常用于文件操作函数,例如:
```c
v2 = _acrt_iob_func(1u);
// 向标准输出流(控制台)写入内容
fprintf(v2, "Hello, stdout!\n"); // 等价于 printf("Hello, stdout!\n");
// 刷新输出缓冲区
fflush(v2); // 等价于 fflush(stdout);
```
### 微软实现printf系列的底层函数
```c
int __cdecl _stdio_common_vfprintf(
unsigned __int64 options, // 控制行为的 flag
FILE *stream, // 输出目标流
char const *format, // 格式化字符串
_locale_t locale, // 本地化(通常传 0)
va_list args // 可变参数列表
);
```
微软在实现时会把 `printf` / `fprintf` 都重定向到它。
`options` 是一个 64 位的位掩码(bitmask/flags),用来控制函数的不同行为,微软文档里对 `options` 的表述是“修改函数行为的选项”。但没有具体公布示例。
我们需要关注的只有如下
stream = _acrt_iob_func(1u);
0u
:对应标准输入流(stdin
)1u
:对应标准输出流(stdout
)2u
:对应标准错误流(stderr
)
所以可以确定,下面这个就是输入流了
scanf_Microsoft_0("%s", user_input);
userInputLen_and_count = -1LL;
do
++userInputLen_and_count;
while ( user_input[userInputLen_and_count] );
if ( userInputLen_and_count == 44 )
从这个可以判断处我们需要输入的就是44字节长的flag
ptr_user_input_index1 += 3;
*(res - 6) = base64table[(unsigned __int64)user_input_first_element >> 2];
v11 = (unsigned __int8)*(ptr_user_input_index1 - 3);
v12 = v11 & 0xF;
v13 = (16LL * (user_input_first_element & 3)) | (v11 >> 4);
v14 = (unsigned __int8)*(ptr_user_input_index1 - 2);
*(res - 5) = base64table[v13]; // 换表的base64
*(res - 4) = base64table[((unsigned __int64)v14 >> 6) | (unsigned int)(4 * v12)];
*(res - 3) = base64table[v14 & 0x3F];
从四个*(res-xx),和与0x3F以及base64table的跟进,我们可以推定这个就是base64的换表。
if ( v17 )
{
res = ptr_Malloc_61ull;
do
{
*res++ ^= 2u; // 变种base64后,每个字符和2异或
++toXorIndex;
check_count = -1LL;
do
++check_count;
while ( ptr_Malloc_61ull[check_count] );// 检测是否处理完全部字符
}
while ( toXorIndex < check_count );
}
如上面和下面代码,就是又进行了一个异或,然后验证字符,至此逻辑就很简单了,就是我们提取字符M@ASL3MF`uL3ICT2IhUgKSD2IeDsICH7Hd26HhQgKSQhNCX7TVL3UFMeHi2?进行异或,和换表base64就出来了。
while ( ptr_Malloc_61ull[v15] == (unsigned __int8)str_arry[v15] )// v15从零开始,这里也是验证加密后的成果
{
LOBYTE(res) = (_BYTE)res + 1; // 逐字节比较
v15 = (char)res;
if ( (char)res >= index ) // 上面有个while,index自动停止到,strarry读取结束,成立跳转
至于换表base64我们可以使用python如下
import base64
encoded = "zCN7zTJJP3hj71C3Bxsj72susnhQ="
old = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
new = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
mapper = str.maketrans(new, old)
tmp = encoded.translate(mapper)
flag = base64.b64decode(tmp)
print(flag.decode())
或者赛博厨子