Notice
Recent Posts
Recent Comments
Link
«   2024/08   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

D1N0's hacking blog

00_angr_find 본문

Reversing/angr_ctf

00_angr_find

D1N0 2021. 2. 24. 22:55

들어가기 전에

이 글은 angr에 대해 배운 내용을 정리하는 글이다

C와 파이썬, 간단한 리버싱 지식을 기본 전제로 하고 있다

모든 문제는 github.com/jakespringer/angr_ctf를 바탕으로 하였고,

바이너리 파일은 github.com/Hustcw/Angr_Tutorial_For_CTF/tree/master/problems를 사용했다

또, 이를 활용한 블로그인 blog.notso.pro/2019-03-20-angr-introduction-part0를 바탕으로 공부하여 정리한 글임을 밝힌다

Angr란

우리가 일반적으로 리버싱 문제를 풀 때에는 암호화 함수를 역연산하여 원래의 비밀번호 등을 구한다

그러나 그 암호화 함수가 너무 길고 복잡하다면?

혹은 단순히 역연산 하기가 너무 귀찮다면? 이럴때 사용할 수 있는 것이 바로 angr이다

angr 깃허브 README 첫줄을 보면

angr is a platform-agnostic binary analysis framework.

라고 설명하고 있다

번역해보면 angr는 플랫폼에 구애받지 않는 바이너리 분석 프레임워크이라는 뜻이다

더 자세히 말하면 Symbolic 분석을 자동으로 해주는 파이썬 기반 프레임워크라고 할 수 있다

이에 대해선 나중에 알아보고, 일단 사용하기 전에 설치부터 해보겠다

설치

angr는 기본적으로 python3 라이브러리이다

python2로도 된다고 하지만 일단 나는 python3을 사용할 것이다

설치는 pip로 간단히 할 수 있다

pip3 install angr

pip 명령어는 상황에 맞게 사용하면 된다

무작정 시작

일단 angr를 사용해보도록 하겠다

처음에 말한 angr_ctf의 00번 문제를 angr를 사용해 풀어보자

 

먼저 프로그램을 분석해보겠다

undefined4 main(undefined4 param_1,undefined4 param_2)

{
  char cVar1;
  int iVar2;
  int in_GS_OFFSET;
  int local_24;
  char local_1d [9];
  int local_14;
  undefined *local_c;
  
  local_c = (undefined *)&param_1;
  local_14 = *(int *)(in_GS_OFFSET + 0x14);
  printf("Enter the password: ");
  __isoc99_scanf(&DAT_08048753,local_1d);
  local_24 = 0;
  while (local_24 < 8) {
    cVar1 = complex_function((int)local_1d[local_24],local_24);
    local_1d[local_24] = cVar1;
    local_24 = local_24 + 1;
  }
  iVar2 = strcmp(local_1d,"JACEJGCS");
  if (iVar2 == 0) {
    puts("Good Job.");
  }
  else {
    puts("Try again.");
  }
  if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

Ghidra를 통해 디컴파일해보았다

사실 뭘 쓰던 상관없고 분석만 할 수 있으면 된다

아무튼 이 프로그램은 local_1d에 문자열을 입력받고,

그 문자열의 글자를 하나씩 complex_fuction에 local_24와 같이 넣어서 새로 문자열을 저장하고, 

그 문자열을 JACEJGCS와 비교하여 같으면 Good Job. 다르면 Try again. 을 출력하는 프로그램이다

int complex_function(int param_1,int param_2)

{
  if ((0x40 < param_1) && (param_1 < 0x5b)) {
    return (param_1 + -0x41 + param_2 * 3) % 0x1a + 0x41;
  }
  puts("Try again.");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

complex_function은 이렇게 생겼다

역연산하라면 못할 것 없겠지만 귀찮으니 angr를 사용해보겠다

 

실제 비밀번호를 구하기 전에, 깃허브의 scaffold00.py를 살펴보자

import angr
import sys

def main(argv):
  path_to_binary = ???  # :string
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)
  
  print_good_address = ???  # :integer (probably in hexadecimal)
  simulation.explore(find=print_good_address)

  if simulation.found:
    solution_state = simulation.found[0]
    print solution_state.posix.dumps(sys.stdin.fileno())
    
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)
import angr
import sys

이 두 줄은 angr와 sys 라이브러리를 사용하기 위해 import 하는 부분이다

angr 라이브러리는 애초에 angr를 사용하는게 목적이니 import 하는 게 당연하고,

sys 라이브러리도 angr를 사용할 때 필요하다

sys 라이브러리는 인자를 받기 위해서와 표준 입출력에 어떤 글자들이 들어갔는지 알기 위해서도 사용된다

def main(argv):
  path_to_binary = ???  # :string
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)

첫째줄은 그냥 코드를 main함수로 뺀 것이니 의미있는 부분이 아니다

