题目链接
https://adworld.xctf.org.cn/challenges/list
题目详情
Reversing-x64Elf-100
解题报告
下载得到的文件使用ida64分析,如果报错就换ida32,得到分析结果,有main函数就先看main
main函数分析
大致逻辑便理清楚了,输入的字符串s要使函数sub_4006FD返回false,这样才能进入else的逻辑判断
sub_4006FD函数分析
双击进入sub_4006FD函数进一步分析函数逻辑
这里首先定义了三个字符串
然后设置循环,每次循环对字符串中的字符进行运算后判断是否结果!=1
但是联系前文,我们需要这个函数返回false,所以我们得让这个条件在12次循环内全为假,最终才能走到这个16行返回false
那么问题就简化成了让【*(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) != 1】 这个条件为假,简单变换为让【*(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) == 1】为真,即经过运算的字符最终结果始终等于1
对于这个算式,涉及到c语言的指针用法,下面进行详细分析:
①v3本质是一个二维数组,v[0]、v[1]、v[2]分别存放上图的三个字符串
②i%3很好理解了,这个结果只会是0,1,2,通过模运算来确保了数组v3不会越界
③对c语言数组稍微有点理解的话,就能明白v[0]、v[1]、v[2]分别代表的是三个字符串的首元素地址,也可以视作指针,对指针进行【+ 2 * (i / 3)】的加法运算,就是让指针的地址往后偏移,经过简单计算不难发现这个算式的结果在0~6之间(7就是三个字符串的长度,下标从0开始),也就确保了二维数组访问三个字符串的字符时都不会越界
例如v[0]代表的是【"Dufhbmf"】中的字符'D'地址,v[0]+1就是字符'u'的地址
如此一来,【v3[i % 3] + 2 * (i / 3)】的意思就是让指针往后偏移,去指向后面的字符
④(char*)指针强转,这步操作也挺关键的,经过第③步的操作,实际上获得到的还是字符串的地址而不是单个字符的地址,所以使用强制转换,将其转为字符指针,最后再使用*指针操作符对字符地址解引用,这下才彻底获取到对应的字符
其实,【*(char *)(v3[i % 3] + 2 * (i / 3))】就等效于【v[i % 3][2 * (i /3)]】,前者是二维数组的指针访问形式
⑤后半部分的【*(char *)(i + a1)】的话,其中a1是我们输入的字符串(函数形参,其实就是前面在main函数里面输入的字符串s),a1+i的话也是进行指针偏移,*(char *)跟上面第④步的操作就同理了,所以这边这个表达式的含义就是访问我们输入的字符串中的字符
综上所述,【*(char *)(v3[i % 3] + 2 * (i / 3))】是获取三个已定义字符串中的字符(记作str),【*(char *)(i + a1)】是获取输入字符中的字符(记作flag),最后拿str减去flag(当然是拿ASCII码来计算了),得到的结果需要为1,即【str - flag == 1】,到这一步逆向思路已经一目了然,即【flag = str - 1】,代入前面的str表达式就是【flag = *(char *)(v3[i % 3] + 2 * (i / 3)) - 1】,至此,脚本就完全会写了
EXP
#include<iostream> using namespace std; int main(){ char v3[3][10] = {"Dufhbmf", "pG`imos", "ewUglpt"}; //ida里面定义字符串的语法是错误的,c/c++字符数组只能在定义时赋值,同时要稍微给大一点,确保能存放下 string flag;//这里flag直接用了c++里string类型,重载了+操作符实现字符拼接 for(int i = 0; i <= 11; i++){ flag += *(char *)(v3[i % 3] + 2 * (i / 3)) - 1;//按我们推理出来的逆向公式把符合要求的字符计算出来,然后拼接进flag } cout << flag;//输出最终flag即可 }
总结
本题涉及到的知识点其实蛮多的,攻防世界把它放在题库里reverse方向新手模式的第一题,属实有点劝退新童鞋
涉及到的关键知识点:
- 一维数组名的含义:数组首元素地址(指针)
- 指针加法运算含义:指针地址向后偏移
- 二维数组指针形式访问:*(char*)(arr[i] + j)完全等效于arr[i][j]