一、通用思路
1、检查文件类型
我们可以将文件拖入kali中,kali中有自带检查文件类型的命令 file ,判断是ELF还是PE,32位还是64位
2、运行程序
放在沙箱环境中尝试运行,了解程序运作原理,只有清楚了解程序是如何运行的才有机会对其逆向
3、strings命令列出程序的二进制
通过查看文件二进制我们可能可以看到其调用的一些文件、一些库函数、甚至是一些字符串 flag 等
4、查壳
通过软件进行查壳,若程序存在壳则尝试利用 upx 命令工具去壳
5、利用工具进行逆向
将程序放入反编译软件进行逆向,查看其伪代码,通过分析伪代码获取 flag
若反编译后函数较少,则可能是程序加了壳
若搜索不到main函数,则可能是程序名隐藏了或进行了混淆
二、题目分类
第一类:z3类
z3类一般是做一些算法,或者数学里面的运算。而z3是一个python中的运算器,我们可以利用这个运算器进行一些复杂的运算从而使我们的运算简化,从而称作为z3类
例题:CTFSHOW——数学不及格
下载附件后 r3 我们按照套路进行分析:
(1)利用kali中的 file 工具对 r3 进行文件格式的分析
我们可以发现此程序是一个 ELF 的64位可执行程序,那我们可以在kali中允许看看程序大概是如何执行的
(2)我们执行发现输出了一个语句 argc nonono,看起来似乎是进行了什么判断,条件不符合从而输出了argc nonono这条语句
随便再后面加上几个参数也是如此,从这里我们看不出该程序是如何执行的,我们可以利用 String 查看其二进制文件
(3)发现程序会输出如下语句,并且其包含了我们刚刚执行程序输出的语句,通过这些打印判断应该是通过了某些条件则会打印 well done!decode your argv!
(4)接着我们把 ELF 程序放入IDA中进行反编译分析,放入IDA找到主程序
int __cdecl main(int argc, const char **argv, const char **envp) { signed int v4; // [rsp+14h] [rbp-4Ch] char *endptr; // [rsp+18h] [rbp-48h] char *v6; // [rsp+20h] [rbp-40h] char *v7; // [rsp+28h] [rbp-38h] char *v8; // [rsp+30h] [rbp-30h] __int64 v9; // [rsp+38h] [rbp-28h] __int64 v10; // [rsp+40h] [rbp-20h] __int64 v11; // [rsp+48h] [rbp-18h] __int64 v12; // [rsp+50h] [rbp-10h] unsigned __int64 v13; // [rsp+58h] [rbp-8h] v13 = __readfsqword(0x28u); if ( argc != 5 ) { puts("argc nonono"); exit(1); } v4 = (unsigned __int64)strtol(argv[4], &endptr, 16) - 0x6543; v9 = f(v4); v10 = strtol(argv[1], &v6, 16); v11 = strtol(argv[2], &v7, 16); v12 = strtol(argv[3], &v8, 16); if ( v9 - v10 != 0x233F0E151CLL ) { puts("argv1 nonono!"); exit(1); } if ( v9 - v11 != 0x1B45F81A32LL ) { puts("argv2 nonono!"); exit(1); } if ( v9 - v12 != 0x244C071725LL ) { puts("argv3 nonono!"); exit(1); } if ( v4 + v12 + v11 + v10 != 0x13A31412F8CLL ) { puts("argv sum nonono!"); exit(1); } puts("well done!decode your argv!"); return 0; }
我们找到主程序逐一分析:
signed int v4; // [rsp+14h] [rbp-4Ch] char *endptr; // [rsp+18h] [rbp-48h] char *v6; // [rsp+20h] [rbp-40h] char *v7; // [rsp+28h] [rbp-38h] char *v8; // [rsp+30h] [rbp-30h] __int64 v9; // [rsp+38h] [rbp-28h] __int64 v10; // [rsp+40h] [rbp-20h] __int64 v11; // [rsp+48h] [rbp-18h] __int64 v12; // [rsp+50h] [rbp-10h] unsigned __int64 v13; // [rsp+58h] [rbp-8h]
这块内容是主程序的定义参数部分
v13 = __readfsqword(0x28u); if ( argc != 5 ) { puts("argc nonono"); exit(1); }
argc
表示命令行参数的个数(argument count),包括程序本身。即 argc 的值至少为 1因此我们可以从程序中知道,argc就是主函数的第一个参数,也就是输入参数的个数,表明参数的个数不能少于 5 个(包括程序本身),因此我们要输入4个参数
v4 = (unsigned __int64)strtol(argv[4], &endptr, 16) - 0x6543; v9 = f(v4); v10 = strtol(argv[1], &v6, 16); v11 = strtol(argv[2], &v7, 16); v12 = strtol(argv[3], &v8, 16);
long int strtol(const char *str, char **endptr, int base)
str -- 要转换为长整数的字符串。
endptr -- 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
base -- 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。如果 base 为 0,则会根据字符串的前缀来判断进制:如果字符串以 '0x' 或 '0X' 开头,则将其视为十六进制;如果字符串以 '0' 开头,则将其视为八进制;否则将其视为十进制。
这部分重点是 strtol 函数,我们可以通过百度百科查询到 strtol 作用是把参数 str 所指向的字符串根据给定的 base 转换为一个长整数
因此我们可以知道:
v4 是将输入的第4个参数转化为16进制后减去 0x6543
v10是将第一个参数转化为16进制
v11是将第二个参数转化为16进制
v12是将第三个参数转化为16进制
v9和v4的关系是通过函数f,因此我们可以进入查看f函数是什么内容
而剩下的代码都是一些参数的判断了,我们先放着,接着查看 f 函数是什么内容 (双击 f 函数即可进入查看)
(5)分析 f 函数
__int64 __fastcall f(signed int a1) { signed int i; // [rsp+1Ch] [rbp-14h] __int64 v3; // [rsp+20h] [rbp-10h] _QWORD *ptr; // [rsp+28h] [rbp-8h] if ( a1 <= 1 || a1 > 200 ) return 0LL; ptr = malloc(8LL * a1); *ptr = 1LL; ptr[1] = 1LL; v3 = 0LL; for ( i = 2; i < a1; ++i ) { ptr[i] = ptr[i - 1] + ptr[i - 2]; v3 = ptr[i]; } free(ptr); return v3; }
我们可以看到 a1 参数是项数,i 从第二项开始,ptr 数组等于前两项之和,我们立刻可以想到是斐波那契数列,因此我们推断这题与斐波那契数列有关系
(6)我们回到主函数中,可以从剩下的多项式判断中可以获得一些方程式
v9 - v10 = 0x233F0E151C v9 - v11 = 0x1B45F81A32 v9 - v12 = 0x244C071725 v4 + v12 + v11 + v10 = 0x13A31412F8C # 我们将这些多项式合并转化可以得到以下内容 3*v9 - (v12 + v11 + v10) = 0x233F0E151C + 0x1B45F81A32 + 0x244C071725 = 0x62d10d4673 # 我们将两个多项式相加可以得到 3*v9 + v4 = 0x13A31412F8C + 0x62d10d4673 = 0x19d024e75ff
我们从 f 函数中可以知道,v4就是多项式的项数,因此我们可以将 0x19d024e75ff / 3就可以得到 v9 大致的值为 591286729898 ,我们则可以通过网上找一个在线的斐波那契生成器
我们可以看到一个与我们算出来的结果非常相近的数值,于是v9=591286729879,v4=0x19d024e75ff-3*v9=58
因此我们的v4、v10、v11、v12都可以计算出来
v9=591286729879 v4=58 print(hex(v9-0x233F0E151C)) #v10=0x666c61677b print(hex(v9-0x1B45F81A32)) #v11=0x6e65776265 print(hex(v9-0x244C071725)) #v12=0x655f686572 print(hex(v4+0x6543)) #v4=0x657d
我们将这些数字输入程序查看是否正确
发现成功!我们将这些16进制按照提示转换成字符串
from Crypto.Util.number import long_to_bytes print(long_to_bytes(0x666c61677b6e65776265655f686572657d))
得到 flag{newbee_here}
例题:CTFSHOW——re2
(1)下载题目中的附件后发现有两个文件,分别是 enflag.txt 和一个名为勒索病毒.exe 的可执行程序
(2)打开 txt 文件查看文件内容有些什么
发现是一对乱码,不知道是什么情况,先不管了,开始分析 exe 可执行程序
(3)由于我们在 VM 中逆向这个软件,因此我们可以直接双击执行可执行文件看看会发生什么
发现是一个可以输入的交互程序,我们输入1试试看,发现输入 1 后程序直接退出了,并且输入其他不规范字符会提示输入不规范,因此我们可以大致了解到这程序的基本运作原理
(4)将程序放入PEiD中发现程序并无加壳,而且程序是一个 32 位的可执行程序,因此我们把程序放入 IDA 中进行分析
(5)放入 IDA 中我们首先第一步就是直接找它的主函数,但是我们发现并没有主函数的影子,猜测应该是被混淆了,因此我们要换一个方向进行切入。我们可以通过字符串定位找到主函数的执行入口
通过字符串查找我们可以找到刚刚执行程序时所获得的文字,我们可以发现这个程序似乎将某个文件进行了加密,并且有 flag.txt 文件和 enflag.txt 文件,而我们已经获得了 enflag.txt 文件,但其中是一个乱码,我们猜测是不是程序把 enflag.txt 文件进行了加密导致的,并且我们发现在图片字符串的第四行有一个很不一样的字符串,我们对其感兴趣,我们双击进入位置进行查看
(6)我们双击字符串进入函数,发现似乎并没有什么有用的信息,我们试一下将其进行交叉引用,并且跳转到其引用的位置 (crtl + x),并且按 F5 进入查看其伪代码
通过伪代码我们可以看到有几个函数,我们可以点进去看发现里面并没有什么,都是直接返回的函数,并没有发现到什么用于的信息,但是我们可以知道这个函数的操作是打开一个文件然后对其进行加密,具体什么加密我们现在可能不太清楚
(7)接着我们还可以利用一个方法,就是查看函数的长度。我们可以看到有很多函数的长度都是00000005,而这些函数都是没有用的函数,我们可以查看一些长度不一样的函数,看看是否能发现有用的函数
(8)通过查找我们能找到一个这样的函数,并且其函数内容和我们刚刚的字符串一样,我们分析一下这个函数
我们可以看到这个函数将一个字符串进行与 0x1F 进行异或,将异或得到的值与这个字符串进行比较,如果相同则返回充值成功,因此我们可以将这个字符串再与 0x1F 进行异或就可以得到原来的字符串
(9)通过下列这个 python 脚本我们可以得到原来的字符串为 [Warnning]Access_Unauthorized,这个字符串结合我们前面查询字符串看到的 “请输入你的密钥” 进行联系,我们猜测这个就是我们的密钥
str = "DH~mqqvqxB^||zll@Jq~jkwpmvez{" str2 = '' for i in str: str2 += chr(ord(i)^0x1F) print(str2) # [Warnning]Access_Unauthorized
而我们有了密钥又通过前面的函数形式,我们很快的就可以想出前面的函数应该就是 rc4 加密,配合我们之前的密钥我们可以利用在线 rc4 解密对其进行解密,但是再次之前我们要先把这些字符串转换为 16 进制,我们可以将其拖入 010Editor 进行查看
(10)我们获得了 16 进制的编码后将其复制下来后从网上找 rc4 解码代码解密得到 flag{RC4&->ENc0d3F1le}
key3 = [ 0xC3,0x82,0xA3,0x25,0xF6,0x4C,0x36,0x3B,0x59,0xCC,0xC4,0xE9,0xF1,0xB5,0x32,0x18,0xB1,0x96,0xAe,0xBF,0x08,0x35] c = key3 t = [] key = '[Warnning]Access_Unauthorized' ch = '' j = 0 # 初始化 s = list(range(256)) # 创建有序列表 for i in range(256): j = (j + s[i] + ord(key[i % len(key)])) % 256 s[i], s[j] = s[j], s[i] i = 0 # 初始化 j = 0 # 初始化 for r in c: i = (i + 1) % 256 j = (j + s[i]) % 256 s[i], s[j] = s[j], s[i] x = (s[i] + (s[j] % 256)) % 256 ch += chr(r ^ s[x]) print(ch)
第二类:upx脱壳类
这一类是需要进行脱壳后才能进行分析
例题:CTFSHOW——flag白给
(1)首先发现是exe文件,那么我们把这个程序放进 PEID 进行查看是否有壳,通过查看发现该程序存在 UPX 壳
(2)已知改程序存在壳,则我们利用 upx 工具对程序进行脱壳
脱壳后再次放入 PEID 发现脱壳成功
(3)脱壳成功后我们先运行该程序,对程序如何运作进行了解,发现其是一个弹窗,并且可以输入一些值,我们随便输入试试
发现该程序弹出错误窗口,我们判断应该是需要输入正确一个内容才会获得flag或者是一个检查flag是否正确的程序
(4)接着我们放入 IDA 进行分析。首先我们第一步想找该程序的主函数,但是我们并没有找到改程序的主函数,可能进行了混淆使得我们无法找到;接着我们通过字符串查找(shift + F12)对程序的全部字符串查找,查看是否有我们前面弹窗中内容字符串
通过我们查找并没有发现有用的字符串内容,或许可能是被混淆或加密了,我们放在 OD 中动态分析
(5)我们利用 Ollydbg 查找字符串,查找是否含有弹窗中的字符串内容
输入我们弹窗中的字符串内容hack点击查找发现程序中存在该字符串,并且内容中还有 “成功了” 和 “错误” 的字眼,我们怀疑 HackAv 就是我们的flag,我们输入到程序中查看是否正确
发现成功了!
则我们获取 flag{HackAv}
第三类:高级语言打包阻碍逆向
一般有C#,java,py等的反编译,建议去找好工具jd反编译java的class字节码文件,还有dnspy反编译C#,py的反编译我用的在线网站
例题:CTFSHOW——签退
(1)该题下载好后发现文件是一个 pyc 文件,pyc 文件是 py 文件编译后的文件,因此我们若想分析此文件需要将其进行反编译,我们可以找 python 的在线反编译平台进行反编译,以下是反编译后的 python 代码
# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information # Version : Python 2.7 import string c_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '()' flag = 'BozjB3vlZ3ThBn9bZ2jhOH93ZaH9' def encode(origin_bytes): c_bytes = [ '{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes ] resp = '' nums = len(c_bytes) // 3 remain = len(c_bytes) % 3 integral_part = c_bytes[0:3 * nums] for x in [ 0, 6, 12, 18]: tmp_unit = [][int(tmp_unit[x:x + 6], 2)] resp += ''.join([ c_charset[i] for i in tmp_unit ]) integral_part = integral_part[3:] if remain: remain_part = ''.join(c_bytes[3 * nums:]) + (3 - remain) * '0' * 8 tmp_unit = [ int(remain_part[x:x + 6], 2) for x in [ 0, 6, 12, 18] ][:remain + 1] resp += ''.join([ c_charset[i] for i in tmp_unit ]) + (3 - remain) * '.' return rend(resp) def rend(s): def encodeCh(ch): f = lambda x: chr(((ord(ch) - x) + 2) % 26 + x) if ch.islower(): return f(97) if (None,).isupper(): return f(65) return (''.join,)((lambda .0: pass)(s))
(2)我们可以逐步分析代码
def encode(origin_bytes): c_bytes = [ '{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes ] resp = '' nums = len(c_bytes) // 3 remain = len(c_bytes) % 3 integral_part = c_bytes[0:3 * nums] for x in [ 0, 6, 12, 18]: tmp_unit = [][int(tmp_unit[x:x + 6], 2)] resp += ''.join([ c_charset[i] for i in tmp_unit ]) integral_part = integral_part[3:] if remain: remain_part = ''.join(c_bytes[3 * nums:]) + (3 - remain) * '0' * 8 tmp_unit = [ int(remain_part[x:x + 6], 2) for x in [ 0, 6, 12, 18] ][:remain + 1] resp += ''.join([ c_charset[i] for i in tmp_unit ]) + (3 - remain) * '.' return rend(resp)
从这里的代码我们我们可以看到程序将内容每三个分为一组,并且这个函数把字节转为二进制,然后试图从每 6 位二进制片段转换为整数,再从一个可能的字符集(
c_charset
)中获取字符拼接,此加密算法与base64加密非常相似,我们怀疑这个采用的是base64算法
def rend(s): def encodeCh(ch): f = lambda x: chr(((ord(ch) - x) + 2) % 26 + x) if ch.islower(): return f(97) if (None,).isupper(): return f(65) return (''.join,)((lambda .0: pass)(s))
这块函数我们可以发现似乎是对输入的字符串
s
进行某种字符编码转换操作,通过仔细观察我们可以发现是对其进行凯撒密码的加密
(3)因此我们猜测程序是把 flag 先进行了base64编码后在通过凯撒密码进行编码,因此我们要倒推回去先用凯撒密码解码再用base64解码,并且我们从代码中可以看到偏移量为 2
得到解密后的结果后对其进行base64解密,获得 flag{c_t_f_s_h_0_w_!}
第四类:迷宫类
解题思路: 1、通过分析函数,找到迷宫地图(可能直接给出,也可能需要自行建立) 2、根据函数找到走出迷宫的方法 3、直接手动走迷宫或编写脚本解密得到最终flag 【BFS——广度优先算法】
例题:攻防世界——reverse_re3
前面检查文件步骤省略,直接进入主体分析
(1)将程序放入 IDA 进行分析,首先就是找到 main 函数进行分析,该 main 函数较为简单,应该就是包含主函数全部内容,我们可以发现了两个函数,分别是 sub_11B4 和 sub_940,我们对其逐一分析
(2)进入 sub_11B4 函数后,我们并没有发现什么有用的信息,我们先放着若后面有需要再进行分析
(3)接着看 sub_940 函数,我们进入 sub_940 函数后,发现其非常多内容,应该主程序的大部分内容都在这里面
signed __int64 sub_940() { signed int v0; // eax int v2; // [rsp+8h] [rbp-218h] int v3; // [rsp+Ch] [rbp-214h] char v4[520]; // [rsp+10h] [rbp-210h] unsigned __int64 v5; // [rsp+218h] [rbp-8h] v5 = __readfsqword(0x28u); v3 = 0; memset(v4, 0, 0x200uLL); _isoc99_scanf(&unk_1278, v4, v4); while ( 1 ) { do { v2 = 0; sub_86C(); v0 = v4[v3]; if ( v0 == 'd' ) // 这里的值原本是ASCII,后来被我转化为字符串 { v2 = sub_E23(); } else if ( v0 > 'd' ) { if ( v0 == 's' ) { v2 = sub_C5A(); } else if ( v0 == 'w' ) { v2 = sub_A92(&unk_1278, v4); } } else { if ( v0 == '\x1B' ) return 0xFFFFFFFFLL; if ( v0 == 'a' ) v2 = sub_FEC(); } ++v3; } while ( v2 != 1 ); if ( dword_202AB0 == 2 ) break; ++dword_202AB0; } puts("success! the flag is flag{md5(your input)}"); return 1LL; }
从这里可以看到,我们这个函数里面主要有四个参数,分别是 v0、v2、v3 和 v4;其中我们可以发现 v0 主要的就是对获取输入的 v4 数组的字符串,然后与 w、a、s、d 和 \x1B 进行判断,并且进入每个独立的函数,从这里我们大致可以知道这个题目是一个 迷宫题 。v2 它的作用是在各个分支里面接收来自4个函数(见下图)的返回值,最终来控制do...while循环是否停止。我们还可以看到有一个参数为 dword_202AB0 ,其参数取值似乎只能 0,1,2,当其循环到 2 结束后就会运行接下来的语句,我们从这里注意到我们的 flag 值是我们输入的值的 md5 编码。
(4)这里有非常多的函数,我们对其进行逐一的分析
sub_86C()
unsigned __int64 sub_86C() { signed int i; // [rsp+0h] [rbp-10h] signed int j; // [rsp+4h] [rbp-Ch] unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); for ( i = 0; i <= 14; ++i ) { for ( j = 0; j <= 14; ++j ) { if ( dword_202020[j + 15LL * i + 225LL * dword_202AB0] == 3 ) { dword_202AB4 = i; dword_202AB8 = j; break; } } } return __readfsqword(0x28u) ^ v3; }
我们可以从函数中看到,这个函数主要是进行一个 for 循环,其循环总加起来应该是 15 * 15 = 225 次,并且我们看到了几个新的参数 dword_202020、dword_202AB4 和 dword_202AB4。
除此之外,我们还看到了之前发现的参数dword_202AB0,我们从前面可知其只能取3个值 0、1、2,因此我们大致可以知道数组 dword_202020 被 dword_202020 大致以 225 为边界划分为 3 个部分,推测应该是有 3 个迷宫
而在数组中的 dword_202020[j + 15LL * i + 225LL * dword_202AB0] 中的 index 值我们可以发现,这里除了以 225 划分数组外,还有 j + 15LL * i 这部分的内容,我们推测应该是对行和列进行一个定位,15 * i 也就是我们的行,表示一行有 15 个数,j 就是在当前行的位置
我们对 dword_202020 数组格外的好奇,我们点进去查看 dword_202020 数组大概是什么内容
dword_202020 数组
我们可以从这边更加确定这题就是一个迷宫题,而里面记录了迷宫的内容,我们从上面的注解可以看到迷宫这个数组有 675 个值
sub_E23()
signed __int64 sub_E23() { if ( dword_202AB8 != 14 ) { if ( dword_202020[dword_202AB8 + 1 + 15LL * dword_202AB4 + 225LL * dword_202AB0] == 1 ) { dword_202020[dword_202AB8 + 1 + 15LL * dword_202AB4 + 225LL * dword_202AB0] = 3; dword_202020[dword_202AB8 + 15LL * dword_202AB4 + 225LL * dword_202AB0] = 1; } else if ( dword_202020[dword_202AB8 + 1 + 15LL * dword_202AB4 + 225LL * dword_202AB0] == 4 ) { return 1LL; } } return 0LL; }
我们可以看到这个函数主要是进行一个判断,第一层判断是判断 dword_202AB8 参数是否为 14 ,若为 14 则退出判断,我们推测应该是迷宫一共有 15 列,按数组下标从 0 开始 14 就是边界了,若 dword_202AB8 参数到达边界则无法移动,根据我们之前的推测进行判断一行15个并且有15列,外加 3 个迷宫,应该为 15 * 15 * 3 = 675 正好符合这个一维数组 675 个值,证明我们猜想正确
接着我们分析这些判断有何作用,我们可以通过这个发现当 dword_202020 位置为 1时,就将当前位置变为 3 并且前一个位置变为 1,如果当 dword_202020 为 4 时则直接退出判断,我们推测 1 就是走过的位置,3 就是当前的位置,而 4 也就是结束的位置。通过简单推测后,接下来的步骤就是画出迷宫图
(5)我们从刚刚的 dword_202020 数组中的内容进行提取(shift + e),按照 C变量初始化 的格式进行提取,提取出来后我们按照一共迷宫有 15 行,1 行有 15 个进行画出迷宫
(6)利用脚本画出的迷宫图如下
1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 3 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 1 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0 4 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 3 1 1 1 1 1 0 0 0 0 0 0 1 1 0 1 1 0 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 1 1 0 0 0 1 1 1 1 1 0 0 1 1 0 1 1 0 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 0 0 0 0 1 1 1 1 0 1 1 0 1 1 0 0 0 0 0 1 0 0 1 0 1 1 0 1 1 0 0 0 0 0 1 0 0 0 0 1 1 0 1 1 1 1 1 1 0 1 0 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 4 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0
而我们通过迷宫图就可以画出我们的路线,获得路线后转化为移动的字符串,获得全部字符串汇总后将其 md5 加密
ddsssddddsssdssdddddsssddddsssaassssdddsddssddwddssssssdddssssdddss
加密后:
aeea66fcac7fa80ed8f79f38ad5bb953
因此获得我们的 flag{aeea66fcac7fa80ed8f79f38ad5bb953}
第五类:tea类
TEA(Tiny Encryption Algorithm)即微型加密算法。它是一种简单快速的分组加密算法。
TEA 类题目的特点在于其加密方式,TEA 是分组加密算法,它以 64 - bit 的数据块为单位进行加密,并且TEA 算法使用 128 - bit 的密钥、采用 32 轮的 Feistel 网络结构进行加密。
其解题思路就是按照 TEA 加密方式写出其解密方式即可
例题:
以下是利用 tea 类型的加密算法
void tea_enc(uint32_t* v, uint32_t* k) { uint32_t v0 = v[0], v1 = v[1]; uint32_t sum = 0; uint32_t delta = 0xd33b470; for (int i = 0; i < 32; i++) { sum += delta; v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]); v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]); } v[0] = v0; v[1] = v1; }
tea 类的特点是含有 sum 和 delta 两个参数,其中 *sum用作加密过程中的一个累加变量,而 delta 的值一般为0x9E3779B9(-0x61C88647),但题目中往往会改变它的值,并不影响算法的逆向
我们可以按照其加密算法逆序写出其解密算法,我们甚至可以不用知道其代码是如何实现的
void tea_dec(uint32_t* v, uint32_t* k) { uint32_t v0 = v[0], v1 = v[1]; uint32_t delta = 0xd33b470; uint32_t sum = 32 * delta; for (int i = 0; i < 32; i++) { v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]); v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]); sum -= delta; } v[0] = v0; v[1] = v1; }
根据加密时的顺序颠倒函数的加密顺序,将加法改为减法(异或部分都是整体,不用管),就是逆向解密过程,通过这个过程就可以实现 tea 类的解密
例题:[MoeCTF 2022]ezTea
打开程序,我们可以找到其主函数发现是 TEA 类的程序,打开的内容虽然是乱码,但是并不影响,我们只关心程序的算法
#include <stdio.h> #include <stdint.h> void encrypt (uint32_t* v, uint32_t* k) { // 涓昏鍔犲瘑鍑芥暟锛岃瘯鐫€鎼炲畾瀹? uint32_t v0 = v[0], v1 = v[1], sum = 0; uint32_t delta = 0xd33b470; for (int i = 0; i < 32; i++) { sum += delta; v0 += ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>5) + k[1]); v1 += ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>5) + k[3]); } v[0] = v0; v[1] = v1; } int main() { uint32_t k[4] = {1, 2, 3, 4}; int8_t input[33] = {0}; scanf("%32s", input); for (int i = 0; i < 32; i+=8) { uint32_t v[2] = {*(uint32_t *)&input[i], *(uint32_t *)&input[i+4]}; encrypt(v, k); for (int j = 0; j < 2; j++) { // 杩欎竴娈典富瑕佹槸鎶?v 鎸夊崟瀛楄妭杈撳嚭锛屽彟澶栧彲浠ヤ簡瑙d竴涓?鈥滃ぇ灏忕搴忊€?鍦ㄨ繖棰樻槸濡備綍浣撶幇鐨? for (int k = 0; k < 4; k++) { printf("%#x, ", v[j] & 0xff); v[j] >>= 8; } } } return 0; }
接着我们可以按照函数的算法逆着写出exp即可
exp:
#include <stdio.h> #include <stdint.h> void decrypt(uint32_t* v, uint32_t* k) { uint32_t v0 = v[0], v1 = v[1], sum = 0xd33b470*32; uint32_t delta = 0xd33b470; for (int i = 0; i < 32; i++) { v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]); v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]); sum -= delta; } v[0] = v0; v[1] = v1; } int main() { uint32_t k[4] = { 1, 2, 3, 4 };//uint32_t——4字节,uint8_t——1字节 int8_t input[] = { 0x17, 0x65, 0x54, 0x89, 0xed, 0x65, 0x46, 0x32, 0x3d, 0x58, 0xa9, 0xfd, 0xe2, 0x5e,0x61, 0x97, 0xe4, 0x60, 0xf1, 0x91, 0x73, 0xe9, 0xe9, 0xa2, 0x59, 0xcb, 0x9a, 0x99,0xec, 0xb1, 0xe1, 0x7d }; for (int i = 0; i < 32; i += 8) { uint32_t v[2] = { *(uint32_t*)&input[i], *(uint32_t*)&input[i + 4] };//1字节强制转换为4字节 decrypt(v, k);//加密 for (int j = 0; j < 2; j++)//一字节一字节打印出来 { for (int k = 0; k < 4; k++) { printf("%c", v[j] & 0xff); v[j] >>= 8; } } } return 0; }
我们就可以获得 moectf{Th3_TEA_!S_s0_t4s7y~~!!!}
由于此内容为课设作业,思路较为潦草,若有错误可以私信我改正!感谢大家观看,评论区咱们可以多多交流,我们下次再见