Reverse Engineering: 역공학
흔히 Reversing, 리버싱이라고 부르기도 한다.
IT 유튜버들 중에서는, 제품을 뜯어보면서 여기에는 무슨 기술이 들어가있고, 어떤 부품을 써서 어떤 기능을 구현했고-
이런 설명을 해주시는 분들이 계신다. 이것도 말하자면 리버싱이라고 볼 수 있겠다.
소프트웨어에서 리버싱을 하는 것은 이것과 크게 다르지 않다.
여러 분석 (바이너리, 메모리, 등)을 거쳐 프로그램의 소스코드를 알아내가는 과정이라고 보면 된다.
크게 역공학에는 두 가지 분석 방법이 있다.
1) Dynamic
- 실행을 시키면서 디버거(gdb, qemu, 등) 를 통해 실행의 흐름을 파악하며 단계적 분석
2) Static
- 실행을 시키지 않고 해당 executable file의 여러가지 구성요소를 분석
어쨌든, 이 게시글에서는 Dynamic/Static한 리버싱을 구체적으로 비교하기 보다는, 컴퓨터공학 분야에서 세계적으로 손꼽히는 대학 중 하나인 Carnegie Mellon University에서 시스템 프로그래밍을 공부하는 학생들을 위해 만들어낸 'Bomb Lab'의 큰 그림을 설명해보려고 한다.
Bomb Lab을 왜 하는 것이며, 무엇을 하는 것인지 갈피를 잡으시는 데 도움이 되어드렸으면 한다.
Bomb Lab을 수행하려면 두 가지를 확실히 알아야 한다.
1) 어셈블리 코드
2) gdb 사용법
#1. Assembly Code

우리가 어셈블리 코드를 이용해서 분석을 하는 이유는 소스코드가 결국 Executable Binary Code로 변환 되었을 때,
이 010101...들과 일대일 대응을 하는 코드가 바로 어셈블리 코드이기 때문이다.
즉, 어셈블리 코드만 보고도 일대일 대응 관계를 이용하여 소스코드를 모르는 상태에서도 소스코드를 역으로 추적 할 수 있게 되는 것이다.

어셈블리 코드를 생성하는 CPU architecture에 따라서 생성되는 어셈블리 코드가 달라진다.
어셈블리 코드를 분석 할 줄 알기 위해서 알아야 되는 것은 본인이 사용하는 CPU architecture의 레지스터들에 대한 개념이다.
이 글은 x64 어셈블리 코드를 기준으로 작성하였다.

레지스터는 '저장장치' 이다. 메모리와 더불어 CPU가 접근할 수 있는 두 저장장치 중 하나이고, 말 그대로 데이터를 저장한다.
레지스터 하나를 뜯어보면:

이렇게 생겼다.
x64 레지스터는 하나당 8bytes (64bits) 를 수용할 수 있지만, 다루고자 하는 데이터 타입에 따라서 이 '용량'을 전부 사용할 수도, 그렇지 않을 수도 있다. 가령, 32bit-data를 다루려고 한다면 %esi에만 접근하면 되는 것이다.
특정 레지스터들은 특별한 목적을 갖고 있는 경우도 있다. 다음은 단편적인 두 예시이다.
- rsp, 스택 포인터
: '스택'의 위치를 기억하는 레지스터 (스택의 top을 항상 가리키고 있다) - rip, instruction 포인터 (레시 수업때 rest in peace가 아니라고 지나가듯 농담을 던졌지만 아무도 웃어주지 않았음)
: 다음 instruction 위치를 기억하는 레지스터
함수를 호출했을 때 인자들을 관리하기가 레지스터 만으로는 부족하다면 stack area에 저장해야 된다.
확실히 알아 둘 개념은 stack area에 데이터를 저장하는 것 보다 레지스터를 활용하는 것이 빠르다는 것. 따라서 레지스터 수를 늘리는게 좋지만, CPU 구조상의 한계 등으로 마냥 늘릴 수가 없다. 기술이 발전할수록 이러한 한계를 점점 뛰어넘는 것이라고 생각하면 될 것 같다!
어셈블리어의 문법은 따로 정리하지 않겠다. 어셈블리어가 무엇인지, 그리고 레지스터가 무엇인지 갈피를 잡았다면, x86 어셈블리의 연산에 관한 문서를 찾아보시길.
#2. gdb: GNU debugger
보아하니 gdb를 이용해서(디버깅 툴을 이용해서) 과제를 수행하라는 것은, Bomb Lab 과제가 결국 버그 투성이인 프로그램을 디버깅 하는 것인가요?
시스템 프로그래밍 학부 수업 레시테이션을 조교로서 진행했을 당시 한 수강생이 나에게 한 질문이고,
나 또한 수강생 입장이었을 당시 들었던 의문이다.
직접적으로 디버깅을 하는게 아니라, 디버깅 툴이 갖고 있는 기능을 이용하여 문제를 해결하는 것, 즉 디버깅 툴 사용 훈련을 하는 것이다.
'디버깅 툴' 역할을 하는 것에는 여러가지가 있다.
대표적이고, 간단하고, 가장 강력한 디버깅 툴로는 assert, printf 가 있겠다.
gdb는 이러한 디버깅 툴 중 하나이다. gdb의 기능을 사용하여 (특정 주소값, 혹은 레지스터에 저장되어 있는 데이터를 읽는 등의 기능) 문제를 풀어내는 과제라고 생각하시면 될 것 같다.
gdb 사용법에 대해서 너무 구체적으로 들어가진 않겠다. 찾아보시면 훨씬 더 구체적으로 자세한 문서들이 많을 테니.
다만, Bomb Lab을 진행하셔야 되는 분들께 가장 크게 도움 될 수 있는 gdb instruction 몇 가지를 짚어드리자면, 다음과 같다.
첫 번째로, 특정 주소값(혹은 레지스터)에 있는 데이터를 읽을 수 있는 명령어이다.

다음으로, 반드시 하셔야 될 breakpoint 설정이다.

과제를 수행하면서 gdb를 이용해서 bomb 파일을 disassemble 하게 될 것이고,
그 결과물로서 assembly 코드를 확인하며 소스코드를 추적하여 bomb을 defuse 하게 될 것이다.
이때 특정 코드 다음은 수행되지 않도록 breakpoint를 설정해주는 것이 매우 중요하다. 자칫하면 bomb을 터뜨릴 수 있기 때문에!
Bomb Lab 과제를 수행하는 학교가 많을텐데, 사실 많은 분들께서 문제들에 대한 해답까지 작성해두신 글들이 많다.
해당 게시글은 본인이 시스템프로그래밍 과목 수업 조교를 하며 레시테이션을 위해 직접 만들었던 슬라이드를 기반으로 Bomb Lab에 대한 큰 그림을 이해하는데 도움이 되어드리고자 작성했다.
*오개념 지적 대환영*