상세 컨텐츠

본문 제목

[angr_ctf] 04_angr_symbolic_stack

SYSTEM HACKING/CTF, etc

by koharin 2022. 5. 14. 15:56

본문

728x90
반응형

파일 정보

$ file 04_angr_symbolic_stack 
04_angr_symbolic_stack: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=dbb593c5082a34aa879daf76e62b081b0676d71b, not stripped

 

 

Code Analysis

main 함수

int __cdecl main(int argc, const char **argv, const char **envp)
{
  printf("Enter the password: ");
  handle_user();
  return 0;
}

 

handler_user() 함수

int handle_user()
{
  int result; // eax
  int v1; // [esp+8h] [ebp-10h]
  int v2; // [esp+Ch] [ebp-Ch]

  __isoc99_scanf("%u %u", &v2, &v1);
  v2 = complex_function0(v2);
  v1 = complex_function1(v1);
  if ( v2 == 0x7C315173 && v1 == 0xFA12140A )
    result = puts("Good Job.");
  else
    result = puts("Try again.");
  return result;
}

scanf로 v1, v2에 입력을 받는다. angr는 scanf에서의 multiple 입력을 처리하지 못 하므로 이번에도 직접 register에 값을 세팅해줘야 한다.

이후 입력값으로 complex_function0,1을 호출해서 리턴값을 각각 v1, v2에 저장 후 if 조건에 맞으면 Good ~ 을 출력해주고 맞지 않으면 Try ~ 를 출력해준다.

 

 

Exploit

set start state: p.factory.blank_state

