상세 컨텐츠

본문 제목

[angr_ctf] 08_angr_constraints

SYSTEM HACKING/CTF, etc

by koharin 2022. 5. 15. 13:59

본문

728x90
반응형

파일 정보

$ file 08_angr_constraints  
08_angr_constraints: 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]=d53f4be8c8a8535398c061131666b583f07c6b19, not stripped

 

코드 분석

main()

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

  password = 'WISO';
  dword_804A034 = 'IXBH';
  dword_804A038 = 'VQOF';
  dword_804A03C = 'BZBS';
  memset(&buffer, 0, 0x11u);
  printf("Enter the password: ");
  __isoc99_scanf("%16s", &buffer);
  for ( i = 0; i <= 15; ++i )
    *(_BYTE *)(i + 0x804A040) = complex_function(*(char *)(i + 0x804A040), 15 - i);
  if ( check_equals_OSIWHBXIFOQVSBZB((int)&buffer, 0x10u) )
    puts("Good Job.");
  else
    puts("Try again.");
  return 0;
}
  • 전역변수 4개 값 설정. password 16바이트는 check_equals 뒤의 OSIWHBXIFOQVSBZB 과 동일하다.
  • buffer 17 바이트 초기화 후, buffer에 scanf() 함수로 16바이트 입력받음
  • complex_function() 함수 호출로 입력값 연산. 이때 10진수를 16진수로 바꿔보면 주소인데, buffer 전역변수 주소이다.
  • .bss:0804A040 buffer db ? ;
  • 입력값 들어있는 buffer 변수와 16을 인자로 check_equals_OSIWHBXIFOQVSBZB() 함수 호출해서 연산한 결과가 0이 아니면 Good Job. 출력하고 0이면 Try again. 출력

 

complex_function()

int __cdecl complex_function(signed int a1, int a2)
{
  if ( a1 <= '@' || a1 > 'Z' )
  {
    puts("Try again.");
    exit(1);
  }
  return (a1 - 65 + 53 * a2) % 26 + 65;
}
  • 암호화 진행
  • buffer에 암호화된 값이 저장된다.

 

check_equals_OSIWHBXIFOQVSBZB()

_BOOL4 __cdecl check_equals_OSIWHBXIFOQVSBZB(int input_addr, unsigned int size)
{
  int v3; // [esp+8h] [ebp-8h]
  unsigned int i; // [esp+Ch] [ebp-4h]

  v3 = 0;
  for ( i = 0; size > i; ++i )
  {
    if ( *(_BYTE *)(i + input_addr) == *(_BYTE *)(i + 0x804A030) )
      ++v3;
  }
  return v3 == size;
}
  • i에 더해지는 10진수를 16진수로 바꿔보면 주소가 나오고, IDA의 jump to address 사용해서 해당 주소로 가보면 전역변수 password 위치임을 알 수 있다.
  • .bss:0804A030 password dd ?
  • 즉, 입력 == OSIWHBXIFOQVSBZB가 같은지 비교한다.

따라서 complex_function() 결과로 암호화된 입력값 buffer와 “OSIWHBXIFOQVSBZB”가 같아야 한다.

컴퓨터는 모든 분기를 보는데 오래 걸리기 때문에, complex_function 함수 호출 전 프로그램 멈춰서 check~ 함수에서 비교하는 password인 “OSIWHBXIFOQVSBZB”와 암호화된 buffer(입력)가 동일하도록 해서 입력값(solution)을 찾는다.

 

 

Exploit

set start state

  • complete_function 호출 전을 시작주소로 지정
state = p.factory.blank_state(
        addr=0x804863c,
        add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, 
                    angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

 

buffer 값: symbolic bitvector

  • symbolic bitvector 값을 memory.store로 buffer 위치에 16바이트 넣어줌
# symbolic bitvector
buffer = state.solver.BVS('buffer', 8*16)

# set buffer value
password_addr = 0x804A040
state.memory.store(password_addr, buffer)

 

check_equals_OSIWHBXIFOQVSBZB() 호출 전 상태 찾기

이제 Good Job을 찾으려면 65,536개의 분기를 봐야 하기 때문에 해당 경로를 찾는건 할 수 없다. 대신 check_equals_OSIWHBXIFOQVSBZB() 호출 시 넣어주는 buffer 값과 “OSIWHBXIFOQVSBZB”이 같기만 하면 되므로, 해당 함수 호출 전 buffer 값이

.text:08048683                 push    10h
.text:08048685                 push    offset buffer
.text:0804868A                 call    check_equals_OSIWHBXIFOQVSBZB

check_equals_OSIWHBXIFOQVSBZB() 함수 호출 전 buffer 16바이트를 넣는 주소(0x08048683)를 찾는다.

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

# find state before check_equals()
simgr.explore(find=0x08048683)

 

solution 구하기

		# get buffer value
    constrained_addr = 0x804A040
    constrained_size = 16
    constrained_bitvector = solution_state.memory.load(constrained_addr, constrained_size)

    # constrain system to find an input that make constrained_bitvector equal desired value
    constrained_desired_value = "OSIWHBXIFOQVSBZB".encode()

    # test whether constrained_bitvector == desired value 
    solution_state.add_constraints(constrained_bitvector == constrained_desired_value)

    solution = solution_state.solver.eval(buffer, cast_to=bytes).decode()

check_equals 호출 전 0x804a040 주소(buffer)에서의 16바이트 값과 “OSIWHBXIFOQVSBZB”가 같은지 add_constraint로 비교해서 같게 하는 solution을 구한다.

 

 

Exploit result

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

p = angr.Project('./08_angr_constraints')

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

# symbolic bitvector
buffer = state.solver.BVS('buffer', 8*16)

# set buffer value
password_addr = 0x804A040
state.memory.store(password_addr, buffer)

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

# find state before check_equals()
simgr.explore(find=0x08048683)

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

    # get buffer value
    constrained_addr = 0x804A040
    constrained_size = 16
    constrained_bitvector = solution_state.memory.load(constrained_addr, constrained_size)

    # constrain system to find an input that make constrained_bitvector equal desired value
    constrained_desired_value = "OSIWHBXIFOQVSBZB".encode()

    # test whether constrained_bitvector == desired value 
    solution_state.add_constraints(constrained_bitvector == constrained_desired_value)

    solution = solution_state.solver.eval(buffer, cast_to=bytes).decode()

    print('solution:', end=' ')
    print(solution)
else:
    raise Exception('could not find the solution.')

728x90
반응형

관련글 더보기