pwnable.kr AEG 문제를 보던 중 푸는데에 ANGR을 사용해보면 좋을 것 같아 이 참에 찾아보았다.

구글링 등 예제 코드를 보고 내가 대충 작성한 코드는 다음과 같았는데

(문제 바이너리를 local 로 다운로드 받았다고 치고 시작)


mine.py 

 def main():

        proj = angr.Project('newbin0', load_options={"auto_load_libs": False})


        input_size = 0x60

        argv1 = claripy.BVS("argv1",input_size * 8)


        init = proj.factory.entry_state(args=["./newbin0", argv1])

        init.libc.buf_symbolic_bytes=input_size + 1

        al = [0x947C86,0x947976,0x9478FB,0x947889,0x947809,0x94778C,0x94770C,0x94768C,0x947610,0x9475A8,0x94752B,0x9474A9,0x94742C,0x9473B3,0x947337,0x9472BB,0x94723B]


        for byte in argv1.chop(8):

                init.add_constraints(byte >= '0')

                init.add_constraints(byte <= 'Z')

                init.add_constraints(byte != '\x3a')

                init.add_constraints(byte != '\x3b')

                init.add_constraints(byte != '\x3c')

                init.add_constraints(byte != '\x3d')

                init.add_constraints(byte != '\x3e')

                init.add_constraints(byte != '\x3f')

                init.add_constraints(byte != '\x40')


        init_path = proj.factory.path(init)

        path_group = proj.factory.path_group(init)

        path_group.explore(find=FIND_ADDR, avoid=al)


        found = path_group.found[0]

        solution = found.state.se.any_str(argv1)

        return solution



위와 같이 코드를 작성하고 실행할 경우 문제는 원하는 방향으로 풀리고 Hex 값을 return 해주지만 실행시간이 약 60초 걸렸다.

문제에서 주어진 시간은 10초로 angr 돌리는데에만 터무니없이 긴 시간이 걸렸음.

시간을 줄이기 위해 avoid 조건을 이용해 이것 저것 넣어봤는데 효과는 미비했다.


구글링 중 github에 aeg 문제풀이 코드가 나와있는 것을 찾았고 여기서 angr 부분만 참고했다.

(출처 : https://github.com/rot256/Wargames/blob/master/pwnable/aeg/attack.py)

원본 코드는 위 URL에서 확인할 수 있으며 밑에는 테스트에 필요한 코드만 추출, 조작함


attack.py

# Create symbolic buffer

p = angr.Project(tar)

buf = claripy.BVS("buf",48*8)

start_state = p.factory.blank_state(addr=0x2233b96)

start_state.memory.store(0x2436160, buf)


# Setup a stack frame

start_state.regs.rbp = start_state.regs.rsp

start_state.regs.rsp = start_state.regs.rsp - 0x50

start_state.memory.store(start_state.regs.rsp, start_state.se.BVV(0, 8*0x32))


# Setup stepper

pg = p.factory.path_group(start_state)

def step_func(pg):

    pg.drop(filter_func = lambda path: path.addr == 0x2233bbd)

    pg.stash(filter_func = lambda path: path.addr == 0x4006e0, from_stash='active', to_stash='found')

    return pg

pg.step(step_func = step_func, until = lambda pg: len(pg.found) > 0)

f = pr.found[0]

cert = f.state.se.any_str(buf)

print cert.encode('hex')


위 스크립트 실행 시 약 6-7s 정도 시간 소요로 답을 구할 수 있었다.


결국 차이점은

1) 적절한 바이너리의 start state 설정

2) avoid 조건

으로 필요한 연산만 하도록해 state를 줄여줄 수 있는 것으로 보인다.


조금 더 공부해봅시다.

'Pwnable.kr > Grotesque' 카테고리의 다른 글

[pwnable.kr] dos4fun - 444pt  (5) 2018.03.14
[pwnable.kr] sudoku - 111pt  (0) 2018.02.19
[pwnable.kr] AEG - 550pt  (0) 2018.02.11
[pwnable.kr] maze - 150pt  (0) 2017.12.23


* 펀드란

불특정 다수인으로부터 모금한 실적 배당형 투자 기금.[1] 주식, 채권, 파생상품 등에 대한 투자를 위해 고객들로부터 돈을 모아서 구성하는 일정 금액의 자금 운용 단위를 가리킨다



* Pros

-    투자의 수익금을 수익이 나는 만큼 투자자에게 돌려준다.

     즉 예, 적금보다 높은 수익률을 기대할 수 있음.

-    전문가를 통해 투자하므로 직접투자에 비해 안전(?)하다고 볼 수 있음

-    소액, 분산 투자가 가능하다


* Cons

-    펀드는 예금과 달리 예금자보호법(1인당 5,000만원 한도 보장)의 보호를 받을 수 없음

     펀드 운용구조를 통해 투자자의 자금을 보호하는 방식을 사용



