Motivation
pwnable.kr AEG 문제를 풀던 중 angr을 사용해보면 좋을 것 같아 슬쩍 훑어보고 코드만 가져다쓰려고 했는데 역시나 생각보다 어렵고 내용이 많아... 한번 공부해보면 좋을 것 같아 쓰게 됨
작성
https://docs.angr.io/ 내용 기반으로, 주관적으로 중요해보이는 내용 위주로 정리함
혹시 참고하시는 분들은 오역에 주의
Top Level Interfaces
Angr 에서는 project가 분석의 프레임으로 사용된다.
Project에는 기본적으로 다음과 같은 속성들이 있다.
>>> import monkeyhex # this will format numerical results in hexadecimal
>>> proj.arch
<Arch AMD64 (LE)>
>>> proj.entry
0x401670
>>> proj.filename
'/bin/true'
Angr에선 바이너리에서 가상메모리로 맵핑시키기 위해 CLE 모듈을 사용하는데 모듈의 결과값을 .loader 를 이용해 사용할 수 있다.
>>> proj.loader
<Loaded true, maps [0x400000:0x5004000]>
>>> proj.loader.shared_objects # may look a little different for you!
{'ld-linux-x86-64.so.2': <ELF Object ld-2.24.so, maps [0x2000000:0x2227167]>,
'libc.so.6': <ELF Object libc-2.24.so, maps [0x1000000:0x13c699f]>}
>>> proj.loader.min_addr
0x400000
>>> proj.loader.max_addr
0x5004000
Angr에서 다양한 다수개 클래스들의 사용 편의를 위해 project.factory 생성자를 제공함
이를 이용해 간편히 자주 사용하는 클래스에 접근 가능하다.
Block - 입력받은 주소의 basic block을 추출
>>> block = proj.factory.block(proj.entry) # lift a block of code from the program's entry point
<Block for 0x401670, 42 bytes>
>>> block.pp() # pretty-print a disassembly to stdout
0x401670: xor ebp, ebp
0x401672: mov r9, rdx
0x401675: pop rsi
0x401676: mov rdx, rsp
0x401679: and rsp, 0xfffffffffffffff0
0x40167d: push rax
0x40167e: push rsp
0x40167f: lea r8, [rip + 0x2e2a]
0x401686: lea rcx, [rip + 0x2db3]
0x40168d: lea rdi, [rip - 0xd4]
0x401694: call qword ptr [rip + 0x205866]
>>> block.instructions # how many instructions are there?
0xb
>>> block.instruction_addrs # what are the addresses of the instructions?
[0x401670, 0x401672, 0x401675, 0x401676, 0x401679, 0x40167d, 0x40167e, 0x40167f, 0x401686, 0x40168d, 0x401694]
SimState - Project 객체는 단순히 프로그램의 초기 이미지이므로 사용자가 angr을 이용 시 시뮬레이팅할 특정 조건을 정의해줘야 한다.
>>> state = proj.factory.entry_state()
<SimState @ 0x401670>
SimState는 메모리, 레지스터, 파일시스템 데이터 등 실행 시 상태에 따라 변경될 수 있는 값들을 포함한다.
사용자는 state.regs, state.mem 등 코드를 통해 사용 가능하다.
>>> state.regs.rip # get the current instruction pointer
<BV64 0x401670>
>>> state.regs.rax
<BV64 0x1c>
>>> state.mem[proj.entry].int.resolved # interpret the memory at the entry point as a C int
<BV32 0x8949ed31>
위 값들을 살펴보면 단순한 정수값이 아닌 bitvector임을 알 수 있는데 상세한 내용은 뒤에서 알아보자.
bitvector와 파이썬 정수형과 변환 예제는 다음과 같다.
>>> state.regs.rsi = state.solver.BVV(3, 64)
>>> state.regs.rsi
<BV64 0x3>
>>> state.mem[0x1000].long = 4
>>> state.mem[0x1000].long.resolved
<BV64 0x4>
Simulation Managers - Simulation Manager는 Angr에서 state 정보를 포함해 프로그램을 실행, 시뮬레이션하는 인터페이스다.
예를 들어 살펴보자. 먼저 사용할 simulation manager를 생성한다.
>>> simgr = proj.factory.simulation_manager(state)
<SimulationManager with 1 active>
>>> simgr.active
[<SimState @ 0x401670>]
simulation manager는 state들의 stash 값을 가지는데, active가 기본값이다.
다음 명령을 통해 symbolic execution의 basic block을 실행할 수 있는데 실행에 따른 값 변화 시 원본 state의 값을 수정하는 것이 아닌 다수개의 state로 저장하는 것이다.
>>> simgr.step()
>>> simgr.active
[<SimState @ 0x1020300>]
>>> simgr.active[0].regs.rip # new and exciting!
<BV64 0x1020300>
>>> state.regs.rip # still the same!
<BV64 0x401670>
분석도구 - angr은 몇 개의 built-in 분석 도구를 포함하고 있으며 이를 사용자 펀의에 따라 사용 가능하다.
>>> proj.analyses. # Press TAB here in ipython to get an autocomplete-listing of everything:
proj.analyses.BackwardSlice proj.analyses.CongruencyCheck proj.analyses.reload_analyses
proj.analyses.BinaryOptimizer proj.analyses.DDG proj.analyses.StaticHooker
proj.analyses.BinDiff proj.analyses.DFG proj.analyses.VariableRecovery
proj.analyses.BoyScout proj.analyses.Disassembly proj.analyses.VariableRecoveryFast
proj.analyses.CDG proj.analyses.GirlScout proj.analyses.Veritesting
proj.analyses.CFG proj.analyses.Identifier proj.analyses.VFG
proj.analyses.CFGAccurate proj.analyses.LoopFinder proj.analyses.VSA_DDG
proj.analyses.CFGFast proj.analyses.Reassembler
상세 내용은 아래 URL의 api documentation에서 사용 가능하다.
(http://angr.io/api-doc/angr.html?highlight=cfg#module-angr.analysis)
바이너리 로딩 - CLE와 angr Projects
Loader 사용
>>> import angr, monkeyhex
>>> proj = angr.Project('/bin/true')
>>> proj.loader
<Loaded true, maps [0x400000:0x5008000]>
Loader 객체
cle.loader는 단일 메모리 공간에 맵핑된 바이너리 객체의 집합이다. 각 바이너리 객체는 loader의 백엔드를 이용해 로딩되고 파일 타입을 이용해 다룰 수 있다(ex. cle.ELF)
>>> proj.loader.all_objects
[<ELF Object fauxware, maps [0x400000:0x60105f]>,
<ELF Object libc.so.6, maps [0x1000000:0x13c42bf]>,
<ELF Object ld-linux-x86-64.so.2, maps [0x2000000:0x22241c7]>,
<ELFTLSObject Object cle##tls, maps [0x3000000:0x300d010]>,
<KernelObject Object cle##kernel, maps [0x4000000:0x4008000]>,
<ExternObject Object cle##externs, maps [0x5000000:0x5008000]>
# This is the "main" object, the one that you directly specified when loading the project
>>> proj.loader.main_object
<ELF Object true, maps [0x400000:0x60105f]>
# This is a dictionary mapping from shared object name to object
>>> proj.loader.shared_objects
{ 'libc.so.6': <ELF Object libc.so.6, maps [0x1000000:0x13c42bf]>
'ld-linux-x86-64.so.2': <ELF Object ld-linux-x86-64.so.2, maps [0x2000000:0x22241c7]>}
# Here's all the objects that were loaded from ELF files
# If this were a windows program we'd use all_pe_objects!
>>> proj.loader.all_elf_objects
[<ELF Object true, maps [0x400000:0x60105f]>,
<ELF Object libc.so.6, maps [0x1000000:0x13c42bf]>,
<ELF Object ld-linux-x86-64.so.2, maps [0x2000000:0x22241c7]>]
# Here's the "externs object", which we use to provide addresses for unresolved imports and angr internals
>>> proj.loader.extern_object
<ExternObject Object cle##externs, maps [0x5000000:0x5008000]>
# This object is used to provide addresses for emulated syscalls
>>> proj.loader.kernel_object
<KernelObject Object cle##kernel, maps [0x4000000:0x4008000]>
# Finally, you can to get a reference to an object given an address in it
>>> proj.loader.find_object_containing(0x400000)
<ELF Object true, maps [0x400000:0x60105f]>
다음과 같이 객체를 이용해 바로 데이터 획득도 가능하다
>>> obj = proj.loader.main_object
# The entry point of the object
>>> obj.entry
0x400580
>>> obj.min_addr, obj.max_addr
(0x400000, 0x60105f)
# Retrieve this ELF's segments and sections
>>> obj.segments
<Regions: [<ELFSegment offset=0x0, flags=0x5, filesize=0xa74, vaddr=0x400000, memsize=0xa74>,
<ELFSegment offset=0xe28, flags=0x6, filesize=0x228, vaddr=0x600e28, memsize=0x238>]>
>>> obj.sections
<Regions: [<Unnamed | offset 0x0, vaddr 0x0, size 0x0>,
<.interp | offset 0x238, vaddr 0x400238, size 0x1c>,
<.note.ABI-tag | offset 0x254, vaddr 0x400254, size 0x20>,
...etc
# You can get an individual segment or section by an address it contains:
>>> obj.find_segment_containing(obj.entry)
<ELFSegment offset=0x0, flags=0x5, filesize=0xa74, vaddr=0x400000, memsize=0xa74>
>>> obj.find_section_containing(obj.entry)
<.text | offset 0x580, vaddr 0x400580, size 0x338>
# Get the address of the PLT stub for a symbol
>>> addr = obj.plt['abort']
>>> addr
0x400540
>>> obj.reverse_plt[addr]
'abort'
# Show the prelinked base of the object and the location it was actually mapped into memory by CLE
>>> obj.linked_base
0x400000
>>> obj.mapped_base
0x400000
Symbols and Relocations
CLE의 loader.find_symbol 을 이용해 심볼 정보를 확인할 수 있으며 심볼 이름과 주소 모두 이용 가능하다
>>> malloc = proj.loader.find_symbol('malloc')
>>> malloc
<Symbol "malloc" in libc.so.6 at 0x1054400>
바이너리 로딩 옵션
바이너리 옵션 - 특정 바이너리 객체를 로딩할때 옵션을 정의하고 싶으면 사용 가능하다
* backend - 사용할 백엔드 지정(ex. elf, pe, mach-o...)
* custom_base_addr - 사용할 베이스 어드레스 주소
* custom_entry_point - 사용할 엔트리 포인트
* custom_arch - 사용할 아키텍쳐
'Fuzzing & Binary analysis' 카테고리의 다른 글
Angr 설치 및 실행 (0) | 2017.12.27 |
---|