상세 컨텐츠

본문 제목

[angr_ctf] 06_angr_symbolic_dynamic_memory

SYSTEM HACKING/CTF, etc

by koharin 2022. 5. 14. 16:48

본문

728x90
반응형

코드 분석

main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v3; // al
  signed int i; // [esp+Ch] [ebp-Ch]

  buffer0 = (char *)malloc(9u);
  buffer1 = (char *)malloc(9u);
  memset(buffer0, 0, 9u);
  memset(buffer1, 0, 9u);
  printf("Enter the password: ");
  __isoc99_scanf("%8s %8s", buffer0, buffer1);
  for ( i = 0; i <= 7; ++i )
  {
    v3 = complex_function(buffer0[i], i);
    buffer0[i] = v3;
    buffer1[i] = complex_function(buffer1[i], i + 32);
  }
  if ( !strncmp(buffer0, "OSIWHBXI", 8u) && !strncmp(buffer1, "FOQVSBZB", 8u) )
    puts("Good Job.");
  else
    puts("Try again.");
  free(buffer0);
  free(buffer1);
  return 0;
}
  • malloc 함수를 통해 동적 할당을 해서 해당 영역에 대한 포인터 buffer0, buffer1에 할당된 주소를 저장한다.
  • 즉, heap에 데이터가 저장된다.
  • 이후 scanf로 buffer0, buffer1 영역에 스트링을 입력받는다. 하나만 scanf로 입력받는게 아니기 때문에 이번에도 angr에서 이를 처리하지 못해서 bitvector로 바이너리에 injection 해줘서 입력값을 줘야 한다.
  • 이후 complex_function으로 어떠한 연산 후 strncmp를 통해 8바이트씩 특정 문자열로 비교후 같을 경우 “Good Job.”을 출력해준다.

 

Exploit

set start state: p.factory.blank_state()

.text:080486A1                 push    eax
.text:080486A2                 push    offset a8s8s    ; "%8s %8s"
.text:080486A7                 call    ___isoc99_scanf
.text:080486AC                 add     esp, 10h
.text:080486AF                 mov     [ebp+var_C], 0
.text:080486B6                 jmp     short loc_8048722

scanf 호출 이후 주소를 시작 주소로 준다. (0x080486AF)

state = p.factory.blank_state(
        addr=0x080486AF,
        add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
                     angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

 

set bitvectors

input0 = state.solver.BVS('input0', 8*8)
input1 = state.solver.BVS('input1', 8*8)

 

set buffer0, buffer1 value to fake heap address

동적 할당의 경우 전역변수 buffer0(0x0A2DEF74), buffer1(0x0A2DEF7C)에는 동적 할당된 영역을 가리키는 주소가 적힌다. 따라서 여기에 적히는 주소를 구해줘야 값을 원하는 공간에 넣을 수 있다.

근데 heap 주소를 알지 못해도 사용되지 않는 영역 주소로 fake heap 주소를 지정해서 buffer0, buffer1에 fake 주소들을 적어주고, fake 주소에 input0, input1 값을 저장해줘서 입력을 설정할 수 있다.

>>> import angr
>>> p = angr.Project('06_angr_symbolic_dynamic_memory')
>>> state = p.factory.blank_state(
...         addr=0x080486AF,
...         add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
...                      angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
... )
>>> 
>>> p.arch
<Arch X86 (LE)>
>>> p.arch.memory_endness
'Iend_LE'

이때 angr는 디폴트로 endian이 big endian인데 바이너리 아키텍처에서 메모리에 값을 넣을 때는 little endian으로 넣어주므로 endianness를 바이너리에 맞게 바꿔준다. 또한 x86 32bit에서 pointer 크기가 4바이트이므로 사이즈로 4로 지정해준다.

# fake heap address 
fake_heap_addr1 = 0x5160000
fake_heap_addr2 = 0x5160008

# set buffer0, buffer1 to fake heap address
state.memory.store(0x0A2DEF74, fake_heap_addr1, endness=p.arch.memory_endness, size=4)
state.memory.store(0x0A2DEF7C, fake_heap_addr2, endness=p.arch.memory_endness, size=4)

 

fill fake heap space with input values

# store fake heap space with input0, input1
state.memory.store(fake_heap_addr0, input0)
state.memory.store(fake_heap_addr1, input1)

 

find solution: simgr.explore

# explore possible path to find solution 
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 found stash is not empty
if simgr.found:
    solution_state = simgr.found[0]
    
    print('solution state:', end=' ')
    print(solution_state)

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

    print("%s %s" % (solution0, solution1))
else:
    raise Exception('could no find the solution')

 

exploit result

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

# create angr project
p = angr.Project('./06_angr_symbolic_dynamic_memory')

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

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

# fake heap address 
fake_heap_addr0 = 0x5160000
fake_heap_addr1 = 0x5160008

# set buffer0, buffer1 to fake heap address
state.memory.store(0x0A2DEF74, fake_heap_addr0, endness=p.arch.memory_endness, size=4)
state.memory.store(0x0A2DEF7C, fake_heap_addr1, endness=p.arch.memory_endness, size=4)

# store fake heap space with input0, input1
state.memory.store(fake_heap_addr0, input0)
state.memory.store(fake_heap_addr1, input1)

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

# explore possible path to find solution 
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 found stash is not empty
if simgr.found:
    solution_state = simgr.found[0]
    
    print('solution state:', end=' ')
    print(solution_state)

    solution0 = solution_state.solver.eval(input0, cast_to=bytes).decode()
    solution1 = solution_state.solver.eval(input1, cast_to=bytes).decode()

    print("%s %s" % (solution0, solution1))
else:
    raise Exception('could no find the solution')

이후 구해준 solution은 int가 아닌 bytes 값이므로 casting 후 decode해서 구해준다.

728x90
반응형

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

[angr_ctf] 08_angr_constraints  (0) 2022.05.15
[angr_ctf] 07_angr_symbolic_file  (0) 2022.05.14
[angr_ctf] 05_angr_symbolic_memory  (0) 2022.05.14
[angr_ctf] 04_angr_symbolic_stack  (0) 2022.05.14
[angr_ctf] 03_angr_symbolic_registers  (0) 2022.05.14

관련글 더보기