IDA dump内存
- 脚本
import idc
def main():
begin = 0xCD956000; #需对应修改
size = 0x2FB000 # #需对应修改
list = []
for i in range(size):
byte_tmp = ida_bytes.get_byte(begin + i)
list.append(byte_tmp)
if (i + 1) % 0x1000 == 0:
print("All count:{}, collect current:{}, has finish {}".format(hex(size), hex(i + 1), float(i + 1) / size))
print('collect over')
file = "/home/t/tmpkqk13.so" #需对应修改
buf = bytearray(list)
with open(file, 'wb') as fw:
fw.write(buf)
print('write over')
if __name__=='__main__':
main()
- shift+e
IDA取字节数组脚本
addr = 0x403040
size = 114
array = []
for i in range(size):
array.append(ida_bytes.get_dword(addr+4*i))
print(array)
IDA自动patch目标指令的脚本
st = 0x0000000000401117
end = 0x0000000000402144
def patch_nop(start,end):
for i in range(start,end):
PatchByte(i, 0x90) #修改指定地址处的指令 0x90是最简单的1字节nop
def next_instr(addr):
return addr+ItemSize(addr) #ItemSize获取指令或数据长度,这个函数的作用就是去往下一条指令
addr = st
while(addr<end):
next = next_instr(addr)
if "ds:dword_603054" in GetDisasm(addr): #GetDisasm(addr)得到addr的反汇编语句
while(True):
addr = next
next = next_instr(addr)
if "jnz" in GetDisasm(addr):
dest = GetOperandValue(addr, 0) #得到操作数,就是指令后的数
PatchByte(addr, 0xe9)
PatchByte(addr+5, 0x90)
offset = dest - (addr + 5)
PatchDword(addr + 1, offset)
print("patch bcf: 0x%x"%addr)
addr = next
break
else:
addr = next
IDC脚本还原加密代码段
import idc
addr = 0x401500
for i in range(187):
b = ida_bytes.get_byte(addr + i)
ida_bytes.patch_byte(addr + i, b ^ 0x41)
- 在 ida 中选择 File -> Script File…,选择该脚本运行
- 来到 encypt 函数,先将函数 Undefine,并从上到下,在其解释为 db xx 指令处按 c,强转为汇编代码,保证整个函数内没有 db xx 这样的指令
- 再在 0x401500 处右键创建函数,F5
函数长度过大导致IDA反编译失败
- IDA限制了解析函数的长度,过长会反编译失败
- 修改配置文件
IDA 7.0\cfg\hexrays.cfg
来解决
IDA函数识别失败
IDA堆栈不平衡导致反编译失败
迷宫转换脚本
maze = "00 00 00 00 23 00 00 00 00 00 00 00 23 23 23 23 00 00 00 23 23 00 00 00 4F 4F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4F 4F 00 50 50 00 00 00 00 00 00 4C 00 4F 4F 00 4F 4F 00 50 50 00 00 00 00 00 00 4C 00 4F 4F 00 4F 4F 00 50 00 00 00 00 00 00 4C 4C 00 4F 4F 00 00 00 00 50 00 00 00 00 00 00 00 00 00 4F 4F 00 00 00 00 50 00 00 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 23 00 00 00 00 00 00 00 00 00 4D 4D 4D 00 00 00 23 00 00 00 00 00 00 00 00 00 00 4D 4D 4D 00 00 00 00 45 45 00 00 00 30 00 4D 00 4D 00 4D 00 00 00 00 45 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 45 45 54 54 54 49 00 4D 00 4D 00 4D 00 00 00 00 45 00 00 54 00 49 00 4D 00 4D 00 4D 00 00 00 00 45 00 00 54 00 49 00 4D 00 4D 00 4D 21 00 00 00 45 45".split(' ')
maze = [int(i, 16) for i in maze]
maze = [i if i !=0 else ord('*') for i in maze]
maze = ''.join(map(chr, maze))
for i in range(0, len(maze), 16):
print maze[i:i+16]
数独游戏的代码识别
字节合并的代码实现
v14是memory输入的每个字节叠在一起的结果,每8次叠加赋值给一个变量(int64最多只能存储64位数据),所以x,y,z都是flag的一部分,剩余的部分存在v8里
字符串和字节数组互转脚本
enc = "********CENSORED********"
m = [0x410A4335494A0942, 0x0B0EF2F50BE619F0, 0x4F0A3A064A35282B]
import binascii
flag = b''
for i in range(3):
p = enc[i * 8:(i + 1) * 8]
print(p)
a = binascii.b2a_hex(p.encode('ascii')[::-1])
print(a)
b = binascii.a2b_hex(hex(int(a, 16) + m[i])[2:])[::-1]
print(b)
flag += b
print(flag)
字符串、字节流、十六进制数组之间的转换脚本
import binascii
# array = "7635fdf57d47fe95137a26593fff31a1857c63026ebd936a3e4d8dd727732d5ecc62f2dfe5d200"
# medium = b"v5\xfd\xf5}G\xfe\x95\x13z&Y?\xff1\xa1\x85|c\x02n\xbd\x93j>M\x8d\xd7's-^\xccb\xf2\xdf\xe5\xd2\x00"
# 字节流转十六进制数组
array = binascii.b2a_hex(bytes).decode('utf-8')
# 十六进制数组转字节流
bytes = binascii.a2b_hex(array).encode('utf-8')
4、8字节数据按小端序转化为字节数组
-
先转化为字节序列
# 可用struct和p32方法 from struct import * raw = [0xFD370FEB59C9B9E, 0xDEAB7F029C4FD1B2, 0xFACD9D40E7636559] target = '' for i in range(3): target += pack("Q", raw[i])
-
再将字节序列转化为数组
for i in range(len(target)): array.append(ord(target[i]))
脱离程序的逐字节爆破脚本
- 要求:可以提取算法,位与位之间无关联
flag = ''
charSet = string.uppercase + string.lowercase
charSet = map(ord, charSet)
for i in range(len(key)):
for char in charSet:
if text[i] == (char - 39 - key[i] + ord('a')) % 26 + ord('a'):
flag += chr(char)
break
print flag
依赖程序的逐字节爆破脚本
- 要求:程序会提示哪一位出错
from pwn import *
import re
flag = "actf{"
k = 0
while True:
for i in range(33,127):
p = process('./SoulLike')
_flag = flag + chr(i)
print _flag
p.sendline(_flag)
s = p.recvline()
r = re.findall("on #(.*?)\n", s)[0]
r = int(r)
if r == k:
print 'no'
print k
elif r == k + 1:
print s
flag += chr(i)
k += 1
p.close()
p.close()
if k > 11:
break
print (flag)
与位置无关的加密爆破
- 一般加密的过程是out = f(in, n),in是输入的单个字节,n是第几位,out是输出的单个字节
- 一种特殊的加密过程是out = f(in),也就是输入的每一位都进行完全相同的变换
- 这种特殊的加密相当于对字符集做了一个映射关系,那么可以输入所有字符,通过动调捕获输出的字节,即可建立该一对一映射关系,从而按照目标输出恢复原始输入
VM虚拟机逐位爆破
int main()
{
env_init();
char flag[16]{};
for (int i = 0; i < 15; i++) {
// 创建快照
save_vm_state();
// 所有可见字符
for (int j = 33; j < 128; j++) {
flag[i] = j;
if (vm_operate(i, flag)) {
// flag[i]中途可能会被case 8修改,这里再赋值一次
flag[i] = j;
break;
}
// 本次猜测不正确,还原现场
recover_vm_state();
}
}
flag[15] = 0;
puts(flag);
}
angr求解目标点的输入
import angr
project = angr.Project("./signal.exe", auto_load_libs = False)
@project.hook(0x40179E)
def MyHook(state):
print(state.posix.dumps(0))
project.terminate_execution()
project.execute()
用angr来输出程序路径信息
import angr
count = 0
project = angr.Project("./attachment.exe")
# 修改目标位置处的寄存器值
@project.hook(addr = 0x401712)
def hook0(state):
# modify variable "inputLen"
state.mem[state.regs.rbp+0x1470].dword = 15
# 到达某地址处打印信息
@project.hook(0x40174C)
def hook1(state):
global count
print("char %d:" % count)
count += 1
# 到达某地址处打印信息
@project.hook(0x4017C6)
def hook2(state):
print("op1")
# 到达某地址处结束执行
@project.hook(0x401B51)
def hook6(state):
project.terminate_execution()
求解多元方程组脚本
z3-resolve
from z3 import *
#s = Solver()
v1 = Int('v1')
v2 = Int('v2')
v3 = Int('v3')
v4 = Int('v4')
v5 = Int('v5')
v6 = Int('v6')
v7 = Int('v7')
v8 = Int('v8')
v9 = Int('v9')
v11 = Int('v11')
s.add(-85 * v9 + 58 * v8 + 97 * v6 + v7 + -45 * v5 + 84 * v4 + 95 * v2 - 20 * v1 + 12 * v3 == 12613)
s.add(
30 * v11 + -70 * v9 + -122 * v6 + -81 * v7 + -66 * v5 + -115 * v4 + -41 * v3 + -86 * v1 - 15 * v2 - 30 * v8 == -54400)
s.add(-103 * v11 + 120 * v8 + 108 * v7 + 48 * v4 + -89 * v3 + 78 * v1 - 41 * v2 + 31 * v5 - (
v6 * 64) - 120 * v9 == -10283)
s.add(71 * v6 + (v7 * 128) + 99 * v5 + -111 * v3 + 85 * v1 + 79 * v2 - 30 * v4 - 119 * v8 + 48 * v9 - 16 * v11 == 22855)
s.add(5 * v11 + 23 * v9 + 122 * v8 + -19 * v6 + 99 * v7 + -117 * v5 + -69 * v3 + 22 * v1 - 98 * v2 + 10 * v4 == -2944)
s.add(-54 * v11 + -23 * v8 + -82 * v3 + -85 * v2 + 124 * v1 - 11 * v4 - 8 * v5 - 60 * v7 + 95 * v6 + 100 * v9 == -2222)
s.add(-83 * v11 + -111 * v7 + -57 * v2 + 41 * v1 + 73 * v3 - 18 * v4 + 26 * v5 + 16 * v6 + 77 * v8 - 63 * v9 == -13258)
s.add(81 * v11 + -48 * v9 + 66 * v8 + -104 * v6 + -121 * v7 + 95 * v5 + 85 * v4 + 60 * v3 + -85 * v2 + 80 * v1 == -1559)
s.add(101 * v11 + -85 * v9 + 7 * v6 + 117 * v7 + -83 * v5 + -101 * v4 + 90 * v3 + -28 * v1 + 18 * v2 - v8 == 6308)
s.add(99 * v11 + -28 * v9 + 5 * v8 + 93 * v6 + -18 * v7 + -127 * v5 + 6 * v4 + -9 * v3 + -93 * v1 + 58 * v2 == -1697)
if s.check() == sat:
result = s.model()
print(result)
angr
import angr
base_addr = 0x400000
project = angr.Project("./UniverseFinalAnswer",
main_opts = {"base_addr" : base_addr},
auto_load_libs = False)
state = project.factory.entry_state(add_options = {angr.options.LAZY_SOLVES})
simManager = project.factory.simgr(state)
simManager.explore(find = base_addr + 0x71A, avoid = base_addr + 0x6EF)
flag = simManager.found[0].posix.dumps(0)
print(flag[:10].decode())
jsfucker解密
-
法一:在线网站
-
法二:js脚本替换
<script> function deEquation(str) { for(let i = 0; i <= 1; i++) { str = str.replace(/l\[(\D*?)](\+l|-l|==)/g, (m, a, b) => 'l[' + eval(a) + ']' + b); } str = str.replace(/==(\D*?)&&/g, (m, a) => '==' + eval(a) + '&&'); return str; } js="jsf**k"//将代码放入其中 res=deEquation(js); document.write(res); </script>
-
法三:python库求解js执行结果
import re import execjs from z3 import * #用execjs获取js代码的执行结果 def FrontSub(matched): num = execjs.eval(matched.group(2)) return "l[%s]" % num def EndSub(matched): num = execjs.eval(matched.group(0)) return str(num) # 从js代码中提取等式 html_path = "E:\\Project\\Reverse\\BuuCtf\\18\\output\\equation.html" with open(html_path, "r") as f: content = f.read().split("\n")[4].strip() _content = re.search('if\((.*)\)', content).group(1) eq_list = _content.split("&&") # 构造js代码解密后的等式 z3_eqs = [] for equation in eq_list: [front, end] = equation.split("==") f_tmp = re.sub('(l\[)([\[\]!\+]+)(\])', FrontSub, front) # 等号前的js代码执行结果 e_tmp = re.sub('.+', EndSub, end) # 等号后的js代码执行结果 z3_eq = f_tmp + '==' + e_tmp # 构造等式 z3_eqs.append(z3_eq) S = Solver() l = IntVector('l', 42) for eq in z3_eqs: eval("S.add(" + eq + ")") if S.check() == sat: result = S.model() for i in range(0x2a): print(chr(int("%s"%(result[l[i]]))),end="")
pyinstaller逆向技巧
- 用PyInstaller Extractor解包,得到pyc和struct文件
- 对比pyc和struct文件,把struct文件前几个字节插入login开头
- 反编译pyc文件,得到源代码
perlAPP压缩程序的还原
- perl程序经过perAPP的压缩
- 在程序运行过程中,会调用script来解压程序(搜索script字符串,找到最近的call)
- 解压后,eax指向perl源码地址,程序调用perl解释器来运行源码
Unity逆向技巧
- Unity生成游戏时,开发者写的所有 C# 类会被整合编译到 Assembly-CSharp.dll 里,该动态链接库所在的路径是 xxx_Data\Managed,其中 xxx 为游戏名。这题的 dll 就在 BJD hamburger competition_Data\Managed 目录下,Reflector 打开该 dll,可以看到类和方法的定义。
- 关注更新UI的方法和语句
xspy分析mfc窗口
dialogbox弹出窗口
CE逆向
-
相比x64dbg,更适用于面向对象语言/框架的逆向,可以直接定位类和实例,获取字段值,给方法下断点
-
内存中暴力搜索字符串
- 首次搜索:字符串要选中utf-16
- 再次搜索:用于定位变化的值
-
查看实例的字段值
- Mono->分析Mono->右键实例->Add Filed
- 记录器中激活目标字段,右键浏览相关区域内存
-
方法下断点,观察参数和返回值
- 分析Mono->右键目标方法,选择jit
- 函数入口处设置断点
- 断下后,查看寄存器值,以16进制显示
VM逆向
- 直接看链接吧(链接)
反调试Patch
SEH反调试
-
UnhandledExceptionFilter只有在没有调试器附加时才会被调用,可以将敏感代码(如SMC代码)放到seh handler中,达到反调试的效果,下面看一个案例
-
在函数开头注册了一个407b58的seh struct,这部分代码不会被反编译出来
- 查看seh struct,4023dc和4023ef分别是filter和handler
- 由于IDA边界识别的问题,filter和handler函数没有被反编译出来
-
handler中调用了另一个SMC函数,该函数对代码进行了解密
-
主函数中搜索sctf段,调用debugbreak,如果没有调试器附加,触发seh handler->smc的调用链,解密隐藏数据
ollvm去平坦化
- ollvm混淆达到平坦化的效果,控制流图
- 反编译代码
-
去平坦化脚本(下载链接),利用angr求解路径,命令行运行,addr后跟待处理的函数首地址
python deflat.py -f attachment --addr 0x4006F0
-
世界美好了
Windows程序的初始化
start->mainCRTStartup->initterm/initterm_e ,该函数初始化了所有全局和静态 C++ 类对象的构造函数
Windows异常处理
-
Windows 用户态异常发生先找调试器,没有再找 VEH,VEH 处理不了再找 SEH, SEH 还处理不了找 UEF(UnhandledExceptionFilter,用户设置的 TopLevelExceptionFilter 在该阶段被调用)
-
VEH通过AddVectoredExceptionHandler注册
-
UEF通过SetUnhandledExceptionFilter注册
-
SEH的注册
Python测试调用DLL
from ctypes import *
dll = cdll.LoadLibrary("Interface.dll")
for i in range(0, 0x64):
print("Testing: " + str(i))
dll.GameObject(i)
python发送win32消息
import win32gui
hwnd = win32gui.FindWindow(None, "Flag就在控件里")
win32gui.SendMessage(hwnd, 0x464, None, None)
逐字节前后异或解密
- 解密方向同加密方向相反,从加密的最后一个字节开始解密
静态分析看不到程序主逻辑
-
程序对关键字符串关键call进行了加密,只能通过动态调试的方式来还原程序
-
通过内存访问断点或中断法,来跟踪对输入的加密流程
-
如果遇到调用了另一个程序或者子进程中处理逻辑的情况,可能需要考虑用工具加解密
-
单步调,直到看到输入的字符串
去花指令的原则
- 编译器不会生成无用指令,无用指令一定要patch
- 有用指令一定要保留
负数的除法处理
- 有符号数右移之后,负数会变成正数,所以需要再异或一个 0x8000000000000000
item = item//2
item = item | 0x8000000000000000
关于指针
-
指针的偏移
int64 a1 *(_DWORD *)(a1 + 8)等价于 *((_DWORD *)a1 + 2) 如果没有指明a1类型,视作整数加减
-
数组可以视作指针
unsigned char Data[3]={1,2,3}; 1.地址关系:&Data[0] = Data 、&Data[1] = Data+1、&Data[2] = Data+2; 2.数值关系:Data[0] = *Data = 1;Data[1] = *(Data+1) = 2 ;Data[2] = *(Data+2) = 3;