.text:08048690 handle_user     proc near               ; CODE XREF: main+21↓p
.text:08048690
.text:08048690 var_10          = dword ptr -10h
.text:08048690 var_C           = dword ptr -0Ch
.text:08048690
.text:08048690 ; __unwind {
.text:08048690                 push    ebp
.text:08048691                 mov     ebp, esp
.text:08048693                 sub     esp, 18h
.text:08048696                 sub     esp, 4
.text:08048699                 lea     eax, [ebp+var_10]
.text:0804869C                 push    eax
.text:0804869D                 lea     eax, [ebp+var_C]
.text:080486A0                 push    eax
.text:080486A1                 push    offset aUU      ; "%u %u"
.text:080486A6                 call    ___isoc99_scanf
.text:080486AB                 add     esp, 10h
.text:080486AE                 mov     eax, [ebp+var_C]
  • scanf에서는 multiple input을 처리하지 못 하므로 시작 주소를 scanf 이후인

0x080486AB 주소로 준다.

 

set bitvectors

scanf(”%u %u”) 대신 값을 넣어주기 위해, bitvectors를 바이너리에 넣어준다.

일단 두 변수가 int 형이므로 32bit 크기의 ‘input0’, ‘input1’ 이름의 bitvector를 만든다.

state.solver.BVS('input0', 32)
state.solver.BVS('input1', 32)

 

push bitvectors to stack: state.stack_push

.text:08048690                 public handle_user
.text:08048690 handle_user     proc near               ; CODE XREF: main+21↓p
.text:08048690
.text:08048690 var_10          = dword ptr -10h
.text:08048690 var_C           = dword ptr -0Ch
.text:08048690
.text:08048690 ; __unwind {
.text:08048690                 push    ebp
.text:08048691                 mov     ebp, esp
.text:08048693                 sub     esp, 18h
.text:08048696                 sub     esp, 4
.text:08048699                 lea     eax, [ebp+var_10]
.text:0804869C                 push    eax
.text:0804869D                 lea     eax, [ebp+var_C]
.text:080486A0                 push    eax
.text:080486A1                 push    offset aUU      ; "%u %u"
.text:080486A6                 call    ___isoc99_scanf
.text:080486AB                 add     esp, 10h
.text:080486AE                 mov     eax, [ebp+var_C]
.text:080486B1                 sub     esp, 0Ch
.text:080486B4                 push    eax
.text:080486B5                 call    complex_function0
.text:080486BA                 add     esp, 10h
.text:080486BD                 mov     [ebp+var_C], eax
.text:080486C0                 mov     eax, [ebp+var_10]
.text:080486C3                 sub     esp, 0Ch
.text:080486C6                 push    eax
.text:080486C7                 call    complex_function1
.text:080486CC                 add     esp, 10h
.text:080486CF                 mov     [ebp+var_10], eax
.text:080486D2                 mov     eax, [ebp+var_C]
.text:080486D5                 cmp     eax, 7C315173h
.text:080486DA                 jnz     short loc_80486E6
.text:080486DC                 mov     eax, [ebp+var_10]
.text:080486DF                 cmp     eax, 0FA12140Ah
.text:080486E4                 jz      short loc_80486F8
.text:080486E6
.text:080486E6 loc_80486E6:                            ; CODE XREF: handle_user+4A↑j
.text:080486E6                 sub     esp, 0Ch
.text:080486E9                 push    offset s        ; "Try again."
.text:080486EE                 call    _puts
.text:080486F3                 add     esp, 10h
.text:080486F6                 jmp     short loc_8048709
.text:080486F8 ; ---------------------------------------------------------------------------
.text:080486F8
.text:080486F8 loc_80486F8:                            ; CODE XREF: handle_user+54↑j
.text:080486F8                 sub     esp, 0Ch
.text:080486FB                 push    offset aGoodJob ; "Good Job."
.text:08048700                 call    _puts
.text:08048705                 add     esp, 10h
.text:08048708                 nop

handler_user 함수에서는 어떤 작업을 하기 전 함수 초기에 함수에서 사용되는 지역변수들을 위한 공간을 stack에 만들어놓는다.

pwndbg> disass handle_user
Dump of assembler code for function handle_user:
   0x08048690 <+0>:	push   ebp
   0x08048691 <+1>:	mov    ebp,esp
   0x08048693 <+3>:	sub    esp,0x18
   0x08048696 <+6>:	sub    esp,0x4
   0x08048699 <+9>:	lea    eax,[ebp-0x10]
   0x0804869c <+12>:	push   eax
   0x0804869d <+13>:	lea    eax,[ebp-0xc]
   0x080486a0 <+16>:	push   eax

esp를 ebp에 넣은 후, ebp-0x10 위치에 하나의 지역변수를 위치시키고, ebp-0xc 위치에 다른 하나의 지역변수를 위치시킨다.

그럼 스택 구조는 다음과 같다.

ebp-0xc는 4byte 이므로 ebp-0x9 부터 ebp-0xc를 차지하고, ebp-0x10도 4byte이므로 ebp-0xd 부터 ebp-0x10까지 차지한다.

그럼 남은 크기는 ebp-0x0 ~ ebp-0x8인 8byte로 총 8byte의 padding이 생긴다.

따라서 두 개의 bitvector를 넣기 전에 8byte의 padding을 stack에 만들어놓는다.

# mov ebp, esp (set ebp to esp register value)
state.regs.ebp = state.regs.esp

# allocate padding by decreasing esp before push bitvectors
padding = 8
state.regs.esp = state.regs.esp - padding
state.stack_push(input0)
state.stack_push(input1)

두 개의 입력에 대한 bitvectors를 스택에 push 한다.

 

이후부터는 angr에 find로 찾을 주소, avoid로 피할 주소를 주고 explore를 사용하면 가능한 경로를 찾아서 찾으면 found stash에 넣어준다. (이 과정은 앞선 과정들과 동일)

 

 

#!/usr/bin/python 
import angr, sys

# create angr project
p = angr.Project('./04_angr_symbolic_stack')

# set start state
state = p.factory.blank_state(
        addr=0x080486AE,
        add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS})

# create bitvectors
input0 = state.solver.BVS('input0', 32)
input1 = state.solver.BVS('input1', 32)

# mov ebp, esp (set ebp to esp register value)
state.regs.ebp = state.regs.esp

# allocate padding by decreasing esp before push bitvectors
padding = 8
state.regs.esp -= padding

# push bitvectors to stack
state.stack_push(input0)
state.stack_push(input1)

# create simulation manager
simgr = p.factory.simgr(state)

# search possible path with given address
simgr.explore(find=lambda s: 'Good'.encode() in s.posix.dumps(sys.stdout.fileno()), avoid=lambda s: 'Try'.encode() in s.posix.dumps(sys.stdout.fileno()))

if simgr.found:
    solution_state = simgr.found[0]
    print('solution state:', end=' ')
    print(solution_state)

    solution0 = solution_state.solver.eval(input0)
    solution1 = solution_state.solver.eval(input1)

    print("%u %u" % (solution0, solution1))
else:
    raise Exception('could not find solution')

전체 코드이다.

angr가 구해준 solution을 프로그램 실행 시 입력으로 넣어주면 “Good Job.”이 출력된다.

728x90
반응형

'SYSTEM HACKING > CTF, etc' 카테고리의 다른 글

[angr_ctf] 06_angr_symbolic_dynamic_memory  (0) 2022.05.14
[angr_ctf] 05_angr_symbolic_memory  (0) 2022.05.14
[angr_ctf] 03_angr_symbolic_registers  (0) 2022.05.14
[angr_ctf] 02_angr_find_condition  (0) 2022.05.14
[angr_ctf] 01_angr_avoid  (0) 2022.05.14

관련글 더보기