Sam Symons의 블로그의 내용을 발췌하여 번역한 후 직접 테스트하며 내용을 수정보완하였습니다.

원문 : https://samsymons.com/blog/reverse-engineering-with-radare2-part-1/


Radare2는 Unix 기반 시스템 환경에서의 리버스 엔지니어링을 할 수 있도록 돕는 프레임워크이며, 대부분 커맨드라인 인터페이스 형식으로 작동한다. 관련된 내용은 공식홈페이지Radare2 공식 가이드에서 확인할 수 있다. 리버스 엔지니어링은 소프트웨어 개발자에게 날개를 달아줄 수 있는 아주 환상적인 기술이라고 믿어 의심치 않는다. 이는 소프트웨어 내부에 숨겨진 비밀들을 발견하게 해주고, 디버깅, 컴파일러의 작동과정 등을 파악하게 하는 묘책이다. 하지만 다른 이유가 아니더라도, 도전하기에 충분할만큼 재미있다.


Radare2의 기능을 테스트해볼 수 있는 적절한 예시를 찾았다. RPISEC 이라는 미국 뉴욕 주 트로이의 렌셀러 폴리테크닉 대학교(Rensselaer Polytechnic Institute) 출신의 연구원들이 만든 기관이 있다. 그곳에는 여러가지 학습자료가 업로드되는데, 특히 2015년 개설되었던 바이너리 취약점 분석 강좌(CSCI 4968 - Modern Binary Exploitation)과 그 강의자료 Github이 상당히 유용하다. 이 강의는 기본적인 C언어에 대한 배경지식이 있는 사람들을 대상으로 하여 리버스 엔지니어링을 학습할 수 있도록 여러가지 도전과제를 적절한 난이도로 제시하고 있다. 그리하여 해당 챌린지 문제들을 Radare2와 gdb 등의 도구를 사용하여 풀이하는 해법을 제시하고자 한다.


Radare2 입문

우선 Radare2가 무엇인가?에 대한 의문을 해결해보자. 설명하기가 약간 난해하지만 단순하게 정의하자면 Software의 disassemble을 돕는 오픈소스 프레임워크라고 부를 수 있다. 일반적으로 RE(역공학)에서 요구되는 다수의 기능들을 지원하고 있어서 파일로부터 다양한 정보를 추출할 수 있다. 특히, Radare2는 굉장히 강력한 CLI 도구(일명 r2)로 구성되어 있어서 interactive shell을 통해 디스어셈블 작업을 수행할 수 있다. 만약 여러분이 IDA ProHopper 같은 다른 도구를 사용해본 경험이 있다면, 이해가 쉬울 것이다. 물론 IDA는 사용하기 쉽고 한눈에 알아보기 쉬운 좋은 소프트웨어이다. 그러나 Radare2는 서버에 설치해두고 원격지에서 자유자재로 활용할 수 있도록 하는 등의 장점이 있기에 나는 더 선호한다. Smash The Stack 같은 CTF 챌린지 대회에서도 radare2를 설치해두고 적극 활용하고 있다.


준비하기

문제풀이를 시작하기 위해 다운로드하자. 이는 RPISEC 2주차 강좌에서 소개된 과제물이다. 

그리고 Radare2를 설치하자. macOS 운영체제라면 아래와 같은 명령어로 설치할 수 있다.

리눅스 시스템에서는 아래와 같이 설치한다. (설치 중 sudo 권한이 요구된다)

다른 운영체제라면 radare 공식 홈페이지의 설치 가이드 문서를 참조하기 바란다. 참고적으로, 여기에서 다루는 챌린지 문제들은 모두 리눅스의 ELF 32bit 바이너리들이다. 그러므로 이것들을 실행하기 위해서는 32bit의 리눅스 시스템을 준비하는 것이 좋은데, 필자는 VMware Workstation 환경에서 Ubuntu Linux 가상머신을 구축해두고 시연하였다. 그밖의 Amazon AWSDigital Ocean등의 클라우드 서비스에서 저렴한 인스턴스 하나를 마련하는 것도 추천한다. 만약 직접 파일을 실행해보지 않고 그저 정적(static)분석 위주로만 학습할 생각이라면 굳이 Linux 없이 윈도우나 macOS 상에서도 분석할 수는 있다.


