<리버싱 핵심 원리>로 공부하였다.
+소스 코드 및 실습 예제 링크 : https://github.com/reversecore/book/tree/master/%EC%8B%A4%EC%8A%B5%EC%98%88%EC%A0%9C/01_%EA%B8%B0%EC%B4%88_%EB%A6%AC%EB%B2%84%EC%8B%B1
02 Hello World! 리버싱
2.1. Hellow World! 프로그램
![]() |
![]() |
왼쪽이 디버그 모드, 오른쪽이 릴리즈 모드로 빌드한건데 실행 결과는 차이가 없다는 것을 알 수 있다.
Debug모드 vs Release모드 빌드의 차이점 : https://coding-factory.tistory.com/648
위 링크에서 설명을 잘해놓았다. 쉽게 말하자면 release모드로 빌드 되었을 때 코드가 훨씬 간결해져서 보기 편하다.
이러한 이유로 우리는 Release 모드로 빌드한 exe 파일을 활용할 것이다.
2.2. helloworld.exe 디버깅
목표 : 실행 파일 디버깅 후 어셈블리 언어로 변환된 메인 함수 찾기
OllyDbg로 helloworld.exe를 열어본 화면이다. 왼쪽 상단 화면은 코드 윈도우, 오른쪽 상단 화면은 레지스터 윈도우, 왼쪽 하단 화면은 덤프 윈도우, 마지막 오른쪽 하단은 스택 윈도우가 보여진다.
1) 코드 윈도우 : 디스어셈블리 코드를 표시하여 각종 주석과 label을 보여주며 코드를 분석해 점프 위치 등 정보를 표시
2) 레지스터 윈도우 : CPU 레지스터 값을 실시간으로 표시하여 특정 레지스터들은 수정도 가능
3) 덤프 윈도우 : 프로세스에서 원하는 메모리 주소 위치를 Hex와 아스키/유니코드 값으로 표시하고 수정도 가능
4) 스택 윈도우 : ESP 레지스터가 가리키는 프로세스 stack memory를 실시간으로 표시하고 수정도 가능
디버거에서 가장 먼저 실행하면 위 사진 속 코드가 가장 위에 표시된다. 가장 처음 보이는 곳은 EP, 코드 시작점이다. CPU에 의해 가장 먼저 실행되는 코드 시작 위치인 곳으로 이 프로그램에서는 13A126F이다.
위 코드 두 줄의 의미 => 13A1638 주소의 함수를 호출(CALL)한다. 013A103D 주소로 점프(JMP)한다.
OllyDbg 기본 단축키
Restart : Ctrl + F2 (디버깅 처음부터 다시 시작)
Step Into : F7 (코드 한 줄 실행, CALL 만나면 코드 내부로 들어감)
Step Over : F8 (코드 한 줄 실행, CALL 만나면 함수 자체 실행함)
Execute till Return : Ctrl + F9 (함수 코드 내에서 RETN 명령어까지 실행(함수 탈출 목적))
그럼 이제 본격적으로 메인 함수를 찾아봅시다.
EP 부터 RETN까지의 코드 윈도우 화면이다. 가장 위에 있는 13A1638 함수 안으로 들어가보자.
들어왔지만 원본 소스코드에서 사용된 API들이 보이지 않는다.(API는 빨간색으로 표시됨)
코드 흐름도 원본코드의 내용이 없다는 것을 알 수 있다. 이 부분은 소스코드에서는 보이지 않지만 비주얼 스튜디오내에서 프로그램 실행을 위해 추가시킨 코드 부분(Stub Code)이다. 여기는 일단 넘기고 메인 함수를 계속 찾아보자.
빠져나온뒤, JMP문을 실행시키면 13A10ED로 점프한다.
여기도 꽤나 복잡한 코드가 나타나지만 이 역시 Stub Code이다. 이 코드를 따라가다 보면 메인함수가 나타난다.
Stub Code는 눈에 잘 익혀두기!(나중에 빨리 건너뛰기 가능, 각 개발 도구의 종류와 버전에 따라 달라짐)
이 곳에서 F7을 누르고 내려가다 보면 세번째 줄에서 13A1930 함수 내부로 들어가게 된다.
하지만 여기도 메인함수라고 보긴 힘들다. 왜냐하면 MessageBox() API 호출 코드가 보이지 않기 때문이다. 여기서 Ctrl+F9를 사용하면 한번에 RETN 명령어 위치까지 디버깅이 진행된다. 손쉽게 현재 함수에서 탈출이 가능한 것이다.
이런식으로 반복하면서 메인 함수를 찾으면된다.
여러번의 삽질끝에 드디어 찾았다. 함수 주소들이 많이 다른걸 볼 수 있는데 재시작할 때마다 주소가 바뀌어서 그렇다..
2.3. 디버거 능숙하게 다루기
디버거 추가 명령어
Go to | Ctrl + G | 원하는 주소로 이동 |
Execute till Cursor | F4 | cursor 위치까지 실행 |
Comment | ; | comment 추가(주석 추가) |
User-defined comment | 우클릭 -> Search for -> all user comment | |
Label | : | label 추가 |
User-defined label | 우클릭 -> Search for -> user defined label(안보임) | |
Set/Reset BreakPoint | F2 | BP 설정/해제 |
Run | F9 | 실행(BP 존재시 그곳에서 실행 정지) |
Show the current EIP | * | 현재 EIP 위치 보여줌 |
Show the previous Cursor | - | 직전 커서 위치 보여줌 |
Preview CALL/JMP address | Enter | 커서가 CALL/JMP 명령어에 위치해 있으면 해당 주소를 따라가서 보여줌(실행X) |
베이스캠프 : 한마디로 책갈피 비슷함
베이스캠프 설치 방법
1. Goto 명령 : 주소를 기억해 놓았다가 재시작 했을 때 그 주소를 입력해 이동.
2. BP 설치 : 해당 위치에 F2로 BP를 설치한 후 F9하는 방법(가장 많이 이용)
View - Breakpoints : Breakpoints 목록 확인, 더블클릭시 해당 주소로 이동
3. 주석 : 주석을 단 후 주석 찾아가는 방법
우클릭 - Search for - User - defined comment
4. 레이블 : 원하는 주소에 특정 이름을 붙여주는 레이블 기능을 이용해 찾아가는 방법
우클릭 - Search for - User defined labels
2.4. 원하는 코드를 빨리 찾아내는 방법
1. 코드 실행 방법
코드의 크기가 작고 기능이 명확한 경우에 사용할 수 있는 방법.
한 줄씩 F8로 실행해가다 메인 함수 속 내용이 실행되면 그게 메인 함수인 것이다.
2. 문자열 검색 방법
우클릭 - Search for - All referenced text strings를 사용해 참조되는 문자열을 한 번에 볼 수 있다.
그럼 메인함수 속에 사용되었던 문자열이 있는 곳을 누르면 손쉽게 메인함수를 찾는 것이 가능하다.
(+코드와 데이터 영역은 나뉘어져 있다!=>hello world 등 문자열이 저장돼있는 주소는 코드 영역의 주소와 다른 영역)
3. API 검색 방법(1) - 호출 코드에 BP
우클릭 - Search for - ALL intermodular calls
Windows 프로그래밍에서 모니터 화면에 출력하려면 Win32 API를 사용해 OS에게 화면출력을 요청해야 한다.
이렇게 기능에 따라 호출되었을 API를 예상해 그걸 찾는 방법이다. 우리가 했던 실습의 경우에는 메시지 박스를 출력하므로 user32.MessageBoxW() API를 사용했을 거라 추측할 수 있다. OllyDbg는 사용되는 API 함수 목록도 정리하는 기능이 있다. 이 방법 역시 더블클릭으로 해당 주소로 이동도 가능하므로 쉽게 원하는 부분을 찾아낼 수 있다.
4. API 검색 방법(2) - API 코드에 직접 BP
우클릭 - Search for - Name in all modules
Ollydbg가 모든 실행 파일에 대해서 API 함수 호출 목록을 추출할 수 있는 것은 아니다. 만약 Packer/Protector로 실행파일을 압축 or 보호하면 파일 구조가 변경돼 API 호출 목록이 보이지 않는다. (디버깅 자체가 어려워짐)
이 경우에 프로세스 메모리에 로딩된 라이브러리(DLL 코드)에 직접 BP를 걸어 본다.
API는 OS에서 제공한 함수이고 실제로 API는 C:\Windows\system32 폴더에 *.dll 파일 내부에 구현되어 있다.
즉, 우리가 만든 프로그램이 어떤 의미 있는 일을 하려면 반드시 os에서 제공된 api를 사용해서 os에게 요청해야 하고 그 api가 실제 구현된 시스템 dll 파일들은 우리 프로그램의 프로세스 메모리에 로딩되어야 한다.
OllyDbg에서 이를 확인해보자.
View - Memory를 누르면 확인할 수 있다.
이렇게 프로세스 메모리를 확인할 수 있고 위 사진에서 USER32 라이브러리가 로딩되어 있는 메모리 영역을 볼 수 있다.
또한 OllyDbg는 실행을 위해 로딩된 시스템 DLL 파일이 제공하는 모든 API 목록을 보여주는 기능이 있다.
여기서 MessageBoxW를 더블클릭하면 구현된 실제 함수가 나타난다.
그리고 해당 부분의 주소는 앞서 우리가 찾았던 9DXXXX 와는 확연히 다른 주소라는 것을 알 수 있다. 이곳에 BP를 설치하고 실행해보자.
MessageBoxW 코드 시작에 설치한 BP에서 실행이 멈췄다. 레지스터 창의 ESP값이 2AFE60인데 이는 프로세스 스택의 주소이다. 아래는 스택 윈도우의 화면이다.
ESP의 값에 있는 리턴 주소는 9D1014로 앞서 찾았던 메인함수 내 MessageBoxW 함수 바로 다음 코드 주소이다.
즉, MessageBoxW 함수의 RETN 명령까지 실행한 다음, RETN 명령도 실행하면, 리턴 주소 9D1014로 갈 수 있다.
9D1014 주소의 바로 위인 9D100E에 MessageBoxW 함수 호출 코드가 있는 걸 확인할 수 있다.
2.5. "Hello World!" 문자열 패치
목표 : helloworld.exe의 메시지 박스에 표시되는 "Hello World!" 문자열을 다른 문자열로 패치하기
문자열 패치 방법
1. 문자열 버퍼를 직접 수정
MessageBoxW 함수의 전달인자 9D2128의 문자열 버퍼를 직접 수정하는 방법이다. 덤프창에서 해당 주소로 이동한다. 그리고 주소를 마우스로 선택한수 Ctrl+E로 편집창을 띄운다.
띄운 창에서 문자열을 변경한다. 변경된 문자열은 빨간색으로 표시된다. 참고로 NULL은 UNICODE 입력창에서 입력 불가하니 HEX에서 입력해야한다.
※ 만약 실제상황에서 원본 문자열보다 긴 문자열로 덮어쓰고자 한다면 그 뒤 데이터를 훼손하지 않도록 조심해야한다. 뒤에 중요한 데이터가 있었다면 프로그램에서 메모리 참조 에러가 발생할 수 있기 때문이다.
직후 F9로 실행해보면 잘 반영된 것을 알 수 있다.(뒤까지 문자열로 들어가서 저렇게 나온걸로 예상 아무튼 변경 성공)
이 방법은 간단하다는 장점이 있지만 앞서 언급했듯이 더 긴 문자열로 대체하기는 힘들다.(문제 발생 가능성 아주 높음)
또한 이건 임시적으로 한 것이라 디버거 종료시 패치했던 내용은 사라진다. 영구 보존하려면 실행파일로 저장해야한다.
변경된 내용을 선택해 우클릭 - Edit - Copy to executable을 선택하면 아래 창이 나타난다.
여기서 우클릭 - Save file을 누르면 저장할 수 있다.(대화창 나오면 yes 누르면 된다.)
잘 생성된 것을 확인할 수 있다.
2. 다른 메모리 영역에 새로운 문자열을 생성하여 전달
앞 방법의 단점을 해소할 수 있는 방법이다.
재시작 한 후 다시 메인함수 부분을 봐보자. 덤프창에서 문자열들 주소로 다시 간 후에 쭉 내리면 위와 같이 프로그램에 사용되지 않는 NULL padding 영역이 나온다. 이곳을 문자열 버퍼로 사용해서 MessageBoxW 함수에 넘겨주는 방법을 이용하는게 바로 2번째 방법이다.
이렇게 입력해준 후 MessageBoxW 함수에게 새로운 버퍼 주소를 파라미터로 전달해줘야 한다. 변경할 코드에서 스페이스바를 누르면 편집창이 뜬다.
우리가 새롭게 문자열을 입력했던 덤프 주소로 변경해 입력해주면된다.
그럼 위와 같이 변경된 내용이 적용된 것을 확인할 수 있다.