path_to_binary는 이름을 보면 알 수 있듯 바이너리 경로를 넣을 것이다

직접 파일 경로를 적어도 되지만, main에서 argv를 넘겨받았기 때문에 argv[1]을 넣어 프로그램 실행 시 파일 경로를 받도록 하겠다

project는 angr를 사용하기 위한 객체이고, 코드를 보면 angr.Project(바이너리 경로) 형태로 사용하는 것을 알 수 있다

angr에서는 파일을 불러올 때, Project함수를 사용한다

이 Project를 바탕으로 angr의 대부분의 작업이 이루어진다

 

initial_state는 아래에서 simgr를 사용하기 위한 state를 설정하는 것인데, 여기서는 entry_state() 함수로 바이너리의 entry_point를 state로 설정했다

이 state와 entry_state에 대한 내용은 나중에 더 깊게 다뤄보기로 하고, 지금은 그냥 프로그램을 entry point부터 실행하는구나 정도만 이해하고 넘어가면 된다

그다음 simulation 변수에 initial_state를 인자로 하여 simgr를 호출했다. simgr는 angr에서 사용할 수 있는 시뮬레이션 관리자(Simulation Manager)이다

angr에서 바이너리를 시뮬레이션 할때 이 simgr가 사용된다

자료를 찾다보면 simgr 대신 simulation_manager를 사용하는 것을 볼 수도 있는데, simgr가 그냥 줄임말일 뿐 똑같은 기능을 한다

결론적으로 이 부분은 바이너리 파일을 angr.Project로 불러온 뒤에, entry point를 시작 지점으로 하여 simgr를 호출하는 코드이다

이 부분이 angr에서 중요하긴 한데 지금 바로 이해하긴 어려우니 일단 angr에 익숙해진 뒤에 다시 보며 이게 무슨 의미인지 생각해보는 것을 추천한다

print_good_address = ???  # :integer (probably in hexadecimal)
simulation.explore(find=print_good_address)

그다음은 print_good_address에 무언가를 넣고, simulation.explore에 find인자로 넣는 코드이다

simgr는 explore 함수를 통해 원하는 바이너리의 실행 흐름을 찾을 수 있다

find 인자에는 프로그램이 가야 할 주소를 넣어주면 된다

우리가 바라는 Good Job를 출력하는 부분의 주소는 08048675이므로 print_good_address에는 0x08048675를 넣어주면 된다

if simulation.found:
  solution_state = simulation.found[0]
  print solution_state.posix.dumps(sys.stdin.fileno())
    
else:
  raise Exception('Could not find the solution')

이제 원하는 주소로 간 시뮬레이션 상황이 있었는지 찾는다

simulation.found가 존재한다면, 즉 목표 주소에 도달한 상황이 있다면 그중 첫 번째 상황을 solution_state에 넣는다

그리고 그 상황에서 표준 입력에 들어갔던 문자열을 출력해주면 비밀번호를 구할 수 있는 것이다

다만 이 코드는 python2를 염두하고 짜인 것이므로 print(solu~~fileno()) 형태로 고치도록 한다

아래에는 비밀번호를 찾지 못했을 때 에러를 띄우는 코드이다

 

최종 코드는 다음과 같다

import angr
import sys

def main(argv):
  path_to_binary = argv[1]  # :string
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)
  
  print_good_address = 0x08048675  # :integer (probably in hexadecimal)
  simulation.explore(find=print_good_address)

  if simulation.found:
    solution_state = simulation.found[0]
    print(solution_state.posix.dumps(sys.stdin.fileno()))
    
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)

쭉 정리해보자면,

1. angr.Project(파일 경로)로 원하는 파일의 Project 객체를 만든다

2. 이 project의 entry point를 시작 지점으로 하는 simgr를 만든다

3. 프로그램이 갔으면 하는 주소를 찾는다

4. 찾은 주소를 simulation.explore의 find인자로 넣고 실행한다

5. 만약 angr가 지정한 주소에 갔다면, 그때의 표준 입력이 뭐였는지를 출력해준다

 

python3 00_angr_find.py ./00_angr_find형태로 실행하면

이렇게 비밀번호가 나온다

나는 JXWVXRKX가 나왔고, 실제로 넣어보면 Good Job이 뜬다

 

지금까지 angr를 사용하여 프로그램이 어느 주소를 실행하면 좋겠는지 설정하고, 그때의 표준 입력이 뭐였을지 찾는 방법을 알아보았다

다음번에는 실행하고 싶은 주소뿐만 아니라 피하고 싶은 주소를 설정하는 방법을 알아보겠다

'Reversing > angr_ctf' 카테고리의 다른 글

04_angr_symbolic_stack  (0) 2021.04.10
03_angr_symbolic_registers  (0) 2021.03.26
02_angr_find_condition  (0) 2021.02.26
01_angr_avoid  (0) 2021.02.26
Comments