附件链接
https://adworld.xctf.org.cn/media/file/task/54836ebebad142679...
这一题没有什么花活,但是和以往的c程序不同,c++大量使用模板、类对象等,反汇编之后会非常丑陋。要努力去克服这种干扰,并且熟悉string这种常见类的成员函数
分析
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
__int64 v12; // rax
__int64 v13; // rax
__int64 v14; // rax
__int64 v15; // rax
__int64 v16; // rax
char v18[32]; // [rsp+10h] [rbp-130h] BYREF
char v19[32]; // [rsp+30h] [rbp-110h] BYREF
char v20[32]; // [rsp+50h] [rbp-F0h] BYREF
char v21[32]; // [rsp+70h] [rbp-D0h] BYREF
char v22[32]; // [rsp+90h] [rbp-B0h] BYREF
char v23[120]; // [rsp+B0h] [rbp-90h] BYREF
unsigned __int64 v24; // [rsp+128h] [rbp-18h]
v24 = __readfsqword(0x28u); //注意这里只是读取canary值,并不是真正的输入
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v18, argv, envp);
std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, v18); //这里从键盘读入 v18
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Quote from people's champ");
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
v6 = std::operator<<<std::char_traits<char>>(
&std::cout,
"*My goal was never to be the loudest or the craziest. It was to be the most entertaining.");
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
v7 = std::operator<<<std::char_traits<char>>(&std::cout, "*Wrestling was like stand-up comedy for me.");
std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
v8 = std::operator<<<std::char_traits<char>>(
&std::cout,
"*I like to use the hard times in the past to motivate me today.");
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
v9 = std::operator<<<std::char_traits<char>>(&std::cout, "-------------------------------------------");
std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
HighTemplar::HighTemplar(v23, v18); //这里用v23和v18作为参数来构造一个类对象。注意v23在最开始是分配过内存的,什么数据都没有记载,可能是buffer
v10 = std::operator<<<std::char_traits<char>>(&std::cout, "Checking....");
std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v19, v18);
func1(v20, v19);
func2(v21, v20);
func3(v21, 0LL); //这三个函数的功能未知,但似乎与我们的结果无关
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v21);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v20);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v19);
HighTemplar::calculate((HighTemplar *)v23); //calculator接受v23参数,经过分析可以知道是将input进行数据加密(当然是后话了)
if ( !(unsigned int)HighTemplar::getSerial((HighTemplar *)v23) ) //只要我们进入这个if内部就能够得到flag,getSerial的作用是比较判断
{
v11 = std::operator<<<std::char_traits<char>>(&std::cout, "/////////////////////////////////");
std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
v12 = std::operator<<<std::char_traits<char>>(&std::cout, "Do not be angry. Happy Hacking :)");
std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
v13 = std::operator<<<std::char_traits<char>>(&std::cout, "/////////////////////////////////");
std::ostream::operator<<(v13, &std::endl<char,std::char_traits<char>>);
HighTemplar::getFlag[abi:cxx11](v22, v23);
v14 = std::operator<<<std::char_traits<char>>(&std::cout, "flag{");
v15 = std::operator<<<char,std::char_traits<char>,std::allocator<char>>(v14, v22);
v16 = std::operator<<<std::char_traits<char>>(v15, "}");
std::ostream::operator<<(v16, &std::endl<char,std::char_traits<char>>);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v22);
}
HighTemplar::~HighTemplar((HighTemplar *)v23);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v18);
return 0;
}
c++反汇编后的程序并不优美,我们要根据上下文猜测一些语句的作用。我们发现,一些用到>>和<<的语句,虽然前面很长,但通过看这个语句的尾部,如(&std::cin, v18)也能大致猜出其功能
另一个困难是充斥着大量的basic_string,我们在后续用到的时候再查阅其功能即可
下面看一看几个重要函数:
- HighTemplar构造函数:
unsigned __int64 __fastcall HighTemplar::HighTemplar(HighTemplar *a1, __int64 a2)
{
char v3; // [rsp+17h] [rbp-19h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
DarkTemplar::DarkTemplar(a1);
*(_QWORD *)a1 = &off_401EA0;
*((_DWORD *)a1 + 3) = 0;
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string((char *)a1 + 16, a2); //注意仔细阅读这一句的代码,理清结构
//a1是第一个参数,也就是我们上述猜测的buffer,将它作为基地址,在+16处填充a2的数据 std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string((char *)a1 + 48, a2); //在+48这里也填充数据
std::allocator<char>::allocator(&v3);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
(char *)a1 + 80,
"327a6c4304ad5938eaf0efb6cc3e53dc", //在+80这里填充固定的一段数据
&v3);
std::allocator<char>::~allocator(&v3);
return __readfsqword(0x28u) ^ v4;
}
basic_string构造函数可能有多种重载,这里我们用到的是copy形式
注意“类”是编程语言层级的抽象,在汇编层级并不存在,因此在反汇编时并不能复原所有信息,需要我们通过上下文进行类结构的猜测复原
- calculate
bool __fastcall HighTemplar::calculate(HighTemplar *this)
{
__int64 v1; // rax
_BYTE *v2; // rbx
bool result; // al
_BYTE *v4; // rbx
int i; // [rsp+18h] [rbp-18h]
int j; // [rsp+1Ch] [rbp-14h]
if ( std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16) != 32 ) //从这里我们可以知道,读入的数据必须是32个字符
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Too short or too long");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
exit(-1);
}
for ( i = 0;
i <= (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16);
++i )
{
v2 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16,
i); //注意熟悉operator[]的代码,熟悉其结构
*v2 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16,
i) ^ 0x50)
+ 23;
} //第一轮,执行的轮数等于数据的长度,也就是32(但好像在临界处是<=号?是33轮?)
for ( j = 0; ; ++j )
{
result = j <= (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16);
if ( !result )
break;
v4 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16,
j);
*v4 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16,
j) ^ 0x13)
+ 11;
} //第二轮加密
return result;
}
我们发现calculate函数就是将input的每个字节进行^0x50 -> +23 -> ^0x13 -> +11
- getSerial
__int64 __fastcall HighTemplar::getSerial(HighTemplar *this)
{
char v1; // bl
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
unsigned int i; // [rsp+1Ch] [rbp-14h]
for ( i = 0;
(int)i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length((char *)this + 16);
++i )
{
v1 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 80,
(int)i);
if ( v1 != *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16,
(int)i) )
{
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "You did not pass ");
v5 = std::ostream::operator<<(v4, i);
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
*((_DWORD *)this + 3) = 1;
return *((unsigned int *)this + 3);
}
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Pass ");
v3 = std::ostream::operator<<(v2, i);
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}
return *((unsigned int *)this + 3);
}
这个就简单了,将+16处和+80处进行逐字节比较
解密
编写解密程序:
#include<stdio.h>
int main(void){
char dst[]="327a6c4304ad5938eaf0efb6cc3e53dc";
char obj[64];
unsigned char b;
for (int i=0;i<32;i++){
b=((dst[i]-11)^0x13-23)^0x50;
printf("%c",b);
}
return 0;
}
但结果输出是一团乱码,我甚至试过用pwntools工具将其喂给crazy,但是仍然显示不通过
后来才发现是异或运算符优先级的问题!!!
只需要该一行:
b=(((dst[i]-11)^0x13)-23)^0x50;
输出为tMx~qdstOs~crvtwb~aOba}qddtbrtcd
再输入到crazy中得到flag:flag{tMx~qdstOs~crvtwb~aOba}qddtbrtcd}
疑惑
在HighTemplar对象中,+16处开始填充了32字节的input,+48处开始填充了32字节的input,+80处填充32字节的data,这三部分难道不应该是无空隙相连的吗?为什么在实际运行时lenth还是返回32?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。