附件
deserve_62ae0401215f98652482b329625d0b88c587e7ca.txz
解题过程
![](https://i-blog.csdnimg.cn/blog_migrate/2b46a8cec821534b70c2988ace1e0ca5.png)
调试环境搭建
这是一个aarch64(arm64)架构的程序,可以在QEMU和Bochs等模拟器中运行这个程序,以QEMU为例,可以使用qemu-aarch64 -g 1234 /path/to/your/program 监听端口,用GDB连接端口即可动态调试,也可以使用IDA PRO的“Remot GDB Debugger选项进行调试”
启动程序需要先安装qemu,并且程序是动态链接的(如上图),因此需要安装相应版本的libc库以确保程序能够正常运行。
//安装lib库
sudo apt-get install gcc-aarch64-linux-gnu
//-L 指定动态链接库的地址,默认./lib/ld-xxx.so
//-g 开启gdb连接协议
qemu-aarch64 -L /usr/aarch64-linux-gnu/ -g 1234 ./deserve
如果要调试非本机架构的程序,需要使用gdb-multiarch。在ARM64指令集下调试的方式可能与其他指令集不同,例如NI(Non-Instruction)步过可能会进入函数,因此直接使用断点比较好。
gdb-multiarch
> set architecture aarch64
> target remote localhost 1234
//如果用gef,需要用gef-remote --qemu-user --qemu-binary ./deserve localhost 1234
输入
2. 运行程序时没有回显,并且在加上参数后会出现"usage: ./deserve"错误提示。通过IDA分析可知,程序的主函数在__libc_start_main中的sub_A80函数中,进一步分析发现在sub_FA0函数中存在feof(stdin)判断,需要从终端输入数据才能继续执行程序。通过fread(&v5[v3], 1uLL, 0x400uLL, stdin)可以一次性输入1024字节的数值保存。需要注意的是,在C语言中,要检测feof(stdin)最后需要使用非空格字符作为终端,因此可以使用Ctrl-D(DOS/Windows中的Ctrl-Z)作为结束标志。
![](https://i-blog.csdnimg.cn/blog_migrate/132fbffb91f6564e340447ab2175aee0.png)
3. 想用IDA调试,但是断点有点问题,还是改用gdb进行调试,先在加密函数入口处下断点0x4000000be0(linux有基地址0x4000000000)
![](https://i-blog.csdnimg.cn/blog_migrate/27227e828f41a5a0f1862eb0e48c5aed.png)
第一轮变换
4. 第一处,判断输入数据是否为字符或数字。为此,程序使用了__ctype_b_loc()函数来存储对应字符的类型信息。例如,对于字母'a',由于满足isalnum,对应flag的二进制标志位为1,即0x0008。在判断时,程序会进行 & 操作,如果结果大于0,则说明输入数据为字符或数字,直接将其放在加密数据的后面。
关键代码:
v8 = __ctype_b_loc(); // 获取字符类型数组的指针
if ( ((*v8)[(unsigned __int8)input[i]] & 8) != 0 )
LDRB W24, [X27,X20] ; Load from Memory
MOV X28, X21 ; Rd = Op2
LDR X1, [X26] ; Load from Memory
UBFIZ X3, X24, #1, #8 ; Signed Bitfield Insert in Zero
LDRH W3, [X1,X3] ; Load from Memory
TBNZ W3, #3, loc_C48 ; Test and Branch Non-Zero
![](https://i-blog.csdnimg.cn/blog_migrate/b77cc956ec882375817fcde65644abb6.png)
a. 在程序中,需要判断输入数据是否为空格。如果输入数据为空格,程序会根据之前的加密数据进行进一步判断。如果上一个加密数据为小写字母,上上一个加密数据为"+",则将上一个加密数据变为大写字母。如果不满足这个条件,程序会将'/'放入加密数据中。
b. 除此之外的特殊字符
如果是"@$_!\"#%&'()*+,-./:;<=>?\n",v33存放的是指向iFlag的指针,下面(*BYTE)v33 - (unsigned __int8)"@$*!"#%&'()*+,-./:;<=>?\n"即偏移量,将偏移量+97存入加密数据,同时将+存入数据
v33 = strchr("@$_!\"#%&'()*+,-./:;<=>?\n", iFlag);/
firstEnc[v34] = (*BYTE)v33 - (unsigned __int8)"@$*!"#%&'()*+,-./:;<=>?\n" + 97;
如果是"[\\]^{|}~`\t",存入两个+和偏移量+97
综上,实际上是一个字符映射的过程,将特殊符号映射成了'+'和对应的字母。对应解密过程如下:
def convert_special_char(s):
result = ""
i = 0
while i < len(s):
if s[i:i+2] == "++":
char = chr(ord(s[i+2]) - 97)
result += "[\\]^{|}~`\t"[ord(char)]
i += 3
elif s[i] == "+" and s[i+1].islower() :
char = chr(ord(s[i+1]) - 97)
result += "@$_!\"#%&'()*+,-./:;<=>?\n"[ord(char)]
i += 2
elif s[i] == "+" and s[i].isupper():
result += "{} ".format(s[i].lower())
i += 1
elif s[i] == "/":
result += " "
i += 1
else:
result += s[i]
i += 1
return result
5. 补长度,如果第一轮加密的数据长度不能被3整除,则继续填充'/',直到长度能够被3整除,保证编码后的数据长度为4的倍数。
if ( (v9 & 3) != 0 )
{
v11 = v9 + 1;
do
{
v5[v11 - 1] = '/';
v12 = (int)v11;
}
while ( v11++ & 3 );
*a2 = v12;
}
第二轮变换
6. 以上描述中的“加密数据”其实并没有被打印出来,也没有被保存。
在程序的后续部分,实际通过对base64编码进行解码,得到flag.enc。
简单分析如下:
将input的数据每次提取4个字符,转换为对应的base64编码,重新生成加密字符串。其中,a,b,c,d是输入的字符在"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"对应的偏移。
已知a,b,c的值都小于64,即每个数最高使用6个bit
(4 *(a & 0x3f )) | (b >> 4) = X 取出a的值放在X的高6位,将b的高2位存放在X的低2位
(16 * (b & 0xf)) | (c4 >> 2)= Y 将b的低4位放在Y的高4位,将c的高4位存放在Y的低4位
((c2 & 3) << 6) | d = Z 将C的低2位存放在Z的高2位,将d的值存放在Z的低6位
![](https://i-blog.csdnimg.cn/blog_migrate/74fb6ad8434e53061f0cb5b97ccd9985.png)
7. 得到的数据有很多的+号,需要再通过第一次的加密进行逆向得到flag
convert_special_char("+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+m+xCompressing/short/messages/is/essential+Nso/ASIS/has/developed/new/one+xThe/flag/for/this/task/is+RASIS++ec0mprEs51nG+csHOr7+ct3xT+cmE5s49es+cASIS+d++g///")
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\nCompressing short messages is essential+Nso ASIS has developed new one\nThe flag for this task is+RASIS{c0mprEs51nG_sHOr7_t3xT_mE5s49es_ASIS!} '
反思
这道题目有趣的地方在于它要求我们进行编码,而平常我们更多地是使用base64编码去解码。同时,它也需要我们对base64的编码原理有一定的了解,以便进行正确的编码过程。