여러분이 최소한 x86 assembly에 대한 기본적인 배경지식이 있다고 간주하고 설명하도록 하겠다. 만약 그렇지 않다면 조나단 바틀렛의 '밑바닥 부터 시작하는 프로그래밍(Programming from the Ground Up Book)'책을 추천한다. 그리고 리버스 엔지니어링과 관련해서는 브루스 댕의 '역공학 X86, X64, ARM, 윈도우 커널, 역공학 도구, 그리고 난독화(Practical Reverse Engineering: x86, x64, ARM, Windows Kernel, Reversing Tools, and Obfuscation., 비제이퍼블릭 역간)을 읽는 것도 추천한다. 이 책은 ARM assembly와 Windows 커널 영역에 대한 지식까지 커버한다.

 

(이 블로그 포스트에서 다룰 수 없는 자세한 부분까지 학습하기 원한다면 RPISEC의 강좌 사이트에서 조금 더 심도있는 연구를 수행하기 바란다.)



Challenge 1 : crackme0x00a

우선 다운받은 파일을 압축을 풀고, 그중 첫번째로 ./crackme0x00a 을 실행해보자.

위의 그림과 같이, password를 입력하라는 메시지가 나온다. 만약 성공하지 못한다면 다음 단계로 넘어갈 수 없다. Brute Force 방식을 떠올릴 수 있겠지만 그닥 효과적인 방법은 아닐 것이다. 좋은 해결책은 이 바이너리를 디스어셈블하여 그 내부로부터 힌트를 얻는 것이다!


Radare2를 본격적으로 사용해보자. 기본적으로 커맨드라인 인터페이스이기 때문에 터미널에서 r2라는 명령어를 사용하여 접근할 수 있다. 우선 파일명인 crackme0x00a을 파라미터로 전달하고, 기타 다른 옵션들을 설정할 수도 있다.

혹시 메타스플로잇을 사용해본 경험이 있다면 이러한 인터렉티브 쉘이 익숙할 것이다. 위와 같이 radare2 쉘 내부에서도 얼마든지 쉘 밖의 기본 명령어들 (pwd, cd 등을)을 이용할 수 있다. 그렇기 때문에 분석중에도 언제든지 외부의 다른 디렉토리로 이동하여 정보를 파악할 수 있다. 


r2명령어를 시작하면 우선 초기 인사말 메시지가 랜덤하게 출력된다. (--Check your IO plugins with 'r2 -L') 이 메시지는 사용할 때마다 바뀐다. 그리고 [0x08048430]이라는 주소값이 >으로 체크되어 있는데, 이곳이 바로 해당 파일에 현재 위치한 메모리 주소를 의미한다. 만약 다음 16자리의 hex값을 출력하고 싶다면 다음과 같이 시도해보자.

단순히 출력하는 것 말고 바이너리 파일 내부를 체계적으로 탐색하려면 seek 명령어인 s를 사용한다. s에 대한 자세한 사용방법은 help 명령어인 ? 를 사용해서 더 자세히 메뉴얼을 참고할 수 있다. 이는 r2의 어떤 명령어라도 다 적용된다. 그러므로 s명령어를 조금 더 자세히 파악하고 싶다면 s?를 입력하라.

본격적으로 분석을 하는데 쓰이는 명령어는 a command이다. 하지만 조금 더 정확히는 그와 비슷한 aa 명령어가 더욱 자주 쓰인다. 이는 Analyze All 의 약자로, 바이너리 전체를 찾아서 symbol 영역 등을 분석해주는 아주 훌륭한 명령어이다. 


