前言:很有意思一道Reverse题,在位移加密有很多巧妙方法,这里记录下做题的过程
这道题学到了很多东西呜呜呜,本篇记录下调试当中的一些技巧方法和参照3cly师傅的博客一些加密的骚操作
此题是个rust 逆向题,是个socket类型的交互题,我们使用字符串定位的方法定位到关键部分,这里我定位到了如下代码位置(前部分找入口找到对应字符串直接交叉引用即可):
1 | _rust_dealloc(); |
seccomp_shell::shell::verify::h898bf5fa26dafbab 它的传参就是我们要输出的字符串(flag) 这个函数就是我们要逆的主要函数
我们进到里面发现代码很多,这里我列出关键部分代码
1 | memcpy(dest, &unk_62920, 0x200uLL); |
我截取了最后的换位加密的代码,前面还有好几个这样的换位加密,但它换位加密的表在整个while循环一次下来都是固定的,在最外层的while循环里是256次,如下代码:
1 | while ( v52 ); |
在经历完换位加密后又进行了一次除取余的操作64次,这里猜测一次只能处理64个字节(其实就是64字节)通过这段加密将整个输入的字符串进行了一个除+取余等操作
经过逆向分析以上进行换位位移加密都是用的固定的索引,在以上换位加密代码和第二次加密的行为与用户输入值没有任何关系,用 00-ff 的映射(引用师傅的一句话逆向加密算法比较复杂,就找出 00-ff 的映射就行了 因为是按位加密的伐然后前面的几次打乱顺序由于是固定的表所以可以看成一次打乱顺序 找出索引交换的顺序即可)
生成 0~ff 的 hex 序列:
1 | for i in range(0, 256, 64): |
这里先放这,我们测试的数据是flag 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{} 先把加密前的数据提取出来,我把断点下在了这个位置
提取flag变量里的数据
数据是HfVl{qPcCYNMoRi6D7Jr}espOL3FhwdWAtTGZba4Ugjvnx1QkKE2IS9yuz5BX08m
这是打乱后的字符串,也就是在经历下面加密前的字符串
下面我们用上面hex序列获取加密后的数据,为什么要生成256,因为整个wile循环是256所以我们要生成256个字字节分别进行64个字节的处理,总共调4次即可
我把断点下在了如下的位置:
我下了如图两个断点,一个是加密前(打乱后),另一个是整个执行完一次后的加密
我们动调的时候用idapython脚本去把flag变量值给改下,脚本如下:
1 | import idaapi |
f9运行到下面哪个断点提取的加密数据是
[0xFB, 0x7B, 0x4E, 0xBB, 0x51, 0x15, 0x8D, 0xDB, 0xB0, 0xAC, 0xA5, 0x8E, 0xAA, 0xB2, 0x60, 0xEB, 0x63, 0x5C, 0xDE, 0x42, 0x2B, 0xC6, 0xA6, 0x35, 0x30, 0x43, 0xD6, 0x5F, 0xBD, 0x24, 0xB1, 0xE3, 0x8C, 0xA7, 0xD5, 0x2A, 0x7C, 0x6D, 0x8B, 0x17, 0x9D, 0x83, 0xFE, 0x69, 0x10, 0x59, 0xA9, 0x9E, 0x0F, 0x1C, 0x66, 0x97, 0x5B, 0x61, 0xED, 0xAD, 0xE0, 0xDA, 0x27, 0x06, 0x25, 0xDC, 0x5E, 0xE7, 0x40, 0x41]
我们发现第一次提取到0x40前,因为一次加密只能处理64个字节,所以我们这里还要再进行3次处理剩余的序列,我们再在动调的时候把flag这个变量值改为0x40开始
第二次数据是4132D2D98FEEAF03933A00A2E1B3EC819FCA58B779FD3BA0020CCBA880C0164D2F75710A0439FFC19CABEFA4D8E214C26C641E6B7E992E090B86746AC42D4FF980
剩下再从80开始动调,直到00-255个数据加密完后就是我们动调后的加密密文
找到后,先还原下加密后的数据,这里参考了3cly师傅的脚本
1 | def decrypt(data: list, table: dict): |
通过以上脚本我们发现可以还原原来的明文
tmp是我们0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}整个流程加密后的数据动调就可以得到,这里是占时的值,因为后面还有加密的操作
然后利用字母对应表还原,(这个操作算是学到了呜呜呜tql3cly师傅)
下一步我们接着往下调
1 | while ( v8 != 256 ); |
sub_5625F9C2BB2B 这个里面一开始是opcode,我们复制出来看下它的汇编
这里用了python的一个脚本,将指令还原汇编指令:
1 | import capstone |
生成了如下汇编
1 | tconexp/hb.py |
目前发现没什么用先放这,继续往下动调,
1 | v81 = v65; |
发现dest赋了新值,我们在这个代码执行完后面 的位置用ida patch到上面哪个shellcode位置
,发现这个位置改变了
1 | 0x10cb: mov eax, 0xxxxx 这个变成了我们输入的数据前4个byte |
嗯,分析这块关键代码我们发现这里进行了xor ror not xor操作,其中r12 r13 r14是未知的,所以我们要想办法获取这3个寄存器的值,我一开始想patch源程序到这个位置,发现到了这个位置就无法执行 了,所以使用另一种方法,就汇编写出asm文件,进行nasm编译,然后再动调把断点下在0x10cb就可以查看r12 13 14的值了
编译具体方法:
n.asm:
1 | section .text |
1 | nasm -f elf64 -o vuln.o n.asm |
将上面的汇编进行编译生成elf文件,然后再使用ida动调得到如下
r12 = 0x0000000464C457F r13 = 0x0000000746F6F72 r14 = 0x000000031F3831F
分析完逻辑我们就可以写解密脚本了,参考3cly师傅的脚本
1 | import numpy as np |
cmp_data直接提取dest数据就可以了,因为比较数据是固定的
总结:这道题学到了不少,学到了flag换位加密用固定表紧跟着第二次加密(64),在第二次加密的时候输入的字符和代码行为没有任何关系,所以采用了映射字母表查询(第一次用到这个方法)