상세 컨텐츠

본문 제목

[angr_ctf] 07_angr_symbolic_file

SYSTEM HACKING/CTF, etc

by koharin 2022. 5. 14. 21:36

본문

728x90
반응형

파일 정보

$ file 07_angr_symbolic_file
07_angr_symbolic_file: 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]=9415cb30bed985681d385c2ae9292ede963d6895, not stripped

 

 

코드 분석

main()

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // ST08_4
  bool v4; // cf
  bool v5; // zf
  signed int v6; // ecx
  _BYTE *v7; // esi
  const char *v8; // edi
  int v9; // [esp-10h] [ebp-2Ch]
  int v10; // [esp-Ch] [ebp-28h]
  int v11; // [esp-8h] [ebp-24h]
  int v12; // [esp-4h] [ebp-20h]
  int v13; // [esp+0h] [ebp-1Ch]
  signed int i; // [esp+0h] [ebp-1Ch]

  memset(&buffer, 0, 0x40u);
  printf("Enter the password: ");
  __isoc99_scanf("%64s", &buffer, v3, v9, v10, v11, v12, v13);
  ignore_me((int)&buffer, 0x40u);
  • 패스워드를 scanf() 함수로 buffer(전역변수)에 입력받는다.
  • buffer 주소값, 0x40을 인자로 ignore_me() 함수를 호출한다.

 

ignore_me()

unsigned int __cdecl ignore_me(int a1, size_t n)
{
  void *v2; // esp
  int v4; // [esp+0h] [ebp-28h]
  void *ptr; // [esp+Ch] [ebp-1Ch]
  size_t v6; // [esp+10h] [ebp-18h]
  void *s; // [esp+14h] [ebp-14h]
  FILE *stream; // [esp+18h] [ebp-10h]
  unsigned int v9; // [esp+1Ch] [ebp-Ch]

  ptr = (void *)a1;
  v9 = __readgsdword(0x14u);
  v6 = n - 1;
  v2 = alloca(16 * ((n + 15) / 0x10));
  s = &v4;
  memset(&v4, 0, n);
  unlink("FOQVSBZB.txt");
  stream = fopen("FOQVSBZB.txt", "a+b");
  fwrite(ptr, 1u, n, stream);
  fseek(stream, 0, 0);
  __isoc99_fscanf(stream, "%64s", s);
  fseek(stream, 0, 0);
  fwrite(s, 1u, n, stream);
  fclose(stream);
  return __readgsdword(0x14u) ^ v9;
}

alloca(size_t size)

  • heap이 아닌 stack 영역에 size 크기만큼 메모리를 동적 할당해준다.
  • 할당 시 stack overflow가 발생하면 프로그램 행위를 예측할 수 없게 된다.

unlink(const char *pathname)

  • pathname으로 지정한 파일을 삭제하는 systemcall

alloca() 함수로 할당받고 해당 stack 영역을 가리키는 주소를 v2에 저장한다.

FOQVSBZB.txt 파일 열어서 ptr 값을 0x39만큼 복사한 후, fseek로 파일 시작으로 이동하여 fscanf() 함수로 파일로부터 s에 64바이트만큼 복사한다.

이후 다시 fseek() 함수로 파일 시작으로 이동 후, s의 0x39만큼을 파일에 복사한 후 파일을 닫는다.

 

main()

	memset(&buffer, 0, 0x40u);
  fp = fopen("FOQVSBZB.txt", "rb");
  fread(&buffer, 1u, 0x40u, fp);
  fclose(fp);
  unlink("FOQVSBZB.txt");
  for ( i = 0; ; ++i )
  {
    v4 = (unsigned int)i < 7;
    v5 = i == 7;
    if ( i > 7 )
      break;
    *(_BYTE *)(i + 134520992) = complex_function(*(char *)(i + 134520992), i);
  }
  v6 = 9;
  v7 = &buffer;
  v8 = "OSIWHBXI";
  do
  {
    if ( !v6 )
      break;
    v4 = *v7 < (const unsigned __int8)*v8;
    v5 = *v7++ == *v8++;
    --v6;
  }
  while ( v5 );
  if ( (!v4 && !v5) != v4 )
  {
    puts("Try again.");
    exit(1);
  }
  puts("Good Job.");
  exit(0);
}
  • buffer 초기화 후, FOQVSBZB.txt 파일을 읽기 전용으로 연 후 buffer에 파일 내용을 0x40바이트만큼 복사한다.
  • 파일을 닫고, FOQVSBZB.txt 파일을 삭제한다.
  • complex_function() 함수를 반복적으로 호출하며 연산 작업 후, do-while문에서 연산 작업을 진행한다.
  • 마지막에 v5, v4에 대한 조건이 만족해야 “Good Job.”을 출력해준다.

 

 

Exploit Flow

바이너리는 main 함수 초반에 scanf로 buffer에 입력을 받긴 하지만, ignore_me() 함수에서 FOQVSBZB.txt 파일에 값을 쓴 후, main() 함수로 돌아와 buffer를 초기화 해서 FOQVSBZB.txt 파일에서 0x40바이트 만큼 buffer에 복사한다.

즉, 패스워드에 대한 입력은 FOQVSBZB.txt에 저장되어있다.

이후에는 연산 작업 후 패스워드가 조건과 맞을 경우 “Good Job.”을 출력해준다.

  1. fread()로 읽을 FOQVSBZB.txt 파일 내용 → symbolic bitvectors 64바이트 크기
  2. angr로 파일시스템을 simulate해서 파일을 simulate된 파일로 대체. content는 symbolic 값(symbolic bitvector 값)으로 파일 초기화. → angr.storage.SimFile(filename, content)
  3. 파일을 내용이 symbolic 입력이 되고(파일 내용이 buffers 변수에 복사돼서), 이후 패스워드로 가능한 solution 찾기

 

 

Exploit

set start state: Project.factory.blank_state

state = p.factory.blank_state(
        addr=0x080488B9,
        add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
  • ignore_me() 함수에서 파일에 내용 적으므로 ignore_me() 호출 이후를 시작 주소로 준다.

 

set symbolic bitvector: state.solver.BVS

password = state.solver.BVS('password', 64)
  • 파일에 적을 내용 64바이트

 

symbolic file 생성: angr.storage.SimFile

filename = 'FOQVSBZB.txt'
password_file = angr.storage.SimFile(filename, content=password)
  • 파일 없으면 새로 생성된다.
  • file_options로 linux 파일 권한을 설정할 수 있다.
  • symbolic bitvector 를 content로 파일 내용 초기화한다.

 

filesystem에 symbolic file 추가: state.fs.insert

state.fs.insert(filename, password_file)

 

 

Exploit result

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

# create angr project
p = angr.Project('./07_angr_symbolic_file')

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

# file content (64 bytes)
password = state.solver.BVS('password', 64)

# symbolic file copying it's content to symbolic input(buffers global variable)
filename = 'FOQVSBZB.txt'
password_file = angr.storage.SimFile(filename, content=password)

# add symbolic file(password_file) to filesystem
state.fs.insert(filename, password_file)

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

# search possible path for 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:
    print('solution state:', end=' ')
    print(simgr.found[0])
    
    solution = simgr.found[0].solver.eval(password, cast_to=bytes).decode()
    
    print('solution: ', end=' ')
    print(solution)
else:
    raise Exception('could not find the solution.')

728x90
반응형

관련글 더보기