[安洵杯 2019]crackMe
分析
MessageBoxW被hook跳转到了别的函数,这部分在拓展再说,先分析程序
跳转到了sub_C02AB0,进行了两步操作
- base64表的大小写互换
- 注册VEH
然后回到main函数,安装了一个SEH,然后将eax清零,在地址0(eax)写入数据,触发 EXCEPTION_ACCESS_VIOLATION异常,执行异常处理
以下是Windows 用户态异常处理的四个主要阶段
flowchart LR A["触发异常"]-->B["调试器"]-->C["向量化异常处理(VEH)"]-->D["结构化异常处理(SEH)"]-->E["顶层未处理异常过滤器(UEF)"]
由于已经注册了VEH,所以先进入VEH处理
VEH中进行了两步操作
- 用where_are_u_now?初始化sm4加密
- 设置 UEF 为 TopLevelExceptionFilter
SEH中对输入进行sm4加密,返回值为 1,继续进行错误处理
进入UEF(TopLevelExceptionFilter)
对str2每两位进行置换,然后对str1进行变种base64加密
进入sub_C0126C
这里将str1进行变种base64加密,加密算法为base64换表,base64有两处换表
- 大小写互换
- 整个表想左移动24字符
最终表为yzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvwx
最后str1和str2在sub_C01136进行对比
exp
import base64
from pysm4 import decrypt
from Crypto.Util.number import long_to_bytes
key = "where_are_u_now?"
key = key.encode().hex()
Str2 = "1UTAOIkpyOSWGv/mOYFY4R==" # !要改成=
temp = list(Str2)
for i in range(0,len(temp),2):
temp[i],temp[i+1] = temp[i+1],temp[i]
res = "".join(temp)
mybase64 = str.maketrans("yzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvwx","ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
temp = base64.b64decode(res.translate(mybase64).encode('utf-8')).hex()
key = 0x77686572655f6172655f755f6e6f773f
temp = 0x59d095290df2400614f48d276906874e
c = decrypt(temp, key)
print(f"flag{{{long_to_bytes(c).decode()}}}")
扩展
本题采用了IAT hook技术
-
什么是IAT hook? IAT Hook(Import Address Table Hook)是一种在 Windows 系统中常见的 API Hook 技术,主要用于拦截和修改应用程序对动态链接库(DLL)中导入函数的调用
-
什么是IAT? 在 Windows 的可执行文件(PE 文件)中,IAT 是一个数据结构,用于存储程序在运行时所需的外部函数的实际地址。当程序调用 DLL 中的函数时,会通过 IAT 中的指针间接调用这些函数。这种机制允许操作系统在加载程序时动态解析和填充这些函数地址
-
IAT Hook 的原理 IAT Hook 的核心思想是修改 IAT 中的函数指针,将原本指向系统或其他 DLL 中函数的地址替换为自定义函数的地址。这样,当程序调用该函数时,实际上会执行自定义的函数,从而实现对函数调用的拦截和控制。
-
实现步骤
-
- 获取模块基地址:使用 GetModuleHandle 获取目标程序或 DLL 的基地址。
-
- 解析 PE 文件结构:通过 PE 文件格式,定位导入表(IMAGE_IMPORT_DESCRIPTOR)和导入地址表(IAT)
-
- 查找目标函数:遍历导入表,找到需要 Hook 的函数在 IAT 中的入口。
-
- 修改 IAT 项:将目标函数在 IAT 中的地址替换为自定义函数的地址。
-
- 实现自定义函数:编写自定义函数,该函数可以在执行特定操作后调用原始函数,或完全替代原始函数的功能。
在main函数处下断点,运行
按下X查找sub_C01023的交叉引用来到sub_C027B0,再向上查找,最后找到rdata数据段的sub_C01235
在sub_C01E40处获取程序基址,并传程序基址,要 hook 的函数所在的 dll 名 - User32.dll,要 hook 的函数名 - MessageBoxW给sub_C0114A
调用 LoadLibrary 加载 user32.dll,返回 user32.dll 的基址存到 hModule 中;调用 GetProcAddress,传入 hModule 与 MessageBoxW 函数名,得到 MessageBoxW 函数的内存地址
继续传程序基址,要 hook 的函数所在的 dll 名 - User32.dll,要 hook 的函数名 - MessageBoxW给sub_C0114A
IAT hook如图所示
[GUET-CTF2019]encrypt
分析
载入IDA找到main函数,F5反编译
输入字符串后会进行三个函数的处理,其中第一个函数没有用到用户内容,可以动调获取结果
后两个进去看看
RC4加密,对输入的每个字符进行异或操作,由于用于对输入的字符串进行异或的LOBYTE(v9[(unsigned __int8)(v7 + v8)]);并没有用到我们输入的内容,所以可以动调获取key
在异或处断点,然后开始动调
esi存放输入的字符串,edx就是异或的密钥,所以我们可以用idc爆破edx,但是还不知道要爆破几位
#include <idc.idc>
static main(){
auto i;
for(i = 0;i < 39;i++){
Message("0x%X,",GetRegValue("EDX"));
RunTo(0x4008D7);
GetDebuggerEvent(WFNE_SUSP, -1);
}
}
先去后面的函数看看
从这里可以知道要爆破的值为39,因为data数据长度为39
将加密整理出来
data[i] = ((flag[i] >> 2) & 0x3f) + 0x3d;
data[i+1] = (((flag[i + 1] >> 4) | (16 * flag[i])) & 0x3f) + 0x3d;
data[i+2] = (((flag[i + 2] >> 6)| (4 * flag[i + 1])) & 0x3f) + 0x3d;
data[i+3] = (flag[i + 2] & 0x3f) + 0x3d;
加密的过程大概是将flag每3位拆成4份,将每份经过拼凑,位移后存与data中,详细分析一下 & 0x3f表示取低6位,画表如下
原始位段 | 存入 data 索引 | 存入 data 位域 | 说明 |
---|---|---|---|
flag[i] bit 7–bit 2 (高 6 位) | data[i] | bit 0–bit 5 | (flag[i] » 2) & 0x3F → 6 位 |
flag[i] bit 1–bit 0 (低 2 位) | data[i+1] | bit 4–bit 5 | (flag[i] « 4) → 再 &0x3F 保留这 2 位 |
flag[i+1] bit 7–bit 4 (高 4 位) | data[i+1] | bit 0–bit 3 | (flag[i+1] » 4) → 4 位 |
flag[i+1] bit 3–bit 0 (低 4 位) | data[i+2] | bit 2–bit 5 | (flag[i+1] « 2) → 再 &0x3F 保留这 4 位 |
flag[i+2] bit 7–bit 6 (高 2 位) | data[i+2] | bit 0–bit 1 | (flag[i+2] » 6) → 2 位 |
flag[i+2] bit 5–bit 0 (低 6 位) | data[i+3] | bit 0–bit 5 | flag[i+2] & 0x3F → 6 位 |
所以只要通过&操作对data取位,通过|拼接就能还原flag
exp
data = [
0x5a, 0x60, 0x54, 0x7A, 0x7A, 0x54, 0x72, 0x44, 0x7C, 0x66,
0x51, 0x50, 0x5B, 0x5F, 0x56, 0x56, 0x4C, 0x7C, 0x79, 0x6E,
0x65, 0x55, 0x52, 0x79, 0x55, 0x6D, 0x46, 0x6B, 0x6C, 0x56,
0x4A, 0x67, 0x4C, 0x61, 0x73, 0x4A, 0x72, 0x6F, 0x5A, 0x70,
0x48, 0x52, 0x78, 0x49, 0x55, 0x6C, 0x48, 0x5C, 0x76, 0x5A,
0x45, 0x3D
]
# print(len(data))
flag=''
for i in range(0,len(data),4):
flag+=chr((((data[i]-0x3D)&0x3F)<<2)|(((data[i+1]-0x3D)&0x30)>>4))
flag+=chr((((data[i+1]-0x3D)&0x0F)<<4)|(((data[i+2]-0x3D)&0x3C)>>2))
flag+=chr(((data[i+3]-0x3D)&0x3F)|((data[i+2]-0x3D)&0x03)<<6)
l = [
0x10,0x59,0x9C,0x92,0x06,0x22,0xCF,0xA5,0x72,0x1E,
0x45,0x6A,0x06,0xCB,0x08,0xC3,0xE4,0x49,0x5A,0x63,
0x0C,0xDF,0xF6,0x5F,0x08,0x28,0xBD,0xE2,0x10,0x15,
0x1F,0x6E,0xAA,0x5A,0xCA,0xEC,0x80,0xAF,0x9B,0x16,
0xBB,0x3D,0x13,0x2F,0x6A,0xA4,0xC7,0x2E,0xBC,0x4B,
0x60,0x9A,0xAF,0xE9,0xCE,0xDA,0x67,0x39,0xBA,0x3B,
0x85,0xEB,0xD2,0x6B,0xAB,0x06,0x6B,0x10,0x57,0x2C,
0x88,0x70,0xF7,0x4F,0xAA,0x7F,0x12,0x47,0xD6,0xDE,
0x74,0xB2,0x1D,0xA4,0xD7,0x76,0x9A,0xE0
]
a=list(flag)
flag=''
for i in range(len(a)):
flag+=chr(ord(a[i])^l[i])
print(flag)
flag{e10adc3949ba59abbe56e057f20f883e}
[WUSTCTF2020]funnyre
分析
载入IDA,红了一大段,发现是main函数存在花指令
NOP后选中红色区域按下P重构函数,F5反编译,发现JUMPOUT(0x400B82),说明还有花指令,按下Tab键去看看,存在相同的花指令
一共有4处,全部NOP掉就行了
最后有个比较,应该就是对比上面一千多行加密后的flag值,很明显这不是人算的,而且也没有字符输入的地方,需要写入内存,所以用angr来进行符号执行
进反汇编看看具体操作
先看看字符串存储模式
从
lea rdi, [rdx+5]
开始是为了绕过前面的比较,不然的话后面需要在bss段也写入flag{
[rdx+5]
就是flag{
后的内容
然后对flag{}包裹的内容进行逐字符异或
再看看最后的比较
已知起点,终点,可以开始建立约束求解
- 利用
angr.Project
加载文件,返回一个 Project 对象,利用auto_load_libs=False
用自动载入依赖库(如 libc),将所有外部函数解析为无约束的 SimProcedure stub(ReturnUnconstrained),以加快分析速度并减少不必要的库模型 - 利用
entry_state
构造一个起始于 虚拟地址 0x400601 的符号执行状态 state - 利用claripy.BVS创建符号位向量flag,用
.memory.store(addr,flag)
将其存入bss段 用state.regs.rdx = 0x603055
将rdx指向bss段 - 利用
simulation_manager
基于初始状态 state 构造一个 SimulationManager对象 sm,用于批量管理和调度多个执行状态。 - 利用
explore(fine=)
设定目标地址,开始符号执行,直到找到目标地址或达到最大执行次数。 - 利用
found[0].solver.eval(flag, cast_to=bytes)
求解flag的值,并转为bytes类型输出。
exp
import angr
import claripy
p = angr.Project('./attachment',auto_load_libs=False)
state = p.factory.entry_state(addr = 0x400601)
flag = claripy.BVS('flag', 8*32)
state.memory.store(0x603055 + 5,flag)
state.regs.rdx = 0x603055 #因为后续的异或操作是从rdx+5处开始的,所以rdx指向前5个字节
sm = p.factory.simulation_manager(state)
sm.explore(find=0x401DA9)
solution = sm.found[0].solver.eval(flag, cast_to=bytes)
print(solution.decode())
flag{1dc20f6e3d497d15cef47d9a66d6f1af}