符号执行(Symbolic Execution)是一种程序分析技术,其功能是通过分析程序来得到让特定代码区域执行的输入。
符号执行介绍:https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.3_symbolic_execution.html
论文:Symbolic Execution for Software Testing: Three Decades Later
在现实中的程序往往很复杂,分析难度大效率低,以至于无法人工地收集执行到某条路径的输入信息,这时候就可以使用程序(Angr)进行相关的辅助工作。
Angr是一个利用python开发的二进制程序分析框架,我们可以利用这个工具尝试对一些CTF题目进行符号执行来找到正确的解答。当然,要注意的是符号执行的路径选择问题到现在依旧是一个很大的问题,换句话说也就是当我们的程序存在循环时,因为符号执行会尽量遍历所有的路径,所以每次循环之后会形成至少两个分支,当循环的次数足够多时,就会造成路径爆炸,整个机器的内存会被耗尽。
Angr使用符号执行分析一个程序时,会将程序的“输入”抽象化为“符号”,而非一般执行程序时使用的具体值。(注意:这里说的“符号”并不是什么字符、字符串,而是类似于数学解方程是的x、y、z等等未知的待解的变量)
Angr将程序中的一块块代码区域描述为“状态”,类型为SimState。在给定一个初始状态为参数,可使用执行模拟器simulation_manager模拟执行程序。在模拟执行的过程中,会得到不同类型的多个的状态(SimState)。通过状态,分析器可以得到相应的路径约束,然后通过约束求解器来得到可以触发目标代码区域的具体值。
使用介绍(官方文档):https://docs.angr.io/core-concepts/toplevel
中文手册:https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.3.1_angr.html
下面的大部分内容为本人在angr_ctf练习时的记录,练习过程主要的参考文章为看雪的系列文章,里面有较为详细的解释:
angr学习(1):https://bbs.pediy.com/thread-264775.htm
angr学习(2):https://bbs.pediy.com/thread-264878.htm
angr学习(3):https://bbs.pediy.com/thread-268300.htm
angr学习(4):https://bbs.pediy.com/thread-269990.htm
目前使用的是这个,比较方便。
# 直接运行:
sudo apt install ipython3
pip install angr
#执行脚本时:ipython3 exp.py
# @@@angr容器
sudo docker run -itd --name angr angr/angr
sudo docker exec -it angr bash
su angr # root用户没有angr的环境,需切换到angr用户
# 如果su angr显示需要密码,但你又不知道密码的话,可以在上一命令加上参数:-u 0
# 即sudo docker exec -u 0 -it angr bash 可以直接root权限打开容器
# @@@安装checksec工具
mkdir /opt/tools
cd /opt/tools
# 官方
# git clone https://github.com/slimm609/checksec.sh.git
# 加速
git clone https://gitclone.com/github.com/slimm609/checksec.sh.git
cd checksec.sh
# 建立链接
ln -s /opt/tools/checksec.sh/checksec /usr/local/bin/checksec
# @@@克隆项目
cd /opt
# 官方
# git clone https://github.com/jakespringer/angr_ctf.git
# 加速
git clone https://gitclone.com/github.com/jakespringer/angr_ctf.git
# 查看二进制文件
cd angr_ctf/dist/
IDA静态分析:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+1Ch] [ebp-1Ch]
char s1[9]; // [esp+23h] [ebp-15h] BYREF
unsigned int v6; // [esp+2Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
printf("Enter the password: ");
__isoc99_scanf("%8s", s1);
for ( i = 0; i <= 7; ++i )
s1[i] = complex_function(s1[i], i);
if ( !strcmp(s1, "JACEJGCS") )
puts("Good Job."); //目标位置
else
puts("Try again.");
return 0;
}
// other:
int __cdecl complex_function(int a1, int a2)
{
if ( a1 <= 64 || a1 > 90 )
{
puts("Try again.");
exit(1);
}
return (3 * a2 + a1 - 65) % 26 + 65;
}
//目标所在位置:
.text:08048675 loc_8048675: ; CODE XREF: main+9A↑j
.text:08048675 sub esp, 0Ch
.text:08048678 push offset aGoodJob ; "Good Job."
.text:0804867D call _puts
.text:08048682 add esp, 10h
Project是angr中的控制基础,是后续操作的把柄。有了它就能对刚加载的可执行文件进行分析和仿真。几乎在angr中使用的每一个对象都依赖于Project的存在。
程序状态 state 是一个 SimState 类型的对象,angr.factory.AngrObjectFactory
类提供了创建 state 对象的方法。其中,.entry_state()
:从主对象文件的入口点创建一个 state。
程序执行过程中会产生很多state,模拟管理器(Simulation Managers)则是对这些状态进行管理。创建模拟管理器对象是需要一个状态,没有参数是默认是程序入口点的状态,即.entry_state()
模拟管理器对象实现了.explore()
功能,允许在探索时查找(find)或避免(avoid)某些地址。
import angr # 引入angr模块
proj = angr.Project('./00_angr_find') # 将一个二进制文件定义为一个对象,是后续操作的基础
init_state = proj.factory.entry_state() # 程序的入口状态,start函数,并不是main
sm = proj.factory.simulation_manager(init_state) #使用执行模拟器,执行起点为init_state。simulation_manager()也可以缩写为simgr()
sm.explore(find=0x08048675) # 开始模拟执行,并设置探索目标为0x08048678处
find_state = sm.found[0] # 探索到的状态
print(find_state.posix.dumps(0)) # posix.dumps模拟posix环境下的输出输入
# 0为能到达该状态的输入值
# 1时为程序的输出信息
# 结果: b'JXWVXRKX'
用32IDA打开,发现对main反编译不了,提示说函数太大了。只能先看汇编大概了解一下程序。
程序和上一程序类似,但main函数中出现超级多次avoid_me函数,导致main不能在IDA反汇编。在模拟执行的explore函数中,参数除了find外还有avoid。因此我们可以通过传入avoid_me函数的地址进行符号执行分析。
目的地:.text:080485E0 push offset aGoodJob ; "Good Job."
需避免的:.text:080485A8 avoid_me proc near ; CODE XREF: main+1CA↓p
// maybe_good函数
int __cdecl maybe_good(char *s1, char *s2)
{
if ( should_succeed && !strncmp(s1, s2, 8u) )
return puts("Good Job.");
else
return puts("Try again.");
}
// avoid_me函数
void avoid_me()
{
should_succeed = 0;
}
另外,避开avoid_me函数不仅仅是因为其数量多导致效率低,而且其还会影响maybe_good的执行路径。
import angr
proj = angr.Project('./01_angr_avoid')
init_state = proj.factory.entry_state()
sm = proj.factory.simulation_manager(init_state)
target = 0x080485E0
bad_addr = 0x080485A8
sm.explore(find=target, avoid=bad_addr)
find_state = sm.found[0]
print(find_state.posix.dumps(0))
int __cdecl complex_function(int a1, int a2)
{
if ( a1 <= 64 || a1 > 90 )
{
puts("Try again.");
exit(1);
}
return (31 * a2 + a1 - 65) % 26 + 65;
}
上一程序是一个函数多次调用。这个从main函数的汇编代码看,出现了很多打印Good Job.
、Try again.
的地方。如果使用地址进行约束,就会很繁琐。本程序需要定义函数来检测输出的字符进行约束。
import angr
def is_successful(state):
output = state.posix.dumps(1)
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(1)
return 'Try again.' in str(output)
proj = angr.Project("./02_angr_find_condition")
init_state = proj.factory.entry_state()
sm = proj.factory.simgr(init_state)
sm.explore(find=is_successful, avoid=should_abort)
find_state = sm.found[0]
print(find_state.posix.dumps(0))
int get_user_input()
{
int v1; // [esp+0h] [ebp-18h] BYREF
int v2; // [esp+4h] [ebp-14h] BYREF
int v3[4]; // [esp+8h] [ebp-10h] BYREF
v3[1] = __readgsdword(0x14u);
__isoc99_scanf("%x %x %x", &v1, &v2, v3);
return v1;
}
//IDA
.text:0804891C xor ecx, ecx
.text:0804891E lea ecx, [ebp+var_10]
.text:08048921 push ecx
.text:08048922 lea ecx, [ebp+var_14]
.text:08048925 push ecx
.text:08048926 lea ecx, [ebp+var_18]
.text:08048929 push ecx
.text:0804892A push offset aXXX ; "%x %x %x"
.text:0804892F call ___isoc99_scanf
.text:08048934 add esp, 10h
.text:08048937 mov ecx, [ebp+var_18]
.text:0804893A mov eax, ecx //v1
.text:0804893C mov ecx, [ebp+var_14]
.text:0804893F mov ebx, ecx //v2
.text:08048941 mov ecx, [ebp+var_10]
.text:08048944 mov edx, ecx //v3
.text:08048946 nop
get_user_imput
函数的作用主要是按顺序对v1、v2和v3输入值,然后v1、v2和v3又分别传值给eax、ebx和edx。
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // ebx
int user_input; // eax
int v5; // edx
unsigned int v7; // [esp+4h] [ebp-14h]
unsigned int v8; // [esp+8h] [ebp-10h]
int v9; // [esp+Ch] [ebp-Ch]
unsigned int v10; // [esp+Ch] [ebp-Ch]
printf("Enter the password: ");
user_input = get_user_input();
v9 = v5;
v7 = complex_function_1(user_input);
v8 = complex_function_2(v3);
v10 = complex_function_3(v9);
if ( v7 || v8 || v10 )
puts("Try again.");
else
puts("Good Job.");
return 0;
}
//IDA
.text:08048978 add esp, 10h
.text:0804897B call get_user_input
.text:08048980 mov [ebp+var_14], eax //v7
.text:08048983 mov [ebp+var_10], ebx //v8
.text:08048986 mov [ebp+var_C], edx //v10
.text:08048989 sub esp, 0Ch
在get_user_input函数中输入的值,分别作为complex_function_1/2/3的参数进行运算,只有三个函数的返回值都为假才成功。这个程序的scanf函数的格式化字符串比其他程序的要复杂(好像只有单个输入时才能自动模拟🙃),所以需要手动注入数据。0x08048980为输入数据后的第一个指令,所以将这里设为初始状态开始模拟,在创建初始状态后,可通过init_state.regs.
对指令传入符号变量,起到get_user_input函数的作用。
angr的求解引擎为Claripy,用来根据程序所需要的输入来设置符号变量以及收集限制式等等。
创建符号变量:claripy.BVS(‘x’, 32)
创建具体值变量:claripy.BVV(0x12345678, 32)
import angr
import claripy
proj = angr.Project("./03_angr_symbolic_registers")
start_addr = 0x8048980
init_state = proj.factory.blank_state(addr=start_addr) # 自定义起始地址
# 创建符号位向量(angr用于给二进制文件注入符号值的数据类型)
value0 = claripy.BVS('x', 32) # value0为32bit
value1 = claripy.BVS('y', 32)
value2 = claripy.BVS('z', 32)
# 将寄存器设置为符号值。 这是向程序中注入符号的一种方法。
# 将不同寄存器设置为不同的符号值
init_state.regs.eax = value0
init_state.regs.ebx = value1
init_state.regs.edx = value2
sm = proj.factory.simgr(init_state)
def is_successful(state):
output = state.posix.dumps(1)
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(1)
return 'Try again.' in str(output)
sm.explore(find=is_successful, avoid=should_abort)
find_state = sm.found[0]
# 使用eval来得到求解值
# 原解题脚本的state.se已经被废弃,根据提示使用state.solver来求解value符号变量的值
s0 = find_state.solver.eval(value0)
s1 = find_state.solver.eval(value1)
s2 = find_state.solver.eval(value2)
s = ' '.join(map('{:x}'.format, [s0,s1,s2]))
print(s)
int handle_user()
{
int v1; // [esp+8h] [ebp-10h] BYREF
int v2[3]; // [esp+Ch] [ebp-Ch] BYREF
__isoc99_scanf("%u %u", v2, &v1); //手动注入数据(栈数据)
v2[0] = complex_function0(v2[0]);
v1 = complex_function1(v1);
if ( v2[0] == 1999643857 && v1 == -1136455217 )
return puts("Good Job.");
else
return puts("Try again.");
}
.text:0804868A push offset aUU ; "%u %u"
.text:0804868F call ___isoc99_scanf
.text:08048694 add esp, 10h
.text:08048697 mov eax, [ebp+var_C] //从这里开始模拟执行
.text:0804869A sub esp, 0Ch
.text:0804869D push eax
.text:0804869E call complex_function0
由于需要往栈里符号化输入,所以需要在启动该程序之前自己构造相应的栈结构。方法是通过state.stack_push(my_bitvector)
来将值my_bitvector push到栈顶。state.regs.esp -= [ByteSize]用于填充ByteSize字节的无用数据。
import angr
import claripy
proj = angr.Project("./04_angr_symbolic_stack")
start_addr = 0x08048697
init_state = proj.factory.blank_state(addr=start_addr)
init_state.regs.ebp = init_state.regs.esp # 这里有点看不懂
value1 = claripy.BVS('x', 32)
value2 = claripy.BVS('y', 32)
init_state.regs.esp -= 8
init_state.stack_push(v1)
init_state.stack_push(v2)
sm = proj.factory.simgr(init_state)
def is_successful(state):
output = state.posix.dumps(1)
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(1)
return 'Try again.' in str(output)
sm.explore(find=is_successful, avoid=should_abort)
find_state = sm.found[0]
s1 = find_state.solver.eval(value1)
s2 = find_state.solver.eval(value2)
s = ' '.join(map(str, [s1,s2])) #s1和s2的顺序?
print(s)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+Ch] [ebp-Ch]
memset(user_input, 0, 0x21u);
printf("Enter the password: ");
__isoc99_scanf("%8s %8s %8s %8s", user_input, &unk_A1BA1C8, &unk_A1BA1D0, &unk_A1BA1D8); //内存空间 bss段
for ( i = 0; i <= 31; ++i )
*(_BYTE *)(i + 0xA1BA1C0) = complex_function(*(char *)(i + 0xA1BA1C0), i);
if ( !strncmp(user_input, "NJPURZPCDYEAXCSJZJMPSOMBFDDLHBVN", 0x20u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
解题思路和上一题是一样的,不过这里使用state.memory.store(address, value)
方法为全局变量代替为符号变量(操作一段连续的内存)。
import angr
import claripy
proj = angr.Project("./05_angr_symbolic_memory")
start_addr = 0x080485FE
init_state = proj.factory.blank_state(addr=start_addr)
v1 = claripy.BVS('a', 8*8)
v2 = claripy.BVS('b', 8*8)
v3 = claripy.BVS('c', 8*8)
v4 = claripy.BVS('d', 8*8)
v_start = 0x0A1BA1C0
init_state.memory.store(v_start, v1)
init_state.memory.store(v_start+8, v2)
init_state.memory.store(v_start+0x10, v3)
init_state.memory.store(v_start+0x18, v4)
def is_successful(state):
output = state.posix.dumps(1)
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(1)
return 'Try again.' in str(output)
sm = proj.factory.simgr(init_state)
sm.explore(find=is_successful, avoid=should_abort)
find_state = sm.found[0]
s0 = find_state.solver.eval(v1,cast_to=bytes)
s1 = find_state.solver.eval(v2,cast_to=bytes)
s2 = find_state.solver.eval(v3,cast_to=bytes)
s3 = find_state.solver.eval(v4,cast_to=bytes)
s = s0 + b' '+ s1 + b' ' + s2 + b' ' + s3
print(s.decode('utf-8'))
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *v3; // ebx
char *v4; // ebx
int v6; // [esp-10h] [ebp-1Ch]
int i; // [esp+0h] [ebp-Ch]
buffer0 = (char *)malloc(9u); //动态的
buffer1 = (char *)malloc(9u);
memset(buffer0, 0, 9u);
memset(buffer1, 0, 9u);
printf("Enter the password: ");
__isoc99_scanf("%8s %8s", buffer0, buffer1, v6);
for ( i = 0; i <= 7; ++i )
{
v3 = &buffer0[i];
*v3 = complex_function(buffer0[i], i);
v4 = &buffer1[i];
*v4 = complex_function(buffer1[i], i + 32);
}
if ( !strncmp(buffer0, "UODXLZBI", 8u) && !strncmp(buffer1, "UAORRAYF", 8u) )
puts("Good Job.");
else
puts("Try again.");
free(buffer0);
free(buffer1);
return 0;
}
由于系统的ASLR保护机制,所以堆地址并不是固定的,因此我们不能直接对堆空间注入符号变量。但我们可以对一个固定的位置注入数据,然后将固定位置的地址赋给buffer0和buffer1,即可。
import angr
import claripy
proj = angr.Project("./06_angr_symbolic_dynamic_memory")
start_addr = 0x08048699
init_state = proj.factory.blank_state(addr=start_addr)
buf1 = 0x0ABCC8A4
buf2 = 0x0ABCC8AC
fake_addr1 = 0xffff0000
fake_addr2 = 0xffff0010
v1 = claripy.BVS('a', 8*8)
v2 = claripy.BVS('b', 8*8)
init_state.memory.store(buf1, fake_addr1, endness=proj.arch.memory_endness)
init_state.memory.store(buf2, fake_addr2, endness=proj.arch.memory_endness)
init_state.memory.store(fake_addr1, v1)
init_state.memory.store(fake_addr2, v2)
def is_successful(state):
output = state.posix.dumps(1)
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(1)
return 'Try again.' in str(output)
sm = proj.factory.simgr(init_state)
sm.explore(find=is_successful, avoid=should_abort)
find_state = sm.found[0]
s0 = find_state.solver.eval(v1,cast_to=bytes)
s1 = find_state.solver.eval(v2,cast_to=bytes)
s = s0 + b' ' + s1
print(s.decode('utf-8'))
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int i; // [esp+Ch] [ebp-Ch]
memset(buffer, 0, sizeof(buffer)); // buffer: [0x0804A0A0]
printf("Enter the password: ");
__isoc99_scanf("%64s", buffer);
ignore_me((int)buffer, 0x40u); // 将buffer的数据写入OJKSQYDP.txt
memset(buffer, 0, sizeof(buffer));
fp = fopen("OJKSQYDP.txt", "rb");
fread(buffer, 1u, 0x40u, fp);
fclose(fp);
unlink("OJKSQYDP.txt");
for ( i = 0; i <= 7; ++i )
*(_BYTE *)(i + 0x804A0A0) = complex_function(*(char *)(i + 0x804A0A0), i);
if ( strncmp(buffer, "AQWLCTXB", 9u) )
{
puts("Try again.");
exit(1);
}
puts("Good Job.");
exit(0);
}
考点:输入为文件(包括网络、另一个程序的输出和/dev/urandom等)时,符号化输入的方法。
方法就是将整个文件都符号化。在angr中,与文件系统、套接字、管道或终端的任何交互的根源都是一个 SimFile 对象。SimFile 是一种存储抽象,它定义一个字节序列,不管是符号的还是其他的。
通过SimFile创建一个有具体内容的文件:
内容为字符串时:
password = angr.storage.SimFile(filename, content='hello world!\n',size=len('hello world!\n'))
内容为符号值时:
symbolic_file_size_bytes = 64
password = claripy.BVS('password', symbolic_file_size_bytes * 8)
password_file = angr.storage.SimFile(filename, content=password, size=symbolic_file_size_bytes)
接着,如果想让 SimFile 对程序可用,我们需要将它放在文件系统中,模拟的文件系统是 state.fs插件。将模拟文件放入文件系统有两种方法:
一是在创建初始状态的同时将模拟文件存储进去:
initial_state = project.factory.blank_state(addr=start_address,fs={filename:password_file})
二是在创建初始状态之后单独使用insert将文件存储到文件系统中,另外还可以使用 get 和 delete 方法从文件系统中加载和删除文件。更多关于SimFile的内容可看官方文档:https://github.com/angr/angr-doc/blob/master/docs/file_system.md。
import angr
import claripy
import sys
p = angr.Project("./07_angr_symbolic_file")
start_addr = 0x080488D3
filename = "OJKSQYDP.txt"
passwd = claripy.BVS('passwd', 0x40*8)
smfile = angr.storage.SimFile(filename, content=passwd, size=0x40)
init_state = p.factory.blank_state(addr=start_addr, fs={filename:smfile})
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.' in str(stdout_output)
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.' in str(stdout_output)
sm = p.factory.simgr(init_state)
sm.explore(find=is_successful, avoid=should_abort)
if sm.found:
solution_state = sm.found[0]
solution = solution_state.solver.eval(passwd, cast_to=bytes)
print(solution.decode('utf-8'))
else:
raise Exception('Could not find the solution')
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+Ch] [ebp-Ch]
qmemcpy(&password, "AUPDNNPROEZRJWKB", 16);
memset(&buffer, 0, 0x11u);
printf("Enter the password: ");
__isoc99_scanf("%16s", &buffer); // 0x804A050 buffer
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(i + 0x804A050) = complex_function(*(char *)(i + 0x804A050), 15 - i);
if ( check_equals_AUPDNNPROEZRJWKB(&buffer, 16) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
//check_equals_AUPDNNPROEZRJWKB函数
_BOOL4 __cdecl check_equals_AUPDNNPROEZRJWKB(int a1, unsigned int a2)
{
int v3; // [esp+8h] [ebp-8h]
unsigned int i; // [esp+Ch] [ebp-4h]
v3 = 0;
for ( i = 0; i < a2; ++i ) //循环
{
if ( *(_BYTE *)(i + a1) == *(_BYTE *)(i + 0x804A040) )
++v3;
}
return v3 == a2;
}
这程序比之前的程序多加了个检查函数check_equals_AUPDNNPROEZRJWKB
,而且里面含有个循环,循环的次数为16,这么说这一段在探索过程路径就有2^16个。我们需要添加约束,替代该函数。(这里没想得太明白,后续再补充😴)后面用到的hook机制,也可以解决这个问题。
import angr
import claripy
import sys
proj = angr.Project('./08_angr_constraints')
state = proj.factory.blank_state(addr=0x08048625)
password = claripy.BVS('password', 16 * 8)
buffer_addr = 0x0804A050
state.memory.store(buffer_addr, password)
simgr = proj.factory.simgr(state)
simgr.explore(find=0x08048565)
found = simgr.found[0]
found.add_constraints(found.memory.load(buffer_addr, 16) == b'AUPDNNPROEZRJWKB')
print(found.solver.eval(password, cast_to=bytes))
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BOOL4 v3; // eax
int i; // [esp+8h] [ebp-10h]
int j; // [esp+Ch] [ebp-Ch]
qmemcpy(password, "XYMKBKUHNIQYNQXE", 16);
memset(buffer, 0, 0x11u);
printf("Enter the password: ");
__isoc99_scanf("%16s", buffer);
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(i + 0x804A054) = complex_function(*(char *)(i + 0x804A054), 18 - i);
equals = check_equals_XYMKBKUHNIQYNQXE((int)buffer, 16u);// 防止路径爆炸,使用hook进行替代
for ( j = 0; j <= 15; ++j )
*(_BYTE *)(j + 0x804A044) = complex_function(*(char *)(j + 0x804A044), j + 9);
__isoc99_scanf("%16s", buffer);
v3 = equals && !strncmp(buffer, password, 0x10u);
equals = v3;
if ( v3 )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
为了防止执行函数check_equals_XYMKBKUHNIQYNQXE
时路径爆炸,可以自己编写个函数来代替该函数去实现同样的功能。
import angr
import sys
import claripy
proj = angr.Project("./09_angr_hooks", auto_load_libs=False)
init_state = proj.factory.entry_state()
hooked_addr = 0x080486B3
hooked_len = 5
# hooked_addr:代替的地址, hooked_len:代替字节长度
@proj.hook(hooked_addr, length=hooked_len) #修饰器
def skip_check_equals(state):
buffer_addr = 0x0804A054
buffer_len = 16
user_input = state.memory.load(buffer_addr, buffer_len)
check_against_string = "XYMKBKUHNIQYNQXE"
state.regs.eax = claripy.If(user_input == check_against_string,
claripy.BVV(1,32), claripy.BVV(0,32))
def is_successful(state):
output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.' in str(output)
sm = proj.factory.simgr(init_state)
sm.explore(find=is_successful, avoid=should_abort)
if sm.found:
for i in sm.found:
solution_state = i
solution = solution_state.posix.dumps(0)
print("[+] Success! Solution is: {0}".format(solution.decode('utf-8')))
else:
raise Exception('Could not find the solution')
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+20h] [ebp-28h]
char s[17]; // [esp+2Bh] [ebp-1Dh] BYREF
unsigned int v6; // [esp+3Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
memcpy(&password, "ORSDDWXHZURJRBDH", 0x10u);
memset(s, 0, sizeof(s));
printf("Enter the password: ");
__isoc99_scanf("%16s", s);
for ( i = 0; i <= 15; ++i )
s[i] = complex_function(s[i], 18 - i);
if ( check_equals_ORSDDWXHZURJRBDH((int)s, 0x10u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
上个程序中参数的地址是固定的,这里的s是个局部变量。可以通过函数名进行hook,这样子函数的参数可以直接引用,从而解决这个问题。
import angr
import sys
import claripy
proj = angr.Project("./10_angr_simprocedures", auto_load_libs=False)
init_state = proj.factory.entry_state()
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, to_check, length):
s_addr = to_check
s_len = length
user_input = self.state.memory.load(s_addr, s_len)
check_string = "ORSDDWXHZURJRBDH"
return claripy.If(user_input == check_string, claripy.BVV(1,32), claripy.BVV(0,32))
check_symbol = "check_equals_ORSDDWXHZURJRBDH"
proj.hook_symbol(check_symbol, ReplacementCheckEquals())
def is_successful(state):
output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.' in str(output)
def should_abort(state):
output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.' in str(output)
sm = proj.factory.simgr(init_state)
sm.explore(find=is_successful, avoid=should_abort)
if sm.found:
for i in sm.found:
solution_state = i
solution = solution_state.posix.dumps(0)
print("[+] Success! Solution is: {0}".format(solution.decode('utf-8')))
else:
raise Exception('Could not find the solution')
链接:https://pan.baidu.com/s/10_7MkZUdAAxS5AzXcBgZVw
提取码:lele
查看文件信息:
tolele@tolele-ubuntu22:~/angr_study/liti $ checksec r100
[*] '/home/tolele/angr_study/liti/r100'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
tolele@tolele-ubuntu22:~/angr_study/liti $
IDA分析:
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+108h] [rbp-8h]
v5 = __readfsqword(0x28u);
printf("Enter the password: ");
if ( !fgets(s, 255, stdin) )
return 0LL;
if ( (unsigned int)sub_4006FD((__int64)s) )
{
puts("Incorrect password!");
return 1LL;
}
else
{
puts("Nice!"); //目标
return 0LL;
}
}
__int64 __fastcall sub_4006FD(__int64 a1)
{
int i; // [rsp+14h] [rbp-24h]
__int64 v3[4]; // [rsp+18h] [rbp-20h]
v3[0] = (__int64)"Dufhbmf";
v3[1] = (__int64)"pG`imos";
v3[2] = (__int64)"ewUglpt";
for ( i = 0; i <= 11; ++i )
{
if ( *(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) != 1 )
return 1LL;
}
return 0LL;
}
// 目标
.text:0000000000400840 test eax, eax
.text:0000000000400842 jnz short loc_400855
.text:0000000000400844 mov edi, offset s ; "Nice!"
.text:0000000000400849 call _puts
简单的方法:
import angr
import sys
proj = angr.Project("./r100")
sm = proj.factory.simgr()
sm.explore(find=0x400844)
final_state = sm.found[0]
print(final_state.posix.dumps(sys.stdin.fileno()))
采用hook机制:
import angr
class my_fgets(angr.SimProcedure):
def run(self, s, num, f):
simfd = self.state.posix.get_fd(0)
data, real_size = simfd.read_data(12)
self.state.memory.store(s, data)
return 12
def main():
p = angr.Project("./r100", auto_load_libs=False)
p.hook_symbol('printf', angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](), replace=True)
p.hook_symbol('fgets', my_fgets(), replace=True)
state = p.factory.blank_state(addr=0x4007E8)
sm = p.factory.simulation_manager(state)
sm.explore(find=0x400844, avoid=0x400855)
return sm.found[0].posix.dumps(0).strip(b'\0\n')
if __name__ == "__main__":
print(main())
还有很多练习和相关的题目,有时间再继续补充。不过得先对angr有更进一步的了解才行~
↶ 返回首页