汇编代码:
.text:08048621 push 9 ; size
.text:08048623 call _malloc
.text:08048628 add esp, 10h
.text:0804862B mov ds:buffer0, eax
.text:08048630 sub esp, 0Ch
.text:08048633 push 9 ; size
.text:08048635 call _malloc
.text:0804863A add esp, 10h
.text:0804863D mov ds:buffer1, eax
.text:08048642 mov eax, ds:buffer0
.text:08048647 sub esp, 4
.text:0804864A push 9 ; n
.text:0804864C push 0 ; c
.text:0804864E push eax ; s
.text:0804864F call _memset
.text:08048654 add esp, 10h
.text:08048657 mov eax, ds:buffer1
.text:0804865C sub esp, 4
.text:0804865F push 9 ; n
.text:08048661 push 0 ; c
.text:08048663 push eax ; s
.text:08048664 call _memset
.text:08048669 add esp, 10h
.text:0804866C sub esp, 0Ch
.text:0804866F push offset aEnterThePasswo ; "Enter the password: "
.text:08048674 call _printf
.text:08048679 add esp, 10h
.text:0804867C mov edx, ds:buffer1
.text:08048682 mov eax, ds:buffer0
.text:08048687 sub esp, 4
.text:0804868A push edx
.text:0804868B push eax
.text:0804868C push offset a8s8s ; "%8s %8s"
.text:08048691 call ___isoc99_scanf
这次scanf() 有两个输入参数,数据保存的位置是通过全局变量的char* 指针来保存到buffer 中,大小为8 字节.
.text:08048699 mov [ebp+var_C], 0
.text:080486A0 jmp short loc_8048706
.text:080486A2 ; ---------------------------------------------------------------------------
.text:080486A2
.text:080486A2 loc_80486A2: ; CODE XREF: main+FE↓j
.text:080486A2 mov edx, ds:buffer0
.text:080486A8 mov eax, [ebp+var_C]
.text:080486AB lea ebx, [edx+eax]
.text:080486AE mov edx, ds:buffer0
.text:080486B4 mov eax, [ebp+var_C]
.text:080486B7 add eax, edx
.text:080486B9 movzx eax, byte ptr [eax]
.text:080486BC movsx eax, al
.text:080486BF sub esp, 8
.text:080486C2 push [ebp+var_C]
.text:080486C5 push eax
.text:080486C6 call complex_function
.text:080486CB add esp, 10h
.text:080486CE mov [ebx], al
.text:080486D0 mov edx, ds:buffer1
.text:080486D6 mov eax, [ebp+var_C]
.text:080486D9 lea ebx, [edx+eax]
.text:080486DC mov eax, [ebp+var_C]
.text:080486DF lea edx, [eax+20h]
.text:080486E2 mov ecx, ds:buffer1
.text:080486E8 mov eax, [ebp+var_C]
.text:080486EB add eax, ecx
.text:080486ED movzx eax, byte ptr [eax]
.text:080486F0 movsx eax, al
.text:080486F3 sub esp, 8
.text:080486F6 push edx
.text:080486F7 push eax
.text:080486F8 call complex_function
.text:080486FD add esp, 10h
.text:08048700 mov [ebx], al
.text:08048702 add [ebp+var_C], 1
.text:08048706
.text:08048706 loc_8048706: ; CODE XREF: main+94↑j
.text:08048706 cmp [ebp+var_C], 7
.text:0804870A jle short loc_80486A2
接下来就到了complex_function 运算的地方了,我们主要是对buffer0 和buffer1 指向的内存做求解.
.bss:09FD92AC buffer0 dd ? ; DATA XREF: main+1F↑w
.bss:09FD92AC ; main+36↑r ...
.bss:09FD92B0 public buffer3
.bss:09FD92B0 buffer3 db ? ;
.bss:09FD92B1 db ? ;
.bss:09FD92B2 db ? ;
.bss:09FD92B3 db ? ;
所以,我们在利用 initial_state.memory.store() 构造内存时,还需要在里面填入指向保存数据的地址,Angr可以不用创建新内存(malloc),直接指向内存中一个任意位置即可,所以我们写0x4444440 和0x44444450 到buffer0 和buffer1 的内存中.
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)start_address = 0x8048699
initial_state = project.factory.blank_state(addr=start_address)password0 = claripy.BVS('password0', 4 * 8)
password1 = claripy.BVS('password1', 4 * 8)fake_heap_address0 = 0x4444440
pointer_to_malloc_memory_address0 = 0x9FD92AC
initial_state.memory.store(pointer_to_malloc_memory_address0,fake_heap_address0, endness=project.arch.memory_endness)
fake_heap_address1 = 0x4444450
pointer_to_malloc_memory_address1 = 0x9FD92B0
initial_state.memory.store(pointer_to_malloc_memory_address1,fake_heap_address1, endness=project.arch.memory_endness)initial_state.memory.store(fake_heap_address0, password0)
initial_state.memory.store(fake_heap_address1, password1)simulation = project.factory.simgr(initial_state)
simulation.explore(find=0x8048759, avoid=0x8048747)
if simulation.found:
solution_state = simulation.found[0]solution0 = solution_state.se.eval(password0)
solution1 = solution_state.se.eval(password1)solution = ' '.join(map('{:x}'.format, [ solution0, solution1 ]))
print(solution)
Angr函数使用总结:
initial_state.memory.store(地址,数据,endness = 数据字节顺序) => 设置初始化内存数据
project.arch.memory_endness => 指的是内存字节顺序
汇编代码:
.text:0804888E push 40h ; n
.text:08048890 push 0 ; c
.text:08048892 push offset buffer ; s
.text:08048897 call _memset
.text:0804889C add esp, 10h
.text:0804889F sub esp, 0Ch
.text:080488A2 push offset aEnterThePasswo ; "Enter the password: "
.text:080488A7 call _printf
.text:080488AC add esp, 10h
.text:080488AF sub esp, 8
.text:080488B2 push offset buffer
.text:080488B7 push offset a64s ; "%64s"
.text:080488BC call ___isoc99_scanf
.text:080488C1 add esp, 10h
.text:080488C4 sub esp, 8
.text:080488C7 push 40h ; n
.text:080488C9 push offset buffer ; int
.text:080488CE call ignore_me
.text:080488D3 add esp, 10h
.text:080488D6 sub esp, 4
.text:080488D9 push 40h ; n
.text:080488DB push 0 ; c
.text:080488DD push offset buffer ; s
.text:080488E2 call _memset
.text:080488E7 add esp, 10h
.text:080488EA sub esp, 8
.text:080488ED push offset aRb ; "rb"
.text:080488F2 push offset name ; "MRXJKZYR.txt"
.text:080488F7 call _fopen
.text:080488FC add esp, 10h
.text:080488FF mov ds:fp, eax
.text:08048904 mov eax, ds:fp
.text:08048909 push eax ; stream
.text:0804890A push 40h ; n
.text:0804890C push 1 ; size
.text:0804890E push offset buffer ; ptr
.text:08048913 call _fread
.text:08048918 add esp, 10h
.text:0804891B mov eax, ds:fp
.text:08048920 sub esp, 0Ch
.text:08048923 push eax ; stream
.text:08048924 call _fclose
.text:08048929 add esp, 10h
.text:0804892C sub esp, 0Ch
.text:0804892F push offset name ; "MRXJKZYR.txt"
.text:08048934 call _unlink
程序逻辑是用户输入一串Key之后,经过计算保存到文件里(在ignore_me() 里面实现),然后通过fread() 读取文件获取数据.Angr库中有一个模拟的文件系统,我们可以通过这个文件系统来模拟fread() 出来的数据,继续往下看汇编
.text:0804893C mov [ebp+var_C], 0
.text:08048943 jmp short loc_8048972
.text:08048945 ; ---------------------------------------------------------------------------
.text:08048945
.text:08048945 loc_8048945: ; CODE XREF: main+FC↓j
.text:08048945 mov eax, [ebp+var_C]
.text:08048948 add eax, 804A0A0h
.text:0804894D movzx eax, byte ptr [eax] ; 生成数据
.text:08048950 movsx eax, al
.text:08048953 sub esp, 8
.text:08048956 push [ebp+var_C]
.text:08048959 push eax
.text:0804895A call complex_function
.text:0804895F add esp, 10h
.text:08048962 mov edx, eax
.text:08048964 mov eax, [ebp+var_C]
.text:08048967 add eax, 804A0A0h
.text:0804896C mov [eax], dl
.text:0804896E add [ebp+var_C], 1
.text:08048972
.text:08048972 loc_8048972: ; CODE XREF: main+C9↑j
.text:08048972 cmp [ebp+var_C], 7
.text:08048976 jle short loc_8048945
.text:08048978 sub esp, 4
.text:0804897B push 9 ; n
.text:0804897D push offset s2 ; "UKNRZDIR"
.text:08048982 push offset buffer ; s1
.text:08048987 call _strncmp
.text:0804898C add esp, 10h
.text:0804898F test eax, eax
.text:08048991 jz short loc_80489AD ; 校验用户输入和生成数据
.text:08048993 sub esp, 0Ch
.text:08048996 push offset s ; "Try again."
.text:0804899B call _puts
.text:080489A0 add esp, 10h
.text:080489A3 sub esp, 0Ch
.text:080489A6 push 1 ; status
.text:080489A8 call _exit
.text:080489AD ; ---------------------------------------------------------------------------
.text:080489AD
.text:080489AD loc_80489AD: ; CODE XREF: main+117↑j
.text:080489AD sub esp, 0Ch
.text:080489B0 push offset aGoodJob ; "Good Job."
.text:080489B5 call _puts
.text:080489BA add esp, 10h
.text:080489BD sub esp, 0Ch
.text:080489C0 push 0 ; status
.text:080489C2 call _exit
这段代码并没有什么特别之处,只是简单地对进行数据生成和校验然后输出判断结果.那么现在我们使用Angr的文件系统来进行求解
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)start_address = 0x80488D6
initial_state = project.factory.blank_state(addr=start_address)filename = 'MRXJKZYR.txt' # :string
symbolic_file_size_bytes = 0x40password = claripy.BVS('password', symbolic_file_size_bytes * 8)
password_file = angr.storage.SimFile(filename, password, size=symbolic_file_size_bytes) # 模拟读文件,默认的文件内容是password,文件大小是symbolic_file_size_bytes
symbolic_filesystem = {
filename : password_file
}
initial_state.posix.fs = symbolic_filesystem # 构建状态上下文里的文件系统数据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.se.eval(password)#,cast_to=str)
print(solution)
我们也可以继续用旧的内存跟踪的方法来做,示例代码如下
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)start_address = 0x804893C
initial_state = project.factory.blank_state(addr=start_address)password = claripy.BVS('password', 0x40 * 8)
initial_state.memory.store(0x804A0A0, password)
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.se.eval(password)
print(solution)
注意事项:
在Angr-CTF 的解题Python 文件中,我们直接执行程序可能会遇到下面这个结果
Traceback (most recent call last):
File "solve07.py", line 130, in
main(sys.argv)
File "solve07.py", line 91, in main
password_file = angr.storage.SimFile(filename, file_options, content=symbolic_file_backing_memory, size=symbolic_file_size_bytes)
TypeError: init() got multiple values for argument 'content'这是因为Angr-CTF 是使用旧版的Angr 库来实现的,现在SimFile() 函数的声明已经被修改如下
SimFile
(name, content=None**,** size=None**,** has_end=None**,** seekable=True**,** writable=True**,** ident=None**,** concrete=None**,** kwargs)也就是说,SimFile 的第二个参数不再是file_options 了,所以我们可以看到SimFile() 提示content 参数被传递了两个参数;而且新版的SimFile() 函数的content 参数只接受字符串或者BitVector ,所以用solution 中的解题脚本会出问题是因为它传递的是内存对象而不是BitVector ,于是代码修改如下
password_file = angr.storage.SimFile(filename, password, size=symbolic_file_size_bytes)
Angr函数使用总结:
angr.storage.SimFile(文件名,文件内容, size = 文件大小) => 创建一个模拟文件,当有被执行的程序fopen 打开文件时,我们可以控制其里面的内容
initial_state.posix.fs => 状态上下文的文件系统对象
汇编代码:
.text:080485C4 mov ds:password, 4A58524Dh ; 这个是全局变量:0x804A040
.text:080485CE mov ds:dword_804A044, 52595A4Bh
.text:080485D8 mov ds:dword_804A048, 454B4D4Bh
.text:080485E2 mov ds:dword_804A04C, 425A464Eh
.text:080485EC sub esp, 4
.text:080485EF push 11h ; n
.text:080485F1 push 0 ; c
.text:080485F3 push offset buffer ; s
.text:080485F8 call _memset
.text:080485FD add esp, 10h
.text:08048600 sub esp, 0Ch
.text:08048603 push offset aEnterThePasswo ; "Enter the password: "
.text:08048608 call _printf
.text:0804860D add esp, 10h
.text:08048610 sub esp, 8
.text:08048613 push offset buffer
.text:08048618 push offset a16s ; "%16s"
.text:0804861D call ___isoc99_scanf ; 用户输入,这个也是全局变量:0x804A050
.text:08048622 add esp, 10h
.text:08048625 mov [ebp+var_C], 0
.text:0804862C jmp short loc_8048663
.text:0804862E ; ---------------------------------------------------------------------------
.text:0804862E
.text:0804862E loc_804862E: ; CODE XREF: main+B4↓j
.text:0804862E mov eax, 0Fh
.text:08048633 sub eax, [ebp+var_C]
.text:08048636 mov edx, eax
.text:08048638 mov eax, [ebp+var_C]
.text:0804863B add eax, 804A050h
.text:08048640 movzx eax, byte ptr [eax]
.text:08048643 movsx eax, al
.text:08048646 sub esp, 8
.text:08048649 push edx
.text:0804864A push eax
.text:0804864B call complex_function ; 对用户输入进行计算
.text:08048650 add esp, 10h
.text:08048653 mov edx, eax
.text:08048655 mov eax, [ebp+var_C]
.text:08048658 add eax, 804A050h
.text:0804865D mov [eax], dl
.text:0804865F add [ebp+var_C], 1
.text:08048663
.text:08048663 loc_8048663: ; CODE XREF: main+79↑j
.text:08048663 cmp [ebp+var_C], 0Fh
.text:08048667 jle short loc_804862E
.text:08048669 sub esp, 8
.text:0804866C push 10h
.text:0804866E push offset buffer
.text:08048673 call check_equals_MRXJKZYRKMKENFZB ; check_equals() 函数是把buffer 和password 来对比
.text:08048678 add esp, 10h
.text:0804867B test eax, eax
.text:0804867D jnz short loc_8048691 ; 对比校验结果
.text:0804867F sub esp, 0Ch
.text:08048682 push offset s ; "Try again."
.text:08048687 call _puts
.text:0804868C add esp, 10h
.text:0804868F jmp short loc_80486A1
.text:08048691 ; ---------------------------------------------------------------------------
.text:08048691
.text:08048691 loc_8048691: ; CODE XREF: main+CA↑j
.text:08048691 sub esp, 0Ch
.text:08048694 push offset aGoodJob ; "Good Job."
.text:08048699 call _puts
.text:0804869E add esp, 10h
.text:080486A1
主要的思路是把complex_function() 计算的结果和字符串MRXJKZYRKMKENFZB
来做对比,我们假定complex_function() 的输入是未知的,check_equals() 函数中对比的内容是已知的,那么我们的关注点就在于对输入进行求解.
首先第一步,我们需要在complex_function() 循环计算之后(地址0x804866C)就可以得到buffer 的符号执行内容,接下来我们需要根据buffer 的内容和对比的字符串MRXJKZYRKMKENFZB
来计算是否有满足的解.
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)start_address = 0x8048625 # scanf() 之后的位置
initial_state = project.factory.blank_state(addr=start_address)buffer = claripy.BVS('buffer', 0x10* 8) # buffer的大小是0x10 字节,也就是0x10 * 8 比特
buffer_address = 0x804A050
initial_state.memory.store(buffer_address, buffer) # buffer 是全局变量,地址是0x804A050simulation = project.factory.simgr(initial_state)
address_to_check_constraint = 0x804866C
simulation.explore(find=address_to_check_constraint) # compilex_function() 计算结束位置if simulation.found:
solution_state = simulation.found[0]constrained_parameter_address = 0x804A050 # 计算约束的内存位置
constrained_parameter_size_bytes = 0x10 # 计算约束的内存大小
constrained_parameter_bitvector = solution_state.memory.load( # 加载内存
constrained_parameter_address,
constrained_parameter_size_bytes
)constrained_parameter_desired_value = 'MRXJKZYRKMKENFZB' # Key-String
solution_state.add_constraints(constrained_parameter_bitvector == constrained_parameter_desired_value) # 添加约束条件使用求解器求解内存中的bitvector 和Key-String 相等时是否有满足的解
solution = solution_state.se.eval(buffer)
print(solution)
Angr函数使用总结:
solution_state.memory.load(内存地址,内存大小) => 加载内存
solution_state.add_constraints(约束条件) => 添加约束条件
汇编代码:
.text:08048604 mov ds:password, 4A58524Dh
.text:0804860E mov ds:dword_804A048, 52595A4Bh
.text:08048618 mov ds:dword_804A04C, 454B4D4Bh
.text:08048622 mov ds:dword_804A050, 425A464Eh ; Key-String
.text:0804862C sub esp, 4
.text:0804862F push 11h ; n
.text:08048631 push 0 ; c
.text:08048633 push offset buffer ; s
.text:08048638 call _memset
.text:0804863D add esp, 10h
.text:08048640 sub esp, 0Ch
.text:08048643 push offset aEnterThePasswo ; "Enter the password: "
.text:08048648 call _printf
.text:0804864D add esp, 10h
.text:08048650 sub esp, 8
.text:08048653 push offset buffer
.text:08048658 push offset a16s ; "%16s"
.text:0804865D call ___isoc99_scanf ; 用户输入Key
.text:08048662 add esp, 10h
.text:08048665 mov [ebp+var_10], 0
.text:0804866C jmp short loc_80486A3
.text:0804866E ; ---------------------------------------------------------------------------
.text:0804866E
.text:0804866E loc_804866E: ; CODE XREF: main+B4↓j
.text:0804866E mov eax, 12h
.text:08048673 sub eax, [ebp+var_10]
.text:08048676 mov edx, eax
.text:08048678 mov eax, [ebp+var_10]
.text:0804867B add eax, 804A054h ; 这个地址是用户输入buffer
.text:08048680 movzx eax, byte ptr [eax]
.text:08048683 movsx eax, al
.text:08048686 sub esp, 8
.text:08048689 push edx
.text:0804868A push eax
.text:0804868B call complex_function ; 对用户输入进行计算
.text:08048690 add esp, 10h
.text:08048693 mov edx, eax
.text:08048695 mov eax, [ebp+var_10]
.text:08048698 add eax, 804A054h
.text:0804869D mov [eax], dl
.text:0804869F add [ebp+var_10], 1
.text:080486A3
.text:080486A3 loc_80486A3: ; CODE XREF: main+79↑j
.text:080486A3 cmp [ebp+var_10], 0Fh
.text:080486A7 jle short loc_804866E ; 这是一个循环complex_function() 计算
.text:080486A9 sub esp, 8
.text:080486AC push 10h
.text:080486AE push offset buffer
.text:080486B3 call check_equals_MRXJKZYRKMKENFZB ; check_equals() 对比结果
.text:080486B8 add esp, 10h
.text:080486BB mov ds:equals, eax ; check_equals 的结果保存在全局变量equals里
.text:080486C0 mov [ebp+var_C], 0
.text:080486C7 jmp short loc_80486FA
.text:080486C9 ; ---------------------------------------------------------------------------
.text:080486C9
.text:080486C9 loc_80486C9: ; CODE XREF: main+10B↓j
.text:080486C9 mov eax, [ebp+var_C]
.text:080486CC lea edx, [eax+9]
.text:080486CF mov eax, [ebp+var_C]
.text:080486D2 add eax, 804A044h ; 这个是Key-String
.text:080486D7 movzx eax, byte ptr [eax]
.text:080486DA movsx eax, al
.text:080486DD sub esp, 8
.text:080486E0 push edx
.text:080486E1 push eax
.text:080486E2 call complex_function ; 这次是对Key-String 进行计算了
.text:080486E7 add esp, 10h
.text:080486EA mov edx, eax
.text:080486EC mov eax, [ebp+var_C]
.text:080486EF add eax, 804A044h
.text:080486F4 mov [eax], dl
.text:080486F6 add [ebp+var_C], 1
.text:080486FA
.text:080486FA loc_80486FA: ; CODE XREF: main+D4↑j
.text:080486FA cmp [ebp+var_C], 0Fh
.text:080486FE jle short loc_80486C9 ; comp
.text:08048700 sub esp, 8
.text:08048703 push offset buffer
.text:08048708 push offset a16s ; "%16s"
.text:0804870D call ___isoc99_scanf
.text:08048712 add esp, 10h
.text:08048715 mov eax, ds:equals
.text:0804871A test eax, eax
.text:0804871C jz short loc_8048740 ; 对比第二次用户输入
.text:0804871E sub esp, 4
.text:08048721 push 10h ; n
.text:08048723 push offset password ; s2
.text:08048728 push offset buffer ; s1
.text:0804872D call _strncmp
.text:08048732 add esp, 10h
.text:08048735 test eax, eax
.text:08048737 jnz short loc_8048740
.text:08048739 mov eax, 1
.text:0804873E jmp short loc_8048745
.text:08048740 ; ---------------------------------------------------------------------------
.text:08048740
.text:08048740 loc_8048740: ; CODE XREF: main+129↑j
.text:08048740 ; main+144↑j
.text:08048740 mov eax, 0
.text:08048745
.text:08048745 loc_8048745: ; CODE XREF: main+14B↑j
.text:08048745 mov ds:equals, eax
.text:0804874A mov eax, ds:equals
.text:0804874F test eax, eax
.text:08048751 jnz short loc_8048765
.text:08048753 sub esp, 0Ch
.text:08048756 push offset s ; "Try again."
.text:0804875B call _puts
.text:08048760 add esp, 10h
.text:08048763 jmp short loc_8048775
.text:08048765 ; ---------------------------------------------------------------------------
.text:08048765
.text:08048765 loc_8048765: ; CODE XREF: main+15E↑j
.text:08048765 sub esp, 0Ch
.text:08048768 push offset aGoodJob ; "Good Job."
.text:0804876D call _puts
.text:08048772 add esp, 10h
由上面的代码我们可以知道基本的逻辑,第一部分是对用户输入进行complex_function() 计算,然后把计算结果传给check_equals() 检查对比;第二部分是把Key-String 传递给complex_function() 计算,再通过第二次用户输入来进行结果对比.
我们来看一下check_equals() 函数的代码,check_equals() 函数主要功能是对比complex_function() 函数计算结果和Key-String 进行对比,相等则返回1 ,不相等返回0 .
.text:080485A5 check_equals_MRXJKZYRKMKENFZB proc near ; CODE XREF: main+C0↓p
.text:080485A5
.text:080485A5 var_8 = dword ptr -8
.text:080485A5 var_4 = dword ptr -4
.text:080485A5 arg_0 = dword ptr 8
.text:080485A5 arg_4 = dword ptr 0Ch
.text:080485A5
.text:080485A5 ; __unwind {
.text:080485A5 push ebp
.text:080485A6 mov ebp, esp
.text:080485A8 sub esp, 10h
.text:080485AB mov [ebp+var_8], 0
.text:080485B2 mov [ebp+var_4], 0
.text:080485B9 jmp short loc_80485DD
.text:080485BB ; ---------------------------------------------------------------------------
.text:080485BB
.text:080485BB loc_80485BB: ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+3E↓j
.text:080485BB mov edx, [ebp+var_4]
.text:080485BE mov eax, [ebp+arg_0]
.text:080485C1 add eax, edx
.text:080485C3 movzx edx, byte ptr [eax]
.text:080485C6 mov eax, [ebp+var_4]
.text:080485C9 add eax, 804A044h ; Key-String ..
.text:080485CE movzx eax, byte ptr [eax]
.text:080485D1 cmp dl, al
.text:080485D3 jnz short loc_80485D9
.text:080485D5 add [ebp+var_8], 1
.text:080485D9
.text:080485D9 loc_80485D9: ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+2E↑j
.text:080485D9 add [ebp+var_4], 1
.text:080485DD
.text:080485DD loc_80485DD: ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+14↑j
.text:080485DD mov eax, [ebp+var_4]
.text:080485E0 cmp eax, [ebp+arg_4]
.text:080485E3 jb short loc_80485BB
.text:080485E5 mov eax, [ebp+var_8]
.text:080485E8 cmp eax, [ebp+arg_4]
.text:080485EB setz al
.text:080485EE movzx eax, al
.text:080485F1 leave
.text:080485F2 retn
.text:080485F2 ; } // starts at 80485A5
在这一题里,使用的方法是Angr Hook .那么我们需要设计一个Hook check_equals() 函数来模拟它的功能.Hook 的插入位置在.text:080486B3 call check_equals_MRXJKZYRKMKENFZB
.
根据这些信息,构造的solver.py 代码如下:
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)initial_state = project.factory.entry_state()
check_equals_called_address = 0x80486B3 # Hook Call Address
instruction_to_skip_length = 0x5 # Call instruction length@project.hook(check_equals_called_address, length=instruction_to_skip_length)
def skip_check_equals_(state):
user_input_buffer_address = 0x804A054 # The input buffer address
user_input_buffer_length = 0x10 # input buffer lengthuser_input_string = state.memory.load( # load this buffer to check
user_input_buffer_address,
user_input_buffer_length
)check_against_string = 'MRXJKZYRKMKENFZB' # :string
state.regs.eax = claripy.If( # Add a constraint .
user_input_string == check_against_string, # Check condition
claripy.BVV(1, 32), # The condition is True than return a int value 1
claripy.BVV(0, 32) # The condition is False than return a int value 0
)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) # Try Explore Execute Path
if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno()) # Get data ..print(solution)
Angr函数使用总结:
Hook回调函数格式:
@project.hook(Hook地址,执行完Hook函数后指令往后跳转n字节)
def skip_check_equals_(state):pass
claripy.If(条件,条件为True时的返回值,条件为False时的返回值) => 创建条件判断
claripy.BVV(值,值大小) => 创建一个数值
汇编代码:
.text:08048678 push 10h ; n
.text:0804867A push offset aMrxjkzyrkmkenf ; "MRXJKZYRKMKENFZB"
.text:0804867F push offset password ; dest
.text:08048684 call _memcpy ; Key-String = MRXJKZYRKMKENFZB
.text:08048689 add esp, 10h
.text:0804868C sub esp, 4
.text:0804868F push 11h ; n
.text:08048691 push 0 ; c
.text:08048693 lea eax, [ebp+s]
.text:08048696 push eax ; s
.text:08048697 call _memset ; 注意这次buffer 是在栈上的
.text:0804869C add esp, 10h
.text:0804869F sub esp, 0Ch
.text:080486A2 push offset aEnterThePasswo ; "Enter the password: "
.text:080486A7 call _printf
.text:080486AC add esp, 10h
.text:080486AF sub esp, 8
.text:080486B2 lea eax, [ebp+s]
.text:080486B5 push eax
.text:080486B6 push offset a16s ; "%16s"
.text:080486BB call ___isoc99_scanf ; 用户输入16 字节大小的内容
.text:080486C0 add esp, 10h
.text:080486C3 mov [ebp+var_28], 0
.text:080486CA jmp short loc_8048701
.text:080486CC ; ---------------------------------------------------------------------------
.text:080486CC
.text:080486CC loc_80486CC: ; CODE XREF: main+C2↓j
.text:080486CC mov eax, 12h
.text:080486D1 sub eax, [ebp+var_28]
.text:080486D4 mov edx, eax
.text:080486D6 lea ecx, [ebp+s]
.text:080486D9 mov eax, [ebp+var_28]
.text:080486DC add eax, ecx
.text:080486DE movzx eax, byte ptr [eax]
.text:080486E1 movsx eax, al
.text:080486E4 sub esp, 8
.text:080486E7 push edx
.text:080486E8 push eax
.text:080486E9 call complex_function ; 这是复杂的complex_function() 计算操作
.text:080486EE add esp, 10h
.text:080486F1 mov ecx, eax
.text:080486F3 lea edx, [ebp+s]
.text:080486F6 mov eax, [ebp+var_28]
.text:080486F9 add eax, edx
.text:080486FB mov [eax], cl
.text:080486FD add [ebp+var_28], 1
.text:08048701
.text:08048701 loc_8048701: ; CODE XREF: main+87↑j
.text:08048701 cmp [ebp+var_28], 0Fh
.text:08048705 jle short loc_80486CC
.text:08048707 cmp [ebp+var_24], 0DEADBEEFh
.text:0804870E jz loc_8049847
.text:08048714 cmp [ebp+var_24], 0DEADBEEFh
.text:0804871B jnz loc_8048FB4
.text:08048721 cmp [ebp+var_24], 0DEADBEEFh
.text:08048728 jnz loc_8048B71
.text:0804872E cmp [ebp+var_24], 0DEADBEEFh
.text:08048735 jnz loc_8048956
.text:0804873B cmp [ebp+var_24], 0DEADBEEFh
.text:08048742 jz loc_804884F
.text:08048748 cmp [ebp+var_24], 0DEADBEEFh
.text:0804874F jz short loc_80487D0
.text:08048751 cmp [ebp+var_24], 0DEADBEEFh
.text:08048758 jnz short loc_8048795
.text:0804875A cmp [ebp+var_24], 0DEADBEEFh
.text:08048761 jz short loc_804877C
代码的第一部分对用户输入做了一系列复杂的计算操作之后,然后就分别跳转到不同的位置,最后执行check_equals() 函数进行数据校验.
.text:08048758 jnz short loc_8048795
.text:0804875A cmp [ebp+var_24], 0DEADBEEFh
.text:08048761 jz short loc_804877C
.text:08048763 sub esp, 8
.text:08048766 push 10h
.text:08048768 lea eax, [ebp+s]
.text:0804876B push eax
.text:0804876C call check_equals_MRXJKZYRKMKENFZB
.text:08048771 add esp, 10h
.text:08048774 mov [ebp+var_2C], eax
.text:08048777 jmp loc_804A969
.text:0804877C ; ---------------------------------------------------------------------------
.text:0804877C
.text:0804877C loc_804877C: ; CODE XREF: main+11E↑j
.text:0804877C sub esp, 8
.text:0804877F push 10h
.text:08048781 lea eax, [ebp+s]
.text:08048784 push eax
.text:08048785 call check_equals_MRXJKZYRKMKENFZB
.text:0804878A add esp, 10h
.text:0804878D mov [ebp+var_2C], eax
.text:08048790 jmp loc_804A969
.text:08048795 ; ---------------------------------------------------------------------------
.text:08048795
.text:08048795 loc_8048795: ; CODE XREF: main+115↑j
.text:08048795 cmp [ebp+var_24], 0DEADBEEFh
.text:0804879C jz short loc_80487B7
.text:0804879E sub esp, 8
.text:080487A1 push 10h
.text:080487A3 lea eax, [ebp+s]
.text:080487A6 push eax
.text:080487A7 call check_equals_MRXJKZYRKMKENFZB
.text:080487AC add esp, 10h
.text:080487AF mov [ebp+var_2C], eax
.text:080487B2 jmp loc_804A969
.text:080487B7 ; ---------------------------------------------------------------------------
.text:080487B7
.text:080487B7 loc_80487B7: ; CODE XREF: main+159↑j
.text:080487B7 sub esp, 8
.text:080487BA push 10h
.text:080487BC lea eax, [ebp+s]
.text:080487BF push eax
.text:080487C0 call check_equals_MRXJKZYRKMKENFZB
.text:080487C5 add esp, 10h
.text:080487C8 mov [ebp+var_2C], eax
.text:080487CB jmp loc_804A969
那么我们来看看check_equals() 的代码:
.text:080485F5 check_equals_MRXJKZYRKMKENFZB proc near ; CODE XREF: main+129↓p
.text:080485F5 ; main+142↓p ...
.text:080485F5
.text:080485F5 var_8 = dword ptr -8
.text:080485F5 var_4 = dword ptr -4
.text:080485F5 arg_0 = dword ptr 8
.text:080485F5 arg_4 = dword ptr 0Ch
.text:080485F5
.text:080485F5 ; __unwind {
.text:080485F5 push ebp
.text:080485F6 mov ebp, esp
.text:080485F8 sub esp, 10h
.text:080485FB mov [ebp+var_8], 0
.text:08048602 mov [ebp+var_4], 0
.text:08048609 jmp short loc_804862D
.text:0804860B ; ---------------------------------------------------------------------------
.text:0804860B
.text:0804860B loc_804860B: ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+3E↓j
.text:0804860B mov edx, [ebp+var_4]
.text:0804860E mov eax, [ebp+arg_0]
.text:08048611 add eax, edx
.text:08048613 movzx edx, byte ptr [eax]
.text:08048616 mov eax, [ebp+var_4]
.text:08048619 add eax, 804C048h ; Key-String 的地址
.text:0804861E movzx eax, byte ptr [eax]
.text:08048621 cmp dl, al
.text:08048623 jnz short loc_8048629
.text:08048625 add [ebp+var_8], 1
.text:08048629
.text:08048629 loc_8048629: ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+2E↑j
.text:08048629 add [ebp+var_4], 1
.text:0804862D
.text:0804862D loc_804862D: ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+14↑j
.text:0804862D mov eax, [ebp+var_4]
.text:08048630 cmp eax, [ebp+arg_4]
.text:08048633 jb short loc_804860B ; 这是一个for 循环的Buffer 内容校验逻辑
.text:08048635 mov eax, [ebp+var_8]
.text:08048638 cmp eax, [ebp+arg_4]
.text:0804863B setz al
.text:0804863E movzx eax, al
.text:08048641 leave
.text:08048642 retn
所以,我们需要Hook check_equals() 并模拟它的执行,这个题目和09 题不同之处在于,09 题我们可以通过一处指令Hook 来实现,但是10 题我们就不能这么做了,是因为Call check_equals()
的地址太多,用09 题的方式不方便,所以我们可以用Angr 的Hook Symbol 来实现对check_equals() 函数的Hook ,而不是想09 题那样只对指令进行Hook .代码如下:
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)initial_state = project.factory.entry_state()
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, check_data_address, check_data_length): # 基本和09 题的逻辑一样,唯独不同的是我们可以获取check_equals() 的参数
check_input_string = self.state.memory.load(
check_data_address,
check_data_length
)check_against_string = 'MRXJKZYRKMKENFZB'
return claripy.If(check_input_string == check_against_string, claripy.BVV(1, 32), claripy.BVV(0, 32))
check_equals_symbol = 'check_equals_MRXJKZYRKMKENFZB' # :string
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) # Hook Symbolsimulation = 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.posix.dumps(sys.stdin.fileno())
print(solution)
Angr函数使用总结:
Hook 回调函数格式:
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, Hook的函数参数列表):
….
return 函数返回值 # 如果是void函数可以省略
project.hook_symbol(要Hook的函数名,SimProcedure类实例)