攻防世界-polyre
Writeup
IDA打开查看,可以看到使用了OLLVM中的一种代码保护方式——控制流平坦化。在IDA反汇编上显示出来就是使用while+switch的形式,经过平坦化之后的代码块大部分整齐地堆积在程序流图的下方,而这部分代码就是真正有意义的代码块,称之为相关块。
具体脚本编写思路可参考:https://security.tencent.com/index.php/blog/msg/112
或者直接网上找脚本(需要安装angr):https://github.com/cq674350529/deflat
脚本解完后,main函数里已经能看出大概逻辑了,不过仍有大量的while ture和不透明谓词,需要再写脚本处理这些虚假控制流,
具体符号执行脚本编写思路可参考:https://mp.weixin.qq.com/s/d8xoR3VdMf6lMfnETaQHZw
我这里就针对题目简单写个IDA脚本,由调试这些可以知道,红框部分的判断恒为false,所以有大量代码块永远不会执行到,把这些不会执行到的代码块nop掉,条件判断的jnz改jmp。
我这里IDA是7.5版本,和之前版本有些API函数变化,可参考官网:https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
几个注意点:
这里JNZ操作码是0F 85,修改成JMP的E9后,需要再修改偏移量,JNZ这条指令长度为6,JMP长度为5,需要把addr+5处nop掉。
JMP偏移量=目标地址-(当前地址+当前指令长度)
只是要IDA能F5反编译结果的话,修改好JNZ成JMP就好了,那些不可达代码块不nop掉无所谓
IDA脚本如下
def NextAddr(addr): return addr+get_item_size(addr) st=0x400620 ed=0x4020c7 addr=st while addr<ed: if "ds:dword_603054" in generate_disasm_line(addr,0): while True: addr=NextAddr(addr) if "jnz" in generate_disasm_line(addr,0): dest=get_operand_value(addr,0) offset=dest-(addr+5) patch_byte(addr,0xe9) patch_byte(addr+5,0x90) patch_dword(addr+1,offset) break if addr>=ed: break addr=NextAddr(addr)
再F5就能看到较清晰的代码逻辑了,对输入的字符串,分成6组,每组8个字符,
解密脚本如下:
s=[0xBC8FF26D43536296, 0x520100780530EE16, 0x4DC0B5EA935F08EC,0x342B90AFD853F450, 0x8B250EBCAA2C3681, 0x55759F81A2C68AE4] res="" key=0xB0004B7679FA26B3 for item in s: for j in range(64): f=item&1 if f==1: item=(item^key)//2 item = item | 0x8000000000000000 else: item=item//2 n=hex(item)[2:] print(hex(item)) for j in range(len(n)-1,-1,-2): res += chr(int("0x" + n[j-1:j + 1], 16)) print(res)
在原来的数左移一位之后,结果最后一位必然是0,如果原来的数大于0,那一次处理的结果最后一位必然是0。如果原来的数小于0,则在进行一步和0xB0004B7679FA26B3异或,异或后最后一位必然是1。所以可以根据结果的最后一位是1还是0,判断原来数是否小于0。如果小于0,负数右移一位会溢出成正数,在加上0x8000000000000000才能得到原来的数。
得到
flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}