攻防世界逆向入门题之maze
继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向入门题的maze
我本来以为我越过了新手区,本来想信心满满地自己做出一道题,结果攻防世界还是给了我一个大巴掌。
附件下载扔入exeinpefo中查看信息:
64位ELF文件,无壳,先扔入IDA中查看伪代码判断题目类型,再决定要不要在linux中运行:
从这里犯下第一个错误,我竟然对第一个判断语句的!=125的125不知所云,还去查了ASCII表,对后面的79,46这些竟然也想查。真的得给自己个一巴掌,flag基本都是字符和数字混合,而且在IDA里数字转ASCII字符直接快捷键R啊!!!!
然后判断题目类型是本身就有的存储型flag还是用用户输入一个个生成的生成型flag。答案是后者,那gdb调试就没法用了,直接静态分析代码即可。
转了字符后基本就明白了,现在开始代码分析了,
代码分析:
puts("Input flag:");
scanf("%s", &input_flag, 0LL);
if ( strlen(&input_flag) != 24 || strncmp(&input_flag, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' ) //这里要求输入的flag是24个字符,且前5个和最后一个都确定了,一开始的125真的搞得我都不知道啥意思。后面的Oo.0也是如此
{
LABEL_22:
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&input_flag) - 1 > 5 )
{
while ( 1 )
{
singleflag = *(&input_flag + v3); // 这里v3是从5开始递增的数,目的是从第5个字符开始判断是否符合下述条件
v5 = 0;
if ( singleflag > 78 ) //这里给个范围,ASCII码大于78的划为第一类
{
singleflag = (unsigned __int8)singleflag;
if ( (unsigned __int8)singleflag == 'O' )//如果第一个取O
{
v6 = sub_400650((_DWORD *)&v9 + 1); // 这里犯下第二个错误,64位的v9分成取高底32字节其实是分到r14和r15两个寄存器的,底32位在r14,高32位在r15才有后面根据寄存器的分开操作,因为在两个不同寄存器中。
goto LABEL_14;
}
if ( singleflag == 'o' )//如果第一个取o
{
v6 = sub_400660((int *)&v9 + 1); // 有符号32位高字节操作,r15寄存器
goto LABEL_14;
}
}
else
{
singleflag = (unsigned __int8)singleflag;
if ( (unsigned __int8)singleflag == '.' )//如果取到.
{
v6 = sub_400670(&v9); // 无符号底字节32位操作,r14寄存器
goto LABEL_14;
}
if ( singleflag == '0' )
{
v6 = sub_400680((int *)&v9); // 有符号底字节32位,r14寄存器
LABEL_14:
v5 = v6;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)sub_400690((__int64)asc_601060, SHIDWORD(v9), v9) )
goto LABEL_22;
if ( ++v3 >= strlen(&input_flag) - 1 ) //在flag范围内v3加1,对应前面singleflag取第6、7、8~个一个个比较
{
if ( v5 ) //如果flag取完了,且sub_这些函数没有返回flase,也就是没有越界,就可以判断是否抵达终点了
break;
LABEL_20:
v7 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != '#' ) //判断是否为#这个终点。
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}
第二个错误看IDA反汇编结构图,底双字在r14寄存器,高双字在r15寄存器:
这里犯下的第三个错误就是对sub_400650、sub_400660、sub_400670、sub_400680、sub_400690、asc_601060、这些IDA自己命名的函数不敢去看!
总是觉得自己看不懂,害怕!!!后来才发现其实不应该害怕的!!要逼自己一把!!!
bool __fastcall sub_400650(_DWORD *a1)
{
int v1; // eax
v1 = (*a1)--;
return v1 > 0;
}
bool __fastcall sub_400660(int *a1)
{
int v1; // eax
v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
}
bool __fastcall sub_400670(_DWORD *a1)
{
int v1; // eax
v1 = (*a1)--;
return v1 > 0;
}
bool __fastcall sub_400680(int *a1)
{
int v1; // eax
v1 = *a1 + 1;
*a1 = v1;
return v1 < 8;
}
这四个函数点开之后是对传入参数+1 -1操作而已,真的不难,而且附带返回的比较后来查资料说是判断有没有越出迷宫边界,false就是越出了,就不用玩了,为true就是没越出,继续玩。
(unsigned __int8)sub_400690((__int64)asc_601060, SHIDWORD(v9), v9)
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3)
{
__int64 result; // rax
result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);
LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';
return result;
}
这里sub_400690点进去分析后的(__int64)asc_601060如图是一串字符串,后来知道了是迷宫的图,sub_400690函数里传入v9的有符号高双字r15寄存器,和v9底双字的r14寄存器,然后运算表达式result = *(unsigned __int8 )(a1 + a2 + 8LL * a3); 就是在asc_601060字符串数组内取字符而已,可以看出a38,所以这是8个字符为一行,也就是说r14寄存器的底双字表示行,r15高双字表示列,+1-1分别对应着向上向下,向左向右移动。
O是左移,o是右移,0是下移,.是上移
所以这里可以写出asc_601060的迷宫图形:
******
* * *
*** * **
** * **
* *# *
** *** *
** *
********
if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != '#' )
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
最后这里就是看最后跳出的flag末尾时是不是到了#这个字符,如果是就表示通关。
所以是:(看得我头晕)
右下右右下下左下下下右右右右上上左左
就是o0oo00O000oooo…OO
总结:
从这里犯下第一个错误,我竟然对第一个判断语句的!=125的125不知所云,还去查了ASCII表,对后面的79,46这些竟然也想查。真的得给自己个一巴掌,flag基本都是字符和数字混合,而且在IDA里数字转ASCII字符直接快捷键R啊!!!!
这里犯下第二个错误,64位的v9分成取高底32字节其实是分到r14和r15两个寄存器的,底32位在r14,高32位在r15才有后面根据寄存器的分开操作,因为在两个不同寄存器中。
这里犯下的第三个错误就是对sub_400650、sub_400660、sub_400670、sub_400680、sub_400690、asc_601060、这些IDA自己命名的函数不敢去看!
总是觉得自己看不懂,害怕!!!后来才发现其实不应该害怕的!!要逼自己一把!!!
解毕!敬礼!