目录
[2019红帽杯]childRE
修饰函数名和函数签名是什么?
对于变换部分的具体分析:
[2019红帽杯]childRE
下载附件,查壳,无壳
在IDA中打开,定位主函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 len; // rax
_QWORD *v4; // rax
const CHAR *v5; // r11
__int64 v6; // r10
int v7; // r9d
const CHAR *v8; // r10
__int64 v9; // rcx
__int64 *v10; // rax
unsigned int i; // ecx
__int64 j; // r9
__int128 input[2]; // [rsp+20h] [rbp-38h] BYREF
memset(input, 0, sizeof(input));
scanf("%s");
len = -1i64;
do
++len;
while ( *(input + len) );
if ( len != 31 ) // 输入长度判断
{
while ( 1 )
Sleep(1000u);
}
v4 = sub_7FF6EDF11280(input);
v5 = name; // 待转换的函数名
if ( v4 )
{
sub_7FF6EDF115C0(v4[1]); // 变换
sub_7FF6EDF115C0(*(v6 + 16));
v7 = chr;
v5[chr] = *v8;
chr = v7 + 1; // i=i+1
}
UnDecorateSymbolName(v5, outputString, 0x100u, 0);// 将修饰后的函数名称转换为函数签名,0是转换符号,表示全转换
v9 = -1i64; // v5=name=修饰后的函数名称
do
++v9;
while ( outputString[v9] );
if ( v9 == 62 )
{
i = 0;
j = 0i64;
do
{
if ( a1234567890Qwer[outputString[j] % 23] != a46200860044218[j] )// 确定outputString%23的余数
_exit(i);
if ( a1234567890Qwer[outputString[j] / 23] != a55565653255552[j] )// 确定outputString/23的商
_exit(i * i);
++i;
++j;
}
while ( i < 62 );
printf("flag{MD5(your input)}\n");
return 0;
}
else
{
v10 = sub_7FF6EDF118A0(std::cout);
std::ostream::operator<<(v10, sub_7FF6EDF11A60);
return -1;
}
}
这一部分是分配内存,输入字符串后再对字符串进行长度判断,要求长度为31
这一部分是对输入字符串的变换,后面具体说明
这一部分中UnDecorateSymbolName()函数是将修饰后的函数名转换为函数签名,v5(也就是name)是修饰后的函数名称 ,outputString是转换成的函数签名,0x100u代表长度,0表示全转换
修饰函数名和函数签名是什么?
20世纪70年代以前,一个函数经过编译器编译后,存储在目标文件内的符号与函数名相同。比如定义一个函数func1,在目标函数中的符号名称也为func1。
但是随着汇编编写库的丰富,定义的函数很有可能与库中的函数名冲突。为了解决这一问题,编译器在将源代码编译成目标文件时,会将函数和变量的名字进行修饰,形成符号名,也就是符号修饰/名字修饰(Decorated Name)函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串,用来指明函数的定义或原型。目的是方便编译过程中,链接器等中间过程识别不同的函数,尤其是在引入重载后,识别函数不能只看函数名,要结合参数、返回值类型来识别。函数签名包含了一个函数的信息,包括函数名、参数类型、所在类、名称空间及其他信息。
比如:
函数int func(int x)
的函数签名为:int func(int)
每个函数签名对应一个修饰后的名字。
具体怎么修饰:可以参考这几篇文章:
(156条消息) c, c++函数名编译符号修饰符说明_IT超人的博客-CSDN博客
(156条消息) 关于去除PE文件中函数修饰的做法_undecoratesymbolname_clever101的博客-CSDN博客
(156条消息) VS编译器C/C++函数编译后的名字修饰_android编译后函数名_大雄_RE的博客-CSDN博客先有个印象,知道这里有个修饰函数名转函数签名, 往后看,了解程序大致作用后再来了解这部分
这部分就是根据outputString的值做的判断了,第一个if语句可以推断出outputString%23的值,第二个if判断可以推出outputstring/23的值。两者联系可以推出整个outputString的值,上脚本
#include<string.h>
#include<stdlib.h>
int main(){
char a[]="(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&";
char b[]="55565653255552225565565555243466334653663544426565555525555222";
char c[]="1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;ASDFGHJKL:ZXCVBNM<>?zxcvbnm,.";
int data[100];int cnt,tent;
int i,j;
for(i=0;i<62;i++){
for(j=0;j<100;j++){
if(c[j]==a[i])
cnt=j;
if(c[j]==b[i])
tent=j;
}
printf("%c",tent*23+cnt);
}
return 0;
}
解得:private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
不难看出是个函数签名(其实当时看了半天....)好了,现在我们知道了函数签名outputString字符串的值,结合程序的整体思路:将输入的字符串经过变换后,再传给UnDecorateSymbolName()函数做修饰变换,最后得到outputString;现在我们需要知道函数签名对应的修饰函数,v5即name的值。根据上面推荐的文章进行转换:
name=?My_Aut0_PWN
name=?My_Aut0_PWN@R0Pxx
name=?My_Aut0_PWN@R0Pxx@@AAE
函数的返回值是chaar*类型即PAD
函数的参数是unsigned char*类型即PAE
name=?My_Aut0_PWN@R0Pxx@@AAEPADPAE
name=?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z
现在,得到了修饰函数的值,再往前推,看这部分怎样进行加密的
动态调试
尽量输入有顺序的31位数,方便观察怎样变换的,F7进入函数查看具体变换,得到如下顺序
对应下标变换就是
15,7,17,18,8,3,19,20,9,21,22,10,4,1,23,24,11,25,26,12,5,27,28,13,29,30,14,6,2,0
逆向解出输入的值
#include<stdio.h>
#include<string.h>
int main(){
char a[]="?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z";
int b[31]={15,16,7,17,18,8,3,19,20,9,21,22,10,4,1,23,24,11,25,26,12,5,27,28,13,29,30,14,6,2,0};
int i,j,cnt=0;
char c[31];
for(i=0;i<strlen(a);i++){
c[b[cnt]]=a[i];
cnt++;
}
for(i=0;i<strlen(a);i++)
printf("%c",c[i]);
return 0;
}
得到值:Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP 再进行MD5加密就可以得到flag啦
对于变换部分的具体分析:
这个部分应该是二叉树,
是进行了建树操作
这部分是后序遍历,即变换操作 ,这样得到最后的序列