很久没有写题解博客了,最近忙着学习深度学习= =,湖湘杯这道题还是不错的,可以锻炼一下自己动态调试的能力,所以刚好在练习的过程中记录一下,方便以后回顾:
首先拿到文件,查壳发现无壳,运行一下大概就是先判断长度,再判断是否是正确flag:
长度错误就输出wrong length并退出。
拿到ida里看一下,发现没有很明显的入口函数,查找字符串找到input your flag:,找到主函数位置
f5看一下,发现没办法反编译,所以我们就直接上动态调试吧:
在0x40DA40下断点(f2),f8单步调试,发现到0x40DA72停住,进行输入:
先随便输入点东西,如’111‘,然后继续调试,到下图这一步,发现将eax的值和0x18进行比对,可以推断出0x40D920函数的作用就是获取输入的flag的长度,然后和24进行比较:
发现下面是je跳转,为了能够实现无条件跳转,我们对这条汇编进行一些修改
方法有两个:一、修改寄存器中的标志寄存器值,由于是je汇编指令,判断z标志位,双击就可以修改;二、修改汇编代码,将je汇编代码改为jmp实现无条件跳转。
这里我使用的是第二种方法,直接修改汇编指令:
继续调试发现函数返回到0x4048DA处,
这里就是整个程序的主要加密处:
(图中地址404919处的判断就是对于循环轮数的判断,如果循环了24次了就进行跳转出循环。)
最后一位数据操作如图:
将加密写成代码的话其实就是这样:
tmp = a[0] &0xe0
for i in range(len(a)-1):
a[i] = ((a[i]<<3)|(a[i+1]>>5))&0xff
a[i] = a[i] ^i
a[23] = (a[23]<<3)|(tmp>>5)
执行24次后继续调试,程序来到了这一代码段,因为上面已经对数据进行了一些操作,所以初步猜测这里很有可能是对比flag阶段:
可以看到是将操作后的数据与0x411000处的字符进行比较,那我们就去把0x411000处的字符dump出来:
基本的逻辑清楚之后,就可以进行暴力破解了:
(由于每两个字符之间都有联系,暴力破解的时候会比较麻烦,看了其他师傅博客,使用的是z3进行解决,指路)
exp
from z3 import *
s = Solver()
flag = [BitVec('x%d'%i,8) for i in range(0x18)]
b = [0x2B, 0x08, 0xA9, 0xC8, 0x97, 0x2F, 0xFF, 0x8C, 0x92, 0xF0,
0xA3, 0x89, 0xF7, 0x26, 0x07, 0xA4, 0xDA, 0xEA, 0xB3, 0x91,
0xEF, 0xDC, 0x95, 0xAB]
for i in range(23):
s.add(((flag[i]<<3|flag[i+1]>>5)&0xff) ^i == b[i])
s.add((flag[23]<<3|flag[0]>>5)&0xff == b[23])
if s.check() == sat:
m = s.model()
Str = [chr(m[flag[i]].as_long().real) for i in range(24)]
print("".join(Str))
再将结果md5一下就是flag了:
看了wp之后感觉这题其实逻辑挺清晰的(但是我当时并没做出来= =,tcl)可以作为学习动态调试的例题。