asong
算是小白第一次接触逆向真正算法的题吧. 吃了算法的亏, 逆向这个算法想了半天🤣
0x1分析
-
下载下来三个文件, 但这不是我以前遇到的那种同一个题给不同的版本.
-
依次看了下, 一个64位elf文件无壳, 2个文本文件. 打开文本文件后,没有头绪. 将elf文件载入ida.
-
整个流程开始看不清楚做什么, 多看几次明白了. 先输入字符串, 打开一个that_girl文件读信息, 最后向out文件写信息. 但这怎么和flag联系起来呢. 其实那个out文件里的就是密文, 我们找到逆向算法, 通过密文求出明文(即我们的输入, 也是flag)
-
下面具体分析每个函数. 第一函数: sub_400B4C 由于很简单, 直接给出函数功能.
- sub_400B4C: 先判断, 再取 { } 之间的内容.
- 下面就是我遇到的难点 1: v4哪里来的及 ++*(_DWORD *)(4LL * v2 + v4);语句的作用
-
第一次遇到, 没有经验, 后来才想到通过汇编的查看, v4即是我们传了参数 a2(也就是主函数的v3), 这是通过寄存器传递. 64位与32位程序的区别吧. 但该语句功能还不是很清楚.
-
继续看一个函数,sub_400E54: 看到这里, 就很清晰了, 就是先通过统计that_girl文件中每个字符出现的次数, 然后按照一定的顺序输入字符, 把每个字符出现的次数按一定顺序给下面的 v5 数组赋值. 那 sub_400936转换字符函数我们是不用管的.
- 下面是2个加密函数, 我算法太菜, 逆算法想半天😅, 首先sub_400D33((unsigned __int8 *)v5);
- 然后 sub_400DB4(v5, v4); 解释起来有点抽象…注意是一个字节, 所以多出的位要舍弃
- 举个例子:
- 最后一个函数: **sub_400CC0((__int64)v5, “out”, v4);**将加密后的值写入 out文件.
0x2逆向解密
-
直接exp:
#include <stdio.h> int main(void) { union { unsigned char ida[152]; int change[38]; }A = { 22, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 30, 0, 0, 0, 24, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 21, 0, 0, 0, 7, 0, 0, 0, 18, 0, 0, 0, 10, 0, 0, 0, 8, 0, 0, 0, 12, 0, 0, 0, 17, 0, 0, 0, 23, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 14, 0, 0, 0, 19, 0, 0, 0, 11, 0, 0, 0, 20, 0, 0, 0, 16, 0, 0, 0, 15, 0, 0, 0, 5, 0, 0, 0, 25, 0, 0, 0, 36, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 29, 0, 0, 0, 37, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 26, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0 }; int i = 0, j = 0, a[100] = {0}, temp = 0, lenth = 0; unsigned char b[100] = {0}, c[2000] = {0}; int count[127] = {0}, change_[38] = {0}; char flag[100] = {0}; FILE *fp = NULL; fp = fopen("out", "rb"); //先从文件读取密文 lenth = fread(b, 1, 100, fp); //返回读取成功个数, 确定字符个数 fclose(fp); //第一层解密 temp = b[lenth-1]&7; //&7 取后三位数据. for(i = 0; i < lenth; i++) { //从整体来看就是将整个数据右移动3位 a[i] = (b[i] >> 3) | (temp << 5); temp = b[i]&7; } fp = fopen("that_girl", "rb"); //打开that_girl, 统计字符数. fread(c, 1, 1742, fp); fclose(fp); for(i = 0; i < sizeof(c); i++) { if(c[i] >= 65 && c[i] <= 90) c[i] += 32; //因为不区分大小写, 都转换为小写 count[c[i]]++; } i = 0, j = 0; while(A.change[i]) { change_[j++] = A.change[i]; //统计每个字符出现次数 i = A.change[i]; } j -= 1; temp = a[0], a[0] = a[change_[j]]; //第二层解密 for(i = 0; i < lenth-2; i++) //按照给定赋值找到一个未变的值, 倒序赋值回去即可 { a[change_[j]] = a[change_[--j]]; } a[change_[j]] = temp; for(i = 0; i < lenth; i++) //第三层解密, 比对每个字符出现的次数的顺序,进而确定输入顺序 { for(j = 0; j < 127; j++) { if(a[i] == count[j]) { flag[i] = j; break; } } } printf("QCTF{%s}", flag); //得到flag. return 0; }
- 总结: (1)算法还是太重要. 算法稍微好点的话, 这道题就轻松很多. (2)注意64位程序的传递参数的方式(3)要习惯看汇编代码