* 펀드 종류

투자 대상에 따라 나눠보면 다음과 같다

-    증권펀드    - 주식형 : 주식에 60% 이상 투자

   - 혼합형    - 주식혼합형펀드 : 주식에 50% 이상 투자

 - 채권혼합형펀드 : 채권에 50% 이상 투자

   - 채권형 : 채권에 60% 이상 투자

-    부동산펀드

-    실물펀드

-    기타 등등.



* 펀드 종류 상세

-    투자금 납입방식에 따라 : 적립식, 거치식

-    지역에 따라 : 국내, 해외(국내법에 의해 만들어졌다면 역내펀드, 외국법에 의해 만들어졌다면 역외펀드)

-    스타일에 따라 : 배당형, 가치형, 성장형, 인덱스형(지수에 따라), 전환형(2개 펀드간 전환이 가능), 엄브렐러형(3개 펀드간 전환이 가능)

-    펀드자금 모집방법에 따라 : 공모형, 사모형

-    세제혜택에 따라 : 절세형, 소득공제형

-    투자자금 회수방법에 따라 : 개방형, 폐쇄형

-    추가가입 여부에 따라 : 추가형, 단위형



* 증권펀드 상세

-    대부분의 사람들이 투자하는 펀드의 종류

-    주식 비율이 높을 수록 고수익, 고위험의 특성을 가짐

-    주식형 펀드는 운용전략에 따라

ㅇ 배당형 : 배당을 많이 주는 주식에 투자(주가 등락에 신경쓰고 싶지 않다면 선택)

ㅇ 가치형 : 실제 기업 가치보다 저평가된 주식에 주로 투자(상동)

ㅇ 성장형 : 성장이 예상되는 산업에 주로 투자(주가 등락에 민감하게 반응하는 스타일이 선택하면 좋음)

-    채권에 대한 고려사항

ㅇ 단기채권, 장기채권을 확인할 것, 장기채권은 금리 변동에 민감할 수 있음

ㅇ 채권 종류와 신용등급을 확인할 것(펀드 평가회사 ex. 제로인, 모닝스타코리아 확인)



* 펀드 규모에 따라

-    100억 미만이면 소형

-    100~1,000억이면 중형

-    1,000억 이상이면 대형

-    5,000억 이상이면 초대형

-    규모가 작을수록 발빠른 대응이 가능하나 상대적으로 보수가 적어 자산운용회사 관리가 소홀할 우려가 있음

-    규모가 클수록 분산투자 운용에 유리하지만 변화에 탄력적 대응이 어려움



* 펀드 투자 advice

-    환매, 손절매에 대한 원칙을 세우고 지켜라

-    초심자라면 적립식 펀드로 중형 이상의 펀드로 시도해보는 것이 좋다

-    채권형 펀드에 분산투자 하는것으로 시작해보자

-    분산 노하우

ㅇ 적립식 펀드로

ㅇ 지역 분산, 국내, 해외

ㅇ 대상 분산, 주식, 혼합, 채권



* 펀드 운용 구조

[투자자]    -    [판매회사]    -    [수탁회사]

|                        |

- [자산운용회사] ---



* 판매회사 평가 참고

-    금융감독원 홈페이지(www.fss.or.kr) 상단 보도홍보 - 보도자료 - 미스터리 검색 - 펀드 미스테리 쇼핑 결과 확인 가능

-    한국금융투자자보호재단의 평가결과도 참고 가능

-    직접 상품비교해보는것도 좋다



* 펀드 비용의 종류

-    수수료

ㅇ 선취 판매 수수료 : 펀드 가입시 내는 비용

ㅇ 후취 판매 수수료 : 펀드 환매 시 내는 비용(선취, 후취 선택)

ㅇ 환매 수수료 : 펀드 환매를 너무 일찍(보통 3개월이내) 할 때 내는 비용

-    보수

ㅇ 판매보수 : 판매회사에 내는 보수

ㅇ 운용보수 : 자산운용회사에

ㅇ 수탁보수 : 수탁회사에



* 수수료 보수 절약법

-    총 비용이 낮은 펀드 선택 : 운용보수보다 판매보수가 낮은 펀드를 선태갛는 것이 유리

-    창구 가입보다 인터넷가입이 수수료에 유리

-    펀드의 클래스 확인 : 2년 이상 중장기에는 클래스A, 2년 이내 단기적 투자에는 클래스C 가 좋음



* 펀드 이름을 보고 파악하기

대신 / 성장중소형주 / 증권 / 투자신탁 / [주식] / ClassA

  1              2            3          4           5          6

1    자산운용사 : 펀드를 관리하고 운용하는 회사

2    스타일(운용전략)

3    운용자산

4    법적속성

5    운용대상

6    판매수수료

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

+ Recent posts