CTF逆向-[安洵杯 2019]game-使用deflat对主要混淆脱混淆后常规逻辑判断
来源:https://buuoj.cn/
内容:无
附件: https://pan.baidu.com/s/1qq_64SNIRnnTTCNqNIKOiw?pwd=1iz9 提取码:1iz9
答案:KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J
总体思路
判断和输入相关的函数,然后对这些函数使用deflat.py对程序扁平化处理。
反向编写脚本得到答案
详细步骤
- 检查文件信息
- 打开
general_inspection
以及trace
等自定义方法的,程序发现控制流特别大,可能是进行了混淆
使用deflat.py(见附件),在ida中查看main方法的地址为0x4006F0
,分别deflat掉对输入有操作的函数,check1
、check3
、check2
-
check3
函数中套了一个check2
,该函数中是判断用户的输入值是否与全局变量相同,双击点开sodoku
和D0g3
变量,shift+e按如图设置得到地图。其中D0g3
变量会接收变更后的input值,即使得用户输入与其sodoku
变量相同即可 -
其中
check1
函数的实现是转换v_input:- v_input的
0
-len/2-1
与len/2
-len
互换 - v_input[2n]和v_input[2n+1]互换
- v_input[k] = (v_input[k] & 0xF3 | ~v_input[k] & 0xC) - 20
- v_input的
-
通过使用z3创建一个
0-9
的映射,即将每个数字都得到原来的输入-
from typing import List import z3 import struct def convert(raw: str) -> List[int]: raw = bytearray.fromhex(raw) raw = [raw[x*4:(x+1)*4] for x in range(int(len(raw)/4))] raw = [struct.unpack('<I', x)[0] for x in raw] return raw v_sudo = '010000000400000005000000030000000200000007000000060000000900000008000000080000000300000009000000060000000500000004000000010000000200000007000000060000000700000002000000080000000100000009000000050000000400000003000000040000000900000006000000010000000800000005000000030000000700000002000000020000000100000008000000040000000700000003000000090000000500000006000000070000000500000003000000020000000900000006000000040000000800000001000000030000000600000007000000050000000400000002000000080000000100000009000000090000000800000004000000070000000600000001000000020000000300000005000000050000000200000001000000090000000300000008000000070000000600000004000000' v_d0g3 = '010000000000000005000000030000000200000007000000000000000000000008000000080000000000000009000000000000000500000000000000000000000200000000000000000000000700000000000000000000000100000000000000050000000000000003000000040000000900000000000000010000000000000000000000030000000000000000000000000000000100000000000000000000000700000000000000090000000000000006000000070000000000000003000000020000000900000000000000040000000800000000000000000000000600000000000000050000000400000000000000080000000000000009000000000000000000000004000000000000000000000001000000000000000300000000000000000000000200000001000000000000000300000000000000070000000000000004000000' v_sudo = convert(v_sudo) v_d0g3 = convert(v_d0g3) print(f'v_sudo:{v_sudo}') print(f'v_d0g3:{v_d0g3}') s = z3.Solver() v_result = [index + 48 for index, x in enumerate([0] * 10)] # 初始化数字的ascii v_input = [0] * len(v_result) v_input = [z3.BitVec(str(index), 16) for index, x in enumerate(v_input)] def exp(index: int): x = v_input[index] r = v_result[index] return (x & 0xf3 | ~x & 0xc) - 20 == r def get_result(index: int) -> int: r = None s.add(exp(index)) result = s.check() if result == z3.sat: rs = s.model() r = rs[0] # 获取最后结果key r = rs[r].as_long() # 转换为value else: print(f'fail on {index}') s.reset() return r key_mapper = [get_result(index) for index, x in enumerate(v_result)] print(f'key_mapper:{key_mapper}')
-
-
得到
key_mapper:[72, 73, 74, 75, 68, 69, 70, 71, 64, 65]
-
发现在最后的input判断中是只判断当前数组中为0的项的,即输入的值是v_sudo和v_d0g3不一致的值
-
-
# 只填入d0g3中为0的位置,使得v_sudo == v_d0g3 is_invalid = -1 v_encoded = [v_sudo[index] if x == 0 else is_invalid for index, x in enumerate(v_d0g3)] # 转换为输入 v_encoded = list(filter(lambda x: x != is_invalid, v_encoded)) v_encoded = [key_mapper[x] for x in v_encoded] v_encoded = [chr(x) for x in v_encoded] print(f'v_encoded:{v_encoded}')
-
得到转换后的v_input
v_encoded:['D', 'F', 'A', 'K', 'F', 'D', 'I', 'G', 'F', 'J', '@', 'A', 'D', 'F', '@', 'E', 'G', 'J', 'J', '@', 'D', 'K', 'E', 'E', 'F', 'I', 'K', 'G', 'J', 'I', 'A', '@', 'G', 'F', 'J', 'E', 'E', 'A', '@', 'F']
-
-
最后将其通过key_mapper后,按:
- v_input的
0
-len/2-1
与len/2
-len
互换 - v_input[2n]和v_input[2n+1]互换
- v_input的
-
最终的exp为
-
from typing import List import z3 import struct def convert(raw: str) -> List[int]: raw = bytearray.fromhex(raw) raw = [raw[x*4:(x+1)*4] for x in range(int(len(raw)/4))] raw = [struct.unpack('<I', x)[0] for x in raw] return raw v_sudo = '010000000400000005000000030000000200000007000000060000000900000008000000080000000300000009000000060000000500000004000000010000000200000007000000060000000700000002000000080000000100000009000000050000000400000003000000040000000900000006000000010000000800000005000000030000000700000002000000020000000100000008000000040000000700000003000000090000000500000006000000070000000500000003000000020000000900000006000000040000000800000001000000030000000600000007000000050000000400000002000000080000000100000009000000090000000800000004000000070000000600000001000000020000000300000005000000050000000200000001000000090000000300000008000000070000000600000004000000' v_d0g3 = '010000000000000005000000030000000200000007000000000000000000000008000000080000000000000009000000000000000500000000000000000000000200000000000000000000000700000000000000000000000100000000000000050000000000000003000000040000000900000000000000010000000000000000000000030000000000000000000000000000000100000000000000000000000700000000000000090000000000000006000000070000000000000003000000020000000900000000000000040000000800000000000000000000000600000000000000050000000400000000000000080000000000000009000000000000000000000004000000000000000000000001000000000000000300000000000000000000000200000001000000000000000300000000000000070000000000000004000000' v_sudo = convert(v_sudo) v_d0g3 = convert(v_d0g3) print(f'v_sudo:{v_sudo}') print(f'v_d0g3:{v_d0g3}') s = z3.Solver() v_result = [index + 48 for index, x in enumerate([0] * 10)] # 初始化数字的ascii v_input = [0] * len(v_result) v_input = [z3.BitVec(str(index), 16) for index, x in enumerate(v_input)] def exp(index: int): x = v_input[index] r = v_result[index] return (x & 0xf3 | ~x & 0xc) - 20 == r def get_result(index: int) -> int: r = None s.add(exp(index)) result = s.check() if result == z3.sat: rs = s.model() r = rs[0] # 获取最后结果key r = rs[r].as_long() # 转换为value else: print(f'fail on {index}') s.reset() return r key_mapper = [get_result(index) for index, x in enumerate(v_result)] print(f'key_mapper:{key_mapper}') # 只填入d0g3中为0的位置,使得v_sudo == v_d0g3 is_invalid = -1 v_encoded = [v_sudo[index] if x == 0 else is_invalid for index, x in enumerate(v_d0g3)] # 转换为输入 v_encoded = list(filter(lambda x: x != is_invalid, v_encoded)) v_encoded = [key_mapper[x] for x in v_encoded] v_encoded = [chr(x) for x in v_encoded] print(f'v_encoded:{v_encoded}') result = v_encoded vel = len(result) # result_len vehl = int(vel / 2) # result_half_len for i in range(vehl): (result[i], result[i+vehl]) = (result[i+vehl], result[i]) for i in range(0, vel-1, 2): (result[i], result[i+1]) = (result[i+1], result[i]) print(''.join(result))
-
得到答案 KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J
-
其他文档
-
CTF逆向-常用的逆向工具 提取码:pnbt
-
常见用法
-
B站教程中国某省队CTF集训(逆向工程部分)
- 中国某省队CTF集训(逆向工程部分)(已授权)(一)
- 基础加密方式例如
XXTEA
、Base64
换表 - Python库
Z3
方程式、不定式等的约束求解
- 基础的假跳转花指令(脏字节)
- 非自然程序流程
- 扁平化程序控制流
- OLLVM程序流程(虚拟机壳) 很难一般不考
- ida里面按
X
键跟踪,寻找所有Ty
为w
的引用(即类型是写入的),通常就是关键位置
- 中国某省队CTF集训(逆向工程部分)(已授权)(二)
- ollydb动调去壳,upx为例子
- python的逆向和自定义虚拟指令
- 使用pycdc 提取码:dorr 解密python编译的exe或者pyc
- 逐条去解析用py字典手动实现的指令调用
- C++编译的程序的逆向
- 中国某省队CTF集训(逆向工程部分)(已授权)(三)
- 简单模运算加密
- base58 寻找一下特别大的数,这种数通常是算法的标识,或者ida7.7版本以上自带的
find crypt
插件ctrl+alt+f
- 常见的关键位置是有新的内存分配的地方通常是关键地方,或者函数中间突然return的地方也是
- 迷宫题 注意绘制出来就好
- 动调题
- 注意观察会执行的反调试分支,例如出现
int 3
,需要跳过去
- 注意观察会执行的反调试分支,例如出现
-
基本知识
更多CTF逆向题通用性做法和常用工具下载参考该博文内容:CTF逆向Reverse题的玩法
相关逆向CTF题
-
Python
-
远程调试汇编
-
流程控制
-
逆向思维
-
安卓
-
虚拟机
-
反调试和SMC
-
加密
-
花指令
-
流程混淆的扁平化处理
-
c/cpp基础