그렇다면 이제 함수(funtion)들을 디스어셈블링 해보자. Radare2는 pdf(Print Disassemble Funtion) 명령어를 제공한다. 만약 pdf @ main 을 입력하면 main function을 분석하여 어셈블리 형태의 결과물을 보여줄 것이다. 아래와 같이 입력하여 main 함수의 디스어셈블 결과를 얻어보자.

이 정보를 보면 Radare2가 제공하는 기능들을 엿볼 수 있는데, 첫째로 예술적인 ASCII 프린팅만으로 프로그램의 제어 흐름을 보여주고 있다. 둘째로 바이너리 내부에 포함되어 있는 주석(comments) 등은 strings 기법으로 문자열이 다 추출되어 보이게 된다. 셋째로 스트링은 str이라는 전치사로 시작하고, import 관련 정보는 imp로 표시된다.

화면의 30번째 줄과 31번째 줄 근처의 어셈블리 코드를 살펴보자.

strcmp(문자열을 비교하는 함수)를 실행하여 g00dJ0B! 과 일치하는지 판단하는 구문이 있다. 이를 기준으로 실패하면 Wrong!을 띄운 후 다시 패스워드를 입력하는 곳으로 되돌아간다. 만약 맞으면 성공(Congrats!)이 되는 구조이다. 그렇다면 이제 g00dJ0B! 을 입력해보자.

성공했다! 물론 챌린지에서 요구하는 몇가지 추가적인 관문들이 있었지만, 우선 이런식으로 문제를 풀 수 있다는 것을 깨달았으면 그것으로 충분히 좋은 시작이었다. 어쨌거나 답을 맞췄기 때문이다. 그렇다면 다음 문제로 가보자.



Challenge 2: crackme0x00b

이제는 2라운드다. r2를 사용하여 crackme0x00b 파일을 열고, aa 명령어로 파일과 심볼 정보를 분석해보자. 1에서 했던 것 처럼 pdf @ main 명령어를 치면 main 함수에 대한 디스어셈블된 코드가 표출된다. 전체 내용은 너무 방대해서 뭐가 중요한지 쉽사리 파악하기 어렵다.

그 중 특히 아래의 내용에 주목해보자. (원문 글의 버전에는 str.w0wgreat 이 표출되지 않으나, 현시점 최신버전에서는 자동으로 스트링 정보가 표출됨)

wcscmp 라는 함수를 사용하고 있다. MSDN에서 관련 내용을 찾아보았다. (아래 링크 참조)

The wcscmp() function is the wide-character equivalent of the strcmp(3) function. It compares the wide-character string pointed to by s1 and the wide-character string pointed to by s2.

이 함수는 결국 strcmp와 동일한 역할을 하는 기능이지만, strcmp가 자료형 char*로 된 문자열을 비교하는 것이라면 wcscmp는 wchar_t*로 된 문자열을 비교하는 것이다. 그래서 유니코드 문자열이라면 wcscmp()를 쓰는 것이다. wcscmp 함수의 파라미터로 전달된 문자열 중에서 Null 문자 앞까지의 모든 내용을 읽어오게 되어있다. 그래서 정확한 패스워드를 알기 위해서는 메모리에 어떤 값이 전달되는지를 자세히 들여다보아야 한다. 다행히 Radare2가 obj.pass.1964라는 힌트를 주석으로 제공하고 있다. 그렇다면 obj.pass.1964의 위치에 있는 스트링을 찾아보자. 참고적으로 특정 위치에 있는 Hex값을 출력하기 위한 명령어는 print hex, 즉 px이다.

Radare2는 32 비트 단위로 문자를 읽어서 보여주고 있는데, 한 글자당 8개의 Hex값으로 이루어지고, 각 Hex값은 4bit로 되어 있다. 이것을 ASCII 형식으로 치환한 내용이 화면의 오른쪽 부분에 병행표기되어 있다. 'w0wgreat'라는 문자열이 있다. (현시점 최신버전의 radare2에서는 자동으로 'str.w0wgreat'라는 정보가 표출되어 손쉽게 파악할 수 있음.) crackme0x00b를 실행하고 정답을 입력하면 된다.



Challenge 3: crackme0x01

그동안 crackme0x00a과 crackme0x00b를 풀어보았다. 여기까지는 거의 예선전이었고, 앞으로 분석할 바이너리가 crackme0x01 부터 crackme0x09까지 아홉개나 된다. 우선은 본 포스팅에서 crackme0x01를 풀이해보고, 나머지 부분은 향후를 기약한다.

crackme0x001은 또다른 차원의 재미있는 퀴즈이다. 실행했을 때 Password를 물어보는 방식은 이전과 동일하다. 우선 앞서했었던 기본적인 동작을 통해 분석하고 디스어셈블하자. main 함수를 잘 들여다보면 빠르게 힌트를 획득할 수 있다. 


여기에서 보면 scanf가 실행된 뒤에 그 결과값에 따라서 jump하여 2종류의 printf() 함수를 호출하는 부분으로 간다. 즉 scanf 결과값이 정답과 일치하면 str.Password_OK_:__n을 출력하고, 아니면 str.Invalid_Password__n 을 출력하도록 되어있다. 그렇다면 scanf() 후에 어떤 값과 비교하는 로직을 찾아야 한다. 중요한 힌트는 scanf()의 인자로 전달되는 %d이다. radare2가 다음과같이 주석으로 표시해주고 있다.

원래는 mov dword [esp], 0x804854c 의 명령어에서 esp로 넘어오는 0x804854c 값을 px로 찾아보면 %d가 나오는 것이며, 실제 해보면 일치한다.

%d는 C언어에서 정수 형식의 숫자를 처리하는 키워드이다. 그러므로 Password는 문자열이 아닌 숫자 형식일 것이다. 그렇다면 그것이 무엇과 cmp되는지를 확인해보자.

이 구문은 사용자가 입력한 값과, 하드코딩된 특정 값(상수)인 0x149a를 비교한다. 그런데 한가지 문제가 있다. 프로그램 내부에서 저장되어 있는 상수는 16진수 값이고, 사용자가 일반적으로 입력하는 값은 10진수이다. 그렇다면 0x149a가 10진수로 무엇일까? 그냥 편하게 계산기로 계산할 수 있다.

그러나 보통 CTF 문제푸는 사람들은 조금 더 까리한(?) 방법을 쓴다. 보통 파이썬을 쓰면 아래와 같다.

그러나 지금은 Radare2를 최대한 활용해보고자하므로, Radare2에 포함되어있는 rax2를 써보자.

이런식으로 입력을 하면 바로 결과가 나온다. 또한, 만약 r2 커맨드 내부에서 r2를 종료하거나 새창을 띄우지 않은 상태로 빠르게 값만 확인하고 싶다면 아래와 같이 ? 0x149a 만 입력하자. 풀 패키지가 출력된다.

어쨌거나 그래서 정답은 5274이다.


마치며

본 포스팅을 통해 여러분이 Radare2의 파워풀한 기능을 조금이나마 이해할 수 있었기를 바란다. 사실 r2는 보다 훌륭한 그래픽 모드를 지원하기도 하며 더 편리한 기능들을 제공하고 있다. 하지만 여기에서는 표면적인 구성 위주로 빠르게 훑어보는데 주안점을 두었다. 방금의 도전과제들은 매우 간단한 수준이었지만, 이것을 출발점으로 삼아서 앞으로 여러가지 고난이도 프로젝트(Stack Buffer Overflow, 암호화 해제, ROP 등)들을 Radare2로 해결해보기 바란다. 


그럼 Happy Hacking !


원문 블로그 : 

CPUU님의 창작활동을 응원하고 싶으세요?