攻防世界逆向高手题之crazy
继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的crazy
下载附件,照例扔入exeinfope中查看信息:
64位ELF文件,扔入对应IDA中查看信息,有main函数看main函数:
可以看到,一堆眼花缭乱的系统函数,有些则是用类名调用的普通C++函数。
这里积累第一个经验:(别人博客的一句话)
代码看着很乱,有很多很长的命令,解决办法:依据英文意思去猜测。
找关键变量的方法:从后往前找,看flag和输入关系。复杂代码本质应该是简洁的,这样才叫出题。
但是从后往前找与flag的有关变量还是很麻烦,所以我们用运行程序方法不断查看显示信息,锁定关键位置。(调试的话不知道断点下在那里可能要遍历很长时间)
。
。
。
第一次乱输入:
.
.
可以看到运行到checking…后显示too short or too long处,还有就是用户输入在字符串输出之前。返回IDA查看代码:
这里有个cin,也是主函数中在其它字符串之前的,cin是c++的输入函数,从这里积累第二个经验:该程序的长字符传命令中最后一个才是我们要关注的命令,比如这里的cin函数,像下面截图中字符串前面也有cout函数。
.
.
.
看check…字符串之后的函数:
可以看到checking到if判断语句之间还是有很多函数的,可以用前面的依据英文意思猜测的方法去看函数,也可以在strings窗口查找too short or too long处的函数位置:(我选择后者)
这里跟踪到HighTemplar::calculate函数,根据英文名是计算函数,但是我看不懂这里的this+16,凭借意思我推测是我们输入flag的地址,把我们输入的flag经过两个简单的循环异或加密后输出:
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 )
{
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, // 这里积累第三个经验:这是一个两步操作,v2取的是对应字符的地址,*v2是指在原v2地址上修改。把修改input_flag字符分成了两步做,让不熟悉的我载了跟头。
i);
*v2 = (*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16,
i) ^ 80)
+ 23;
}
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) ^ 19)
+ 11;
}
return result;
}
.
.
.然后由于HighTemplar::calculate函数的下一个就是if判断函数,所以我们只能跟踪if判断函数的HighTemplar::getSerial了。
.
.
前面this+16我推测是我们输入flag的地方,但是这个this+80存了什么东西我是真不知道了。双击跟踪堆栈也是未赋值的状态。这里积累第四个经验,逆向中不符合预期带份运算结果基本都是中间做了其它操作,如之前遇到的HOOK,这里很多函数我还没跟踪,那说明的确会有未发现的操作。
.
.
回到一开始cin函数的地方,表黄输入变量,看哪里还引用过该变量:
可以看到在checking前面还引用了一下,而该函数我们并没有分析,双击跟踪分析:
unsigned __int64 __fastcall HighTemplar::HighTemplar(DarkTemplar *a1, __int64 input_flag)
{
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, // C++函数,basic_string(字符串类模板),不是复制就是比较,这里是复制输入字符串给a1+16开始的地址的数组中
input_flag);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
(char *)a1 + 48, // C++函数,basic_string(字符串类模板),不是复制就是比较,这里是复制输入字符串给a1+48开始的地址的数组中,与前面隔了32个字符
input_flag);
std::allocator<char>::allocator(&v3);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(
(char *)a1 + 80,
"327a6c4304ad5938eaf0efb6cc3e53dc", // C++函数,basic_string(字符串类模板),不是复制就是比较,这里是复制输入字符串给a1+80开始的地址的数组中,与前面还是隔了32个字符,这个v3不清楚
&v3);
std::allocator<char>::~allocator(&v3);
return __readfsqword(0x28u) ^ v4;
}
这里我们可以看到把输入的flag分别给了this+16和this+48地址处,在this+80地址处给了327a6c4304ad5938eaf0efb6cc3e53dc这个字符串,那么前面对this+80的疑惑就解释得通了。
.
.
回过头去看this+80的那个HighTemplar::calculate函数:
__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, // this+80地址处是327a6c4304ad5938eaf0efb6cc3e53dc的字符串
(int)i);
if ( v1 != *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
(char *)this + 16, // this+80处如果不等于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);
}
.
.
简单的加密逻辑,直接写脚本逆向逻辑即可:
key1="327a6c4304ad5938eaf0efb6cc3e53dc"
flag1=""
flag=""
print(len(key1))
for i in range(len(key1)):
flag1+=chr((ord(key1[i])-11)^19)
for i in range(len(flag1)):
flag+=chr((ord(flag1[i])-23)^80)
print(flag)
.
.
结果:(这里积累第5个经验:现在的flag真的是越来越古灵精怪了,还有花括号,我一开始都以为我写错脚本了,现在看来,什么类型的flag都可以!一次不行就修改再交几次)
.
.
.
最后,下面这三个函数有什么用呢,我判断它是没什么用的,因为参数没有传入我输入的flag,跟踪里面也没有我输入的flag地址,除非是偏移地址间接引用我输入的flag,不过那样的话题就很难了!
.
.
.
总结:
1:这里积累第一个经验:(别人博客的一句话)
代码看着很乱,有很多很长的命令,解决办法:依据英文意思去猜测。
找关键变量的方法:从后往前找,看flag和输入关系。复杂代码本质应该是简洁的,这样才叫出题。但是从后往前找与flag的有关变量还是很麻烦,所以我们用运行程序方法不断查看显示信息,锁定关键位置。(调试的话不知道断点下在那里可能要遍历很长时间)
2:
这里积累第二个经验:该程序的长字符传命令中最后一个才是我们要关注的命令,比如这里的cin函数,像下面截图中字符串前面也有cout函数。
3:
这里积累第三个经验:这是一个两步操作,v2取的是对应字符的地址,*v2是指在原v2地址上修改。把修改input_flag字符分成了两步做,让不熟悉的我载了跟头。
4:
这里积累第四个经验,逆向中不符合预期带份运算结果基本都是中间做了其它操作,如之前遇到的HOOK,这里很多函数我还没跟踪,那说明的确会有未发现的操作。
5:
这里积累第5个经验:现在的flag真的是越来越古灵精怪了,还有花括号,我一开始都以为我写错脚本了,现在看来,什么类型的flag都可以!一次不行就修改再交几次
解毕!敬礼!