106

리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

  • View
    463

  • Download
    35

Embed Size (px)

DESCRIPTION

강병탁 지음 | 해킹 & 보안 시리즈_005 | ISBN: 9788998139728 | 35,000원 | 2014년 11월 20일 발행 | 604쪽

Citation preview

Page 1: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학
Page 2: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학
Page 3: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블코드 재창조의 미학

Page 4: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

지은이 강병탁 | 감수 김휘강

펴낸이 박찬규 | 엮은이 이대엽 | 표지디자인 아로와 & 아로와나

펴낸곳 위키북스 | 주소 경기도 파주시 문발로 115, 세종출판벤처타운 311호

전화 031-955-3658, 3659 | 팩스 031-955-3660

재출간판1쇄발행 2014년 11월 21일

등록번호 제406-2006-000036호 | 등록일자 2006년 05월 19일

홈페이지 wikibook.co.kr | 전자우편 [email protected]

리버스 엔지니어링 바이블(재출간판)

코드 재창조의 미학

ISBN 978-89-98139-72-8 (93000)

이 책의 한국어판 저작권은 저작권자와의 독점 계약으로 위키북스가 소유합니다.

신 저작권법에 의해 한국 내에서 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다.

이 책의 내용에 대한 추가 지원과 문의는 위키북스 출판사 홈페이지 wikibook.co.kr이나

이메일 [email protected]을 이용해 주세요.

이 도서의 국립중앙도서관 출판예정도서목록(CIP)은 서지정보유통지원시스템 홈페이지(http://seoji.nl.go.kr)와 국가자료공동목록시스템(http://

www.nl.go.kr/kolisnet)에서 이용하실 수 있습니다.(CIP제어번호: CIP2014032111)

Reverse Engineering Bible Reprint EditionCopyright © 2014 by 강병탁All rights reserved.First published in Korea in 2014 by WIKIBOOKS

Page 5: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학
Page 6: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

IV

01

리버스 엔지니어링 기본

01. 리버스 엔지니어링만을 위한 어셈블리 ..............................................................................................................................................................................................................1

어셈블리의 기본 구조 ..............................................................................................................................................................................................................................................................................................3

어셈블리의 명령 포맷 ................................................................................................................................................................................................................................................................................................5

레지스터, 복잡한 설명은 그만 ......................................................................................................................................................................................................................................................................6

EAX .......................................................................................................................................................................................................................................................................................................................................................7

EDX .......................................................................................................................................................................................................................................................................................................................................................7

ECX .......................................................................................................................................................................................................................................................................................................................................................7

EBX ........................................................................................................................................................................................................................................................................................................................................................8

ESI, EDI ...........................................................................................................................................................................................................................................................................................................................................8

외울 필요가 없는 어셈블리 명령어 ..................................................................................................................................................................................................................................................15

PUSH, POP ....................................................................................................................................................................................................................................................................................................16

MOV ..........................................................................................................................................................................................................................................................................................................................16

LEA ................................................................................................................................................................................................................................................................................................................................16

ADD ............................................................................................................................................................................................................................................................................................................................17

SUB ...............................................................................................................................................................................................................................................................................................................................17

INT ..................................................................................................................................................................................................................................................................................................................................17

CALL ...........................................................................................................................................................................................................................................................................................................................17

INC, DEC ............................................................................................................................................................................................................................................................................................................18

AND, OR, XOR .......................................................................................................................................................................................................................................................................................18

NOP .............................................................................................................................................................................................................................................................................................................................18

CMP, JMP ........................................................................................................................................................................................................................................................................................................18

리버스 엔지니어링에 필요한 스택 ..................................................................................................................................................................................................................................................18

목차

Page 7: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

V

함수의 호출 .................................................................................................................................................................................................................................................................................................................................19

리턴 주소 .........................................................................................................................................................................................................................................................................................................................................20

02. C 문법과 디스어셈블링 ........................................................................................................................................................................................................................................................................22

함수의 기본 구조 ................................................................................................................................................................................................................................................................................................................23

함수의 호출 규약 ................................................................................................................................................................................................................................................................................................................25

if 문 ..........................................................................................................................................................................................................................................................................................................................................................31

반복문 ..................................................................................................................................................................................................................................................................................................................................................34

구조체와 API Call ...........................................................................................................................................................................................................................................................................................................36

결론 ..........................................................................................................................................................................................................................................................................................................................................................44

03. C++ 클래스와 리버스 엔지니어링 ...............................................................................................................................................................................................................................45

C++ 분석의 난해함 ........................................................................................................................................................................................................................................................................................................46

클래스 뼈대 .................................................................................................................................................................................................................................................................................................................................47

클래스의 수명과 전역변수 ..................................................................................................................................................................................................................................................................................53

객체의 동적 할당과 해제 ........................................................................................................................................................................................................................................................................................55

생성자와 소멸자 ..................................................................................................................................................................................................................................................................................................................58

캡슐화 분석 .................................................................................................................................................................................................................................................................................................................................63

다형성 구조 파악 ................................................................................................................................................................................................................................................................................................................64

04. DLL 분석 .............................................................................................................................................................................................................................................................................................................................67

DLL의 번지 계산법 .........................................................................................................................................................................................................................................................................................................68

재배치를 고려한 방법 .................................................................................................................................................................................................................................................................................................71

1) push 문 .............................................................................................................................................................................................................................................................................................................72

2) 점프문 ...................................................................................................................................................................................................................................................................................................................73

목차

Page 8: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

VI

번지 고정 ...................................................................................................................................................................................................................................................................................................................................74

익스포트 함수 ....................................................................................................................................................................................................................................................................................................................74

DllAttach/DllDetach 찾기 ....................................................................................................................................................................................................................................................................77

패킹된 DLL의 DllMain() 찾기 ..............................................................................................................................................................................................................................................................81

DisableThreadLibraryCalls로 찾기 ......................................................................................................................................................................................................................................83

02

리버스 엔지니어링 중급

05. PE 헤더(PE Header) .......................................................................................................................................................................................................................................................................85

PE에 대한 상식적인 개념 ................................................................................................................................................................................................................................................................................88

빌드 과정 ...................................................................................................................................................................................................................................................................................................................................89

PE 파일 구조 .......................................................................................................................................................................................................................................................................................................................90

IMAGE_DOS_HEADER .......................................................................................................................................................................................................................................................91

IMAGE_NT_HEADER ..............................................................................................................................................................................................................................................................94

IMAGE_FILE_HEADER .........................................................................................................................................................................................................................................................95

IMAGE_OPTIONAL_HEADER ...............................................................................................................................................................................................................................99

IMAGE_SECTION_HEADER .................................................................................................................................................................................................................................108

PE와 API 호출의 원리 ....................................................................................................................................................................................................................................................................................117

IAT에서 API를 계산하는 방법 ..........................................................................................................................................................................................................................................................119

익스포트 테이블(export table) ...................................................................................................................................................................................................................................................128

마무리 .......................................................................................................................................................................................................................................................................................................................................129

목차

Page 9: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

VII

06. 흔히 사용하는 패턴 ...................................................................................................................................................................................................................................................................................130

조건문의 기본 .....................................................................................................................................................................................................................................................................................................................131

심화된 조건문 .....................................................................................................................................................................................................................................................................................................................132

반복문 .............................................................................................................................................................................................................................................................................................................................................138

break가 들어가는 경우 .....................................................................................................................................................................................................................................................................................142

continue가 들어갈 경우 ................................................................................................................................................................................................................................................................................143

return이 생기는 경우...........................................................................................................................................................................................................................................................................................145

문자열 컨트롤 .....................................................................................................................................................................................................................................................................................................................147

1. strcpy ................................................................................................................................................................................................................................................................................................................147

2. strcat .................................................................................................................................................................................................................................................................................................................152

3. strlwr.................................................................................................................................................................................................................................................................................................................155

정리 .....................................................................................................................................................................................................................................................................................................................................................157

07. MFC 리버싱 ............................................................................................................................................................................................................................................................................................................158

리버스 엔지니어링에 필요한 MFC 구조 .................................................................................................................................................................................................................................159

MFC로 개발됐는지 여부 확인 .................................................................................................................................................................................................................................................................160

MFC 라이브러리 등록 .........................................................................................................................................................................................................................................................................................161

MFC 초기화 루틴 찾기 .......................................................................................................................................................................................................................................................................................163

버튼 핸들러 찾아내기 ............................................................................................................................................................................................................................................................................................166

헤더 파일 활용 ...................................................................................................................................................................................................................................................................................................................170

그 밖의 MFC 관련 리버싱 접근 방법 ............................................................................................................................................................................................................................................175

목차

Page 10: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

VIII

03

연산 루틴 리버싱

08. 시리얼 추출 방법 .....................................................................................................................................................................................................................................................................................177

F-Secure Reverse Engineering 대회 ..........................................................................................................................................................................................................................179

Level 1 문제 풀이 .................................................................................................................................................................................................................................................................................................181

1) 사전 정보 획득 첫 번째 – 파일 스캐너로 스캔 .............................................................................................................................................................................182

2) 사전 정보 획득 두 번째 – API 확인...............................................................................................................................................................................................................183

3) 사전 정보 획득 세 번째 – 파일의 이미지 베이스 확인 ....................................................................................................................................................184

Level 2 문제 풀이 .................................................................................................................................................................................................................................................................................................187

문자열 컨트롤 ...............................................................................................................................................................................................................................................................................................................191

안티 디버깅 기법 .....................................................................................................................................................................................................................................................................................................195

WinMain보다 먼저 가동되는 코드 .........................................................................................................................................................................................................................................198

09. 코드 속이기..........................................................................................................................................................................................................................................................................................................202

Level 3 문제 풀이 .................................................................................................................................................................................................................................................................................................203

데이터 속에 숨은 코드 ....................................................................................................................................................................................................................................................................................206

리버싱으로 방정식 세우기 .......................................................................................................................................................................................................................................................................209

바이너리 파싱 ...............................................................................................................................................................................................................................................................................................................213

재사용이 어려운 코드 ....................................................................................................................................................................................................................................................................................218

디스어셈블러 속이기 ........................................................................................................................................................................................................................................................................................218

정리하며 ................................................................................................................................................................................................................................................................................................................................221

목차

Page 11: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

IX

04

안티 리버싱 엔지니어링

10. 교과서적인 안티 디버깅 .................................................................................................................................................................................................................................................................223

프로그램 개발 완료 후 다가올 리버싱의 위험 .................................................................................................................................................................................................................224

리버싱에 대항하는 A씨의 반격, 그 후… .................................................................................................................................................................................................................................224

안티 디버깅이란? .........................................................................................................................................................................................................................................................................................................225

고전적인 안티 디버깅 ............................................................................................................................................................................................................................................................................................225

API 로 제공되는 안티 디버깅 기능 ...................................................................................................................................................................................................................................................226

다중 기능을 보유한 맥가이버칼, NtQueryInformationProcess ............................................................................................................................................230

Debug Object Handle................................................................................................................................................................................................................................................................................232

또 핸들을 찾아라, NtQueryObject ............................................................................................................................................................................................................................................233

NoDebugInherit.....................................................................................................................................................................................................................................................................................................234

디버거는 어디에, NtSetInformationThread ........................................................................................................................................................................................................235

int3을 이용한 디버거 감지 ...........................................................................................................................................................................................................................................................................238

SetUnhandledExceptionFilter ..................................................................................................................................................................................................................................................240

0xCC 자체를 탐지하자 ......................................................................................................................................................................................................................................................................................242

소프트아이스 감지 ......................................................................................................................................................................................................................................................................................................243

PEB를 이용한 방법 ....................................................................................................................................................................................................................................................................................................244

정리 .....................................................................................................................................................................................................................................................................................................................................................250

목차

Page 12: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

X

11. 한 단계 높은 안티 디버깅 ......................................................................................................................................................................................................................................................251

생각보다 활용도가 높은 프로세스 체크 ..............................................................................................................................................................................................................................252

버전 체크 ..............................................................................................................................................................................................................................................................................................................................253

부모 프로세스 체크 ..............................................................................................................................................................................................................................................................................................256

SeDebugPrivilege 권한 체크 .......................................................................................................................................................................................................................................................260

WinDBG 검출...........................................................................................................................................................................................................................................................................................................261

키보드 입력 봉쇄 .....................................................................................................................................................................................................................................................................................................262

CloseHandle()을 이용한 안티 리버싱 ..............................................................................................................................................................................................................................263

OutputDebugString이 주는 두 가지 마술 ............................................................................................................................................................................................................264

시간차 공격! 타이머를 이용한 방법 ..........................................................................................................................................................................................................................................266

PREFIX REP을 통한 예외 처리 .......................................................................................................................................................................................................................................................268

API Hook을 이용한 디버깅 감지 ................................................................................................................................................................................................................................................269

리모트 디버깅 감지 ..............................................................................................................................................................................................................................................................................................271

생각해 볼 과제 .............................................................................................................................................................................................................................................................................................................273

12. 패커가 사용하는 기술 ....................................................................................................................................................................................................................................................................274

패커? 대수롭지 않은 개념 .........................................................................................................................................................................................................................................................................275

IAT의 변화 ........................................................................................................................................................................................................................................................................................................................279

매뉴얼 언패킹(MUP) ......................................................................................................................................................................................................................................................................................282

파일 스캐너 ......................................................................................................................................................................................................................................................................................................................284

디버거의 구조 ...............................................................................................................................................................................................................................................................................................................285

Self Debugging .................................................................................................................................................................................................................................................................................................287

스택 세그먼트 레지스터 ...............................................................................................................................................................................................................................................................................289

int 0x2d..............................................................................................................................................................................................................................................................................................................................291

목차

Page 13: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XI

덤프 방지 ....................................................................................................................................................................................................................................................................................................................................292

디버그 레지스터 .............................................................................................................................................................................................................................................................................................................294

13. 우회 방법 ........................................................................................................................................................................................................................................................................................................................298

API Hooking을 통한 우회 ..........................................................................................................................................................................................................................................................................300

OllyDBG 옵션을 이용한 우회 .................................................................................................................................................................................................................................................................306

플래그 수정으로 우회 ............................................................................................................................................................................................................................................................................................307

05

OllyDBG 플러그인

14. OllyDBG 플러그인 SDK .............................................................................................................................................................................................................................................................309

최고의 매력 OllyDBG 플러그인 .........................................................................................................................................................................................................................................................310

플러그인 가동 원리 ....................................................................................................................................................................................................................................................................................................311

플러그인 SDK 사용 규약 ..................................................................................................................................................................................................................................................................................314

초기화 코드 ............................................................................................................................................................................................................................................................................................................................315

PreInit - ODBG_Plugindata() ........................................................................................................................................................................................................................................................316

초기화 루틴 - ODBG_Plugininit() ...............................................................................................................................................................................................................................................317

메뉴 작성 - ODBG_Pluginmenu() ............................................................................................................................................................................................................................................320

메뉴 핸들러 작성 - ODBG_Pluginaction() ..................................................................................................................................................................................................................325

종료 처리 ....................................................................................................................................................................................................................................................................................................................................328

PDK 활용 ..................................................................................................................................................................................................................................................................................................................................328

t_dump - 덤프 관련 구조체.......................................................................................................................................................................................................................................................................329

목차

Page 14: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XII

t_memory – 메모리 관련 구조체 ...............................................................................................................................................................................................................................................333

t_disasm – 디스어셈블 구조체 .......................................................................................................................................................................................................................................................335

Readmemory..........................................................................................................................................................................................................................................................................................................336

구조체 종합 예제 - Extra Copy .....................................................................................................................................................................................................................................................338

15. 리버싱 보조 플러그인 ....................................................................................................................................................................................................................................................................343

IsDebuggerPresent() 무력화 ......................................................................................................................................................................................................................................................344

디버거 떼어내기 .......................................................................................................................................................................................................................................................................................................346

자동 어태치 ......................................................................................................................................................................................................................................................................................................................348

단축키 - ODBG_Pluginshortcut() .....................................................................................................................................................................................................................................350

OllyAdvance 분석 ............................................................................................................................................................................................................................................................................................351

Setbreakpoint()와 TLS CallBack 감지 .......................................................................................................................................................................................................................353

커맨드라인 실행 바 ..............................................................................................................................................................................................................................................................................................357

디버깅 흔적 청소 .....................................................................................................................................................................................................................................................................................................360

게임 해킹 플러그인 – Game Invader ................................................................................................................................................................................................................................361

Game Invader의 구현 원리 ..............................................................................................................................................................................................................................................................364

메모리 속성 옵션 .....................................................................................................................................................................................................................................................................................................366

암복호화 코드 분석 – Find Crypt ...............................................................................................................................................................................................................................................368

FindCrypt의 구현 원리...............................................................................................................................................................................................................................................................................369

남은 과제 ..............................................................................................................................................................................................................................................................................................................................371

16. 메모리 스캔 플러그인 개발 ................................................................................................................................................................................................................................................372

아직 세상에 없는 플러그인을 만들어 보자 .....................................................................................................................................................................................................................373

분석 시간을 줄일 수 있다면? ...............................................................................................................................................................................................................................................................374

플러그인 코어 루틴 컨셉 .............................................................................................................................................................................................................................................................................374

목차

Page 15: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XIII

UI 디자인 ...................................................................................................................................................................................................................................................................................................................................376

버튼 핸들러 제작 ...........................................................................................................................................................................................................................................................................................................377

후킹 탐지 기능 ...................................................................................................................................................................................................................................................................................................................379

파일과 메모리의 기준 번지를 정하는 방법 ...........................................................................................................................................................................................................................381

IAT가 코드 섹션에 포함된 경우 .............................................................................................................................................................................................................................................................382

패킹 체크 ....................................................................................................................................................................................................................................................................................................................................383

후킹 탐지 스캔 결과 ..................................................................................................................................................................................................................................................................................................383

암복호화 루틴 탐지 ....................................................................................................................................................................................................................................................................................................384

화이트 리스트 처리 ....................................................................................................................................................................................................................................................................................................386

OllyMemScan 평가 .............................................................................................................................................................................................................................................................................................387

06

보안 모듈 우회

17. 바이러스 감지 회피 ...................................................................................................................................................................................................................................................................................391

백신은 과연 바이러스만 감지하는가 .............................................................................................................................................................................................................................................392

기본적인 감지 원리 ....................................................................................................................................................................................................................................................................................................393

고전적인 우회 방법 ....................................................................................................................................................................................................................................................................................................395

쓰레기 코드를 통한 우회 방법 ..................................................................................................................................................................................................................................................................396

실행조차 되지 않는 코드 ...................................................................................................................................................................................................................................................................................399

헤더 변조 ....................................................................................................................................................................................................................................................................................................................................401

문자열 패턴 우회 ...........................................................................................................................................................................................................................................................................................................402

바이너리 패킹의 시대 ............................................................................................................................................................................................................................................................................................404

목차

Page 16: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XIV

대체 가능한 어셈블리어 ...............................................................................................................................................................................................................................................................................406

패커의 특성을 고려한 회피 .....................................................................................................................................................................................................................................................................407

섹션 이름 변경 ...........................................................................................................................................................................................................................................................................................................408

백신사의 대응 .............................................................................................................................................................................................................................................................................................................409

18. 백신을 공격하는 악성코드 ...................................................................................................................................................................................................................................................411

백신을 공격하는 바이러스는 어느 정도 존재하는가.......................................................................................................................................................................................412

특정 백신 언급 증가 ............................................................................................................................................................................................................................................................................................413

백신 프로세스 종료 ..............................................................................................................................................................................................................................................................................................414

트레이 속임 ......................................................................................................................................................................................................................................................................................................................416

공식적으로 언급된 취약점 .......................................................................................................................................................................................................................................................................416

업데이트 우회 ...............................................................................................................................................................................................................................................................................................................419

보안업체 비방 ...............................................................................................................................................................................................................................................................................................................421

드라이버 공격 ...............................................................................................................................................................................................................................................................................................................421

몸체가 없는 공격 코드 ....................................................................................................................................................................................................................................................................................424

Conficker ........................................................................................................................................................................................................................................................................................................................426

SDT 취약점 ......................................................................................................................................................................................................................................................................................................................427

벤치마킹의 중요성 ................................................................................................................................................................................................................................................................................................428

19. 실시간 감시 기능 취약점.........................................................................................................................................................................................................................................................429

바이러스 감지 후 필요한 행위 ............................................................................................................................................................................................................................................................430

파일 시스템 필터 드라이버 .....................................................................................................................................................................................................................................................................431

실시간 감시 기능의 구현 원리 ............................................................................................................................................................................................................................................................433

목차

Page 17: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XV

콜백 함수 무력화 ...........................................................................................................................................................................................................................................................................................................436

콜백 무력화 대응 방법 ..........................................................................................................................................................................................................................................................................................438

노티파이 카운트 취약점 .....................................................................................................................................................................................................................................................................................438

화이트리스트 강제 주입 .....................................................................................................................................................................................................................................................................................439

파일 접근 통제 ...................................................................................................................................................................................................................................................................................................................440

ZIP 취약점 ................................................................................................................................................................................................................................................................................................................................441

가상 PC 감지 .......................................................................................................................................................................................................................................................................................................................442

그외 안티바이러스 우회를 위한 다양한 방법 ...................................................................................................................................................................................................................444

20. 키보드 보안 솔루션 우회...............................................................................................................................................................................................................................................................445

키보드 취약점이 되는 구간 ...........................................................................................................................................................................................................................................................................446

키보드 보안의 원리 ....................................................................................................................................................................................................................................................................................................447

폴링 취약점 ............................................................................................................................................................................................................................................................................................................................449

IE의 추가 기능 관리 ..................................................................................................................................................................................................................................................................................................451

가장 피해 사례가 많은 ring3 영역 ...................................................................................................................................................................................................................................................452

핵심 모듈 공격 ...................................................................................................................................................................................................................................................................................................................453

언로드 DLL .............................................................................................................................................................................................................................................................................................................................455

ring3에서 키보드 보안을 우회하는 이유 ..............................................................................................................................................................................................................................457

키보드 보안의 문제점 ............................................................................................................................................................................................................................................................................................458

가상 키보드 취약점 ....................................................................................................................................................................................................................................................................................................458

인터넷 뱅킹 취약점 – 메모리 해킹 ......................................................................................................................................................................................................................................................460

키보드 보안이 어려운 이유 ...........................................................................................................................................................................................................................................................................462

클라이언트에서 완전한 보안은? ..........................................................................................................................................................................................................................................................463

목차

Page 18: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XVI

07

한 차원 높은 바이너리 창조

21. 코드 후킹 ..................................................................................................................................................................................................................................................................................................................467

코드 후킹과 공동경비구역 .......................................................................................................................................................................................................................................................................468

번지 계산의 고통 .....................................................................................................................................................................................................................................................................................................470

DLL 인젝션 코드 ......................................................................................................................................................................................................................................................................................................471

CreateRemoteThread ..........................................................................................................................................................................................................................................................................471

DLL 인젝션 테스트 ..............................................................................................................................................................................................................................................................................................477

코드 후킹 기본 디자인 ....................................................................................................................................................................................................................................................................................478

여섯 바이트를 변조하는 이유 ..............................................................................................................................................................................................................................................................480

DLL 번지의 강제 고정과 naked 함수 ...................................................................................................................................................................................................................................482

패킷 버퍼 로깅 .............................................................................................................................................................................................................................................................................................................485

하드코딩에서 탈피하자 .................................................................................................................................................................................................................................................................................487

후킹 코드의 확장 .....................................................................................................................................................................................................................................................................................................489

MakeJMP - 후킹 함수의 간결화 .................................................................................................................................................................................................................................................494

마무리 .......................................................................................................................................................................................................................................................................................................................................495

22. 코드 패칭 ..................................................................................................................................................................................................................................................................................................................496

동적 접근 방법과 정적 접근 방법 ...................................................................................................................................................................................................................................................501

프로세스 검출 루틴 분석 .............................................................................................................................................................................................................................................................................506

점프문 공격 ......................................................................................................................................................................................................................................................................................................................508

목차

Page 19: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XVII

파일에서 패칭할 때와 메모리에서 패칭할 때 ...................................................................................................................................................................................................................510

상수값 패칭 ............................................................................................................................................................................................................................................................................................................................513

스택 해킹 ....................................................................................................................................................................................................................................................................................................................................514

거짓을 리턴 ............................................................................................................................................................................................................................................................................................................................518

스레드 공격 ............................................................................................................................................................................................................................................................................................................................520

마무리 .............................................................................................................................................................................................................................................................................................................................................525

23. 난독화와 더미 코드 ...................................................................................................................................................................................................................................................................................526

디스어셈블러 개론 ......................................................................................................................................................................................................................................................................................................527

안티 디스어셈블링 ......................................................................................................................................................................................................................................................................................................529

가비지 코드 속에 담긴 진짜 코드 .........................................................................................................................................................................................................................................................532

ASPack이 사용하는 안티 디스어셈블링 ...............................................................................................................................................................................................................................534

Themida에서 사용하는 안티 디스어셈블링 ...................................................................................................................................................................................................................536

ASProtect의 코드 카피 ...................................................................................................................................................................................................................................................................................540

Themida의 코드 리다이렉트 ..................................................................................................................................................................................................................................................................542

상수 암호화 ............................................................................................................................................................................................................................................................................................................................550

API 호출 감추기 .............................................................................................................................................................................................................................................................................................................552

난독화 코드 찾아내어 우회하기 ............................................................................................................................................................................................................................................................556

마무리 .............................................................................................................................................................................................................................................................................................................................................558

목차

Page 20: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XVIII

정보보안의 태동기에는 네트워크 보안, 시스템 보안 두 영역에서 방화벽과 같은 네트워크 단에서의 필

터링 기술과, OS 단에서의 아주 기본적인 요소기술이 발전했었다. 그 뒤 나온 보안기술들을 보더라도

네트워크 기반 침입탐지시스템(N-IDS ), 침입방지시스템(IPS ), 웹애플리케이션방화벽(WAF )과 같

이 주로 이미 발생해서 인스턴스화된 공격에 대해 탐지와 예방을 하는 것들이 주류를 이뤘다. 예전부터

개념 역시 존재했고 소수 기업 및 인원들에 의해 이뤄지고는 있었지만, 이미 개발된 프로그램 그 자체

에 대한 취약점을 발견해 내기 위해 시큐어코딩이나 개발보안에 사람들이 관심을 갖고 노력을 하기 시

작한 것은 사실 10년도 채 되지 않았다고 볼 수 있다.

보안산업 역시 고도화 되어가는 과정에서 나타나는 현상 중 하나로, 일단 기본적인 기술들이 먼저 발전

한 뒤에야, 특화된 영역에 대한 요소기술로 깊이 연구가 이뤄지게 된다. 전통적인 네트워크 보안 영역

에 쏠렸고(Firewall, N-IDS ), 그 후 OS 영역(H-IDS, SecureOS )에 주 초점이 맞춰져 있었다가,

DB/Application 영역(DB보안, 웹보안 및 시큐어코딩 등의 개발보안)으로 관심사가 옮겨감에 따라

리버스엔지니어링 기술의 중요성은 두말할 나위 없이 보안 분야 종사가 반드시 갖춰야 할 스킬로 자리

잡았다.

다양한 보안직군 가운데 모의해킹, 취약점분석을 주로 하거나 또는 악성코드 분석 또는 안티리버스 엔

지니어링(Anti-Reverse Engineering )을 통해 보안을 강화하고자 한다면 리버스 엔지니어링 관련

지식을 꼭 쌓을 것을 권하고 싶다. 리버스 엔지니어링 지식과 더불어 Solaris, HP-UX, AIX, Linux,

FreeBSD와 같은 다양한 OS를 익혀보고 각 환경에서의 프로그래밍 스킬을 함께 익히게 된다면 보안

분야에서 “자신만의 고유한(unique ) 능력을 갖춘 대체 불가능한(irreplaceable ) 인재”로 성장하리

라 확신한다.

이 책은 리버스 엔지니어링에 대해 충실히 다룰 뿐 아니라, 익힌 리버스 엔지니어링 기법을 실제로 어

떤 곳에 활용할 수 있는지에 대해서도 상세히 다룬다. 그간 저자가 오랜 기간 동안 시행착오를 겪어오

면서 익힌 소중한 지식을, 그다음에 배우는 사람들은 실수와 시행착오를 최소한으로 하고 배울 수 있게

여기저기에 배려하고 있으며, 복잡하고 어렵기만 한 내용을 편안하게 전달하는 저자 특유의 노하우가

담겨 있다.

감수자의 글

Page 21: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XIX

저자는 이미 정보보안 분야 종사자 중에서도 실력이 완성에 이르렀다고 볼 만큼 시스템 보안 및 개발 보

안에 능하고, 국내에서 여러 유수의 세미나 및 대학에서의 강의를 한 경험이 있는 자기만의 철학과 유머

를 갖춘 구루다. 독자들이 이 책을 통해서 많은 지식을 전달받을 수 있을 것이라 기대한다.

- 김휘강

현 고려대학교 정보보호대학원 조교수

전 엔씨소프트 정보보안실장/TD (Technical Director )

전 A3 시큐리티컨설팅 창업자

감수자의 글

Page 22: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XX

리버스 엔지니어링의 역사는 사실 깊다. 아마도 엔지니어링이 시작되었던 인류 역사 초기부터, 리버스

엔지니어링은 존재했을 것이다. 리버스 엔지니어링은 누군가가 만들어 놓은 것을 분해해서 분석하고

재조합하는 과정을 말한다. 그 대상이 인간이 만든 기계 장치가 될 수도 있을 것이고, 자연에 존재하는

생명체나 유전자, 화학 물질 등이 될 수도 있겠다.

소프트웨어의 리버스 엔지니어링의 역사는 소프트웨어의 역사만큼이나 길 것이다. 기계어로 코딩을 해

야 하는 시기에는 엔지니어링에 사용하는 언어와 리버스 엔지니어링으로 분석하는 언어도 같았다. 하

지만 컴퓨터 언어 체계가 발전하고 다분화하면서 엔지니어링 과정에 비해 리버스 엔지니어링 과정은

현저히 복잡한 과정이 되었다. 단 한줄의 코드도 컴파일러의 종류, 작동 방식과 최적화 알고리즘 등에

의해 다른 형상을 가진 비트(bit )를 생성해 낸다. 이러한 복잡성의 증가로 인해 리버스 엔지니어링은

완벽함을 실현할 수 있는 분야가 아니다. 의도되거나 의도되지 않은 수많은 가지치기된 기술의 출현으

로 바이너리들은 온갖 형상을 취할 수 있기 때문이다.

소프트웨어 리버스 엔지니어링은 보안 분야나 악성 코드 분석에만 한정된 것이 아니다. 엔지니어링을

할 때도 리버스 엔지니어링은 필수적인 과정의 일부다. 특히 요즘처럼 개발을 위한 요소가 모듈화돼 있

는 경우 소스코드를 얻기 어려운 모듈의 작동 방식에 대한 이해도를 높이기 위해서는 리버스 엔지니어

링은 필수다. 또한 자신이 직접 작성한 코드라도 작동 방식의 문제를 해결하거나 최적화 등을 이루기

위해서는 리버스 엔지니어링은 필수적인 과정이다.

본질적으로 리버스 엔지니어링을 가장 빠르게 습득하는 방법은 엔지니어링을 먼저 섭렵하는 것이다.

엔지니어링에 관한 각 분야별 정보는 넘치도록 있으니 일단 그 부분을 섭렵하고 들어 가는 것이 좋을

것이다. 하지만 이미 그러한 과정을 거치고 지나간 선생들의 가르침을 들어 보는 것도 좋을 것이다. 이

책의 의미가 바로 거기에 있다. 책 한권으로 리버스 엔지니어링을 모두 이해하겠다는 것은 어불성설이

다. 하지만 이 책을 통해 현업에서 실제로 쓰이는 기술과 전략, 트릭 등에 대한 지식을 습득할 수 있을

것이라고 본다. 책을 읽다 보면 저자가 실무에서 풍부한 경험을 쌓아 왔다는 것을 책을 통해 쉽게 느낄

수 있었다.

추천의 글 I

Page 23: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXI

책 자체는 심심풀이용으로 읽을 수 있을 만큼 재미 있게 쓰여져 있다. 예전에 어렸을 적에 흔히 보던 퍼

즐 문제집을 읽는 것처럼 흥미진진하다. 저자의 의도대로 책을 읽으면서 예제를 따라해 본다면 어느덧

리버스 엔지니어링의 세계에 빠져 있는 자신을 발견할지도 모른다. 흥미로운 전개 방식은 첫 장을 읽고

어딘가에 처박아 놓고 몇 달, 몇 년이 지나도 다시 펴 보지 않는 책과의 차별 요소라고 할 것이다. 그러한

재미를 담는 여유 또한 지은이의 오랜 연륜에서 나오는 것이라 보고 싶다.

이제 독자 여러분도 맛있는 리버스 엔지니어링의 세계에 푹 빠져 볼 것을 권한다. 물론 맛있게 요리된 지

식을 소화해서 자신의 것으로 만드는 것은 전적으로 독자의 몫일 것이다. 이렇게 재미와 내용의 균형이

잡힌 책인 한국에서도 나왔다는 사실을 기뻐하며, 이 서평을 마칠까 한다.

- Matt 오정욱

현) Microsoft Malware Protection Center (미국 시애틀) 근무

블랙햇, 데프콘, 캔섹 웨스트(CanSecWest ), EuSecWest,

ShmooCon, ToorCon, BayThreat, CARO, XCON,

CodeGate 스피커 활동, 다른 그림(http://darungrim.org ) 개발자

전) Websense, eEye Digital Security 근무, BugTruck 운영진

추천의 글 I

Page 24: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXII

우리는 지금 총성 없는 전쟁, 바로 사이버 전쟁 시대에 살고 있다. 국가에서도 사이버 사령부를 창설하

고 보안 인재를 육성하기 위해 다양한 시도를 하고 있으며 언론이나 사람들의 관심도 날로 높아지고 있

다. 한편 보안 전문가라 하면 보안이라는 용어가 지닌 지나치게 광범위한 의미 탓에 과연 어떤 지식을

겸비해야 보안 전문가로 칭할 수 있는지가 애매한 부분이 있고, 때문에 보안 전문가를 만나보면 문제

풀이나 이론적인 부분에만 치중해 있어 실무적인 감각은 떨어지는 경우가 종종 있다.

이 책은 국내 최고 보안 전문가이면서 넥슨 미국지사 보안팀을 책임지고 있는 강병탁 님의 실전 노하우

가 녹아있는 책이다. 저자가 진행한 세미나와 블로그를 통해 그의 노하우가 공개된 적은 있지만 접하기

어려운 사람들이 많았기에 이번 책이 특히 기대되는 이유이기도 하다. 이 책은 수많은 보안 기술 분야

중에서 ‘리버싱’을 중점적으로 설명하고 있다. 리버싱은 보안 전문가라면 반드시 마스터해야 하는 기술

로 소위 ‘분석’ 작업의 기초 기술이기도 하다. 하지만 기계가 이해할 수 있는 있는 언어로 만들어진 코드

를 인간이 이해할 수 있는 논리도 변환하기는 쉽지 않다. 그뿐만 아니라 패킹과 같은 안티-리버싱 기법

의 범용화로 더 이상 예제 수준의 리버싱 기법만 배워서는 실무에서 어떤 정보도 얻지 못하고 좌절하고

말 것이다. 이 책은 저자가 실무에서 경험하고 해결했던 수많은 사례를 통해 리버싱의 기초부터 안티-

리버싱 기법에 이르는 전방위적인 리버싱 기법에 대해 설명한다.

모든 보안 분야가 그렇듯이 사용하는 사람에 따라 칼이 될 수도 있고 방패가 될 수도 있다. 이 책의 내

용은 기업의 자산을 외부로부터 보호하기 위해서지만 ‘6장 보안 모듈 우회’와 같은 기법은 해킹 도구를

만드는 사람들에게는 악용될 소지가 있다. 하지만 그럼에도 불구하고 이 책에서 상세하게 보안 모듈 우

회 기법을 설명함으로써 오히려 음지의 기법을 양지로 드러내면 해당 분야가 더욱 발전할 수 있는 기회

가 될 것이다.

끝으로 리버싱이 기술적 난이도로 인해 설명하기 쉽지 않은 분야지만 저자만의 위트 있는 설명과 다양

한 도식, 예제 코드 등을 통해 독자 여러분도 소설책 읽듯이 리버싱을 이해할 수 있을 것이다. 오랜만에

국내 저자가 집필한 완성도 높은 보안 서적을 만나게 되어 정말 기쁘다.

- 서우석

현) 파인드스티브 대표이사

전 디버그랩 운영진 / Microsoft C# MVP

전 국가기관 보안 연구원, 안철수 연구소 엔진팀 근무

추천의 글 II

Page 25: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXIII

추천의 글 III

인간은 살아가면서 한번쯤 모순적인 선택을 할 수밖에 없는 상황에 부딪히게 된다. 보안과 해킹! 절대로

뚫릴 수 없다는 보안과 무엇이든 뚫을 수 있다는 해킹, 보안을 천직으로 하는 사람들은 이 창과 방패의

모순 속에서 치열하게 싸움을 하고 있다. 보안과 해킹은 그 태생이 같다. 기술적인 기본이 같다는 얘기

다. 그 기술을 사용하는 사람과 적용하는 대상에 따라 선이 될 수도 있고 악이 될 수도 있다. 그렇기 때문

에 보안과 해킹은 더더욱 모순과의 싸움이 될 수밖에 없다고 생각한다.

어찌보면 이 책의 저자인 강병탁 님도 이 책을 통해 그 모순에 대한 스스로의 고민을 말하고 싶었는지도

모르겠다. 리버싱의 기본부터 방어기능, 공격기능을 차례로 설명하면서, 선과 악에 대한 선택을 독자에

게 맡기는 잔인함도 보인다. 자칫 양날의 검이 될 수도 있는 기술들 중에서 가장 기본이 되는 것이 바로

리버싱이다.

그동안 리버싱에 대해서는 많은 번역서가 출간되었다. 그러나 지금까지의 리버싱 관련 서적은 현업에서

의 경험과 지식에 기반을 두고 있다기보다는 대부분 이론적인 내용을 기반으로 한다. 리버싱을 완벽하게

이해하려면 운영체제, 프로그래밍, 컴파일러 등 여러 가지 기본적인 지식이 필요하기 때문에 이론에만

치우칠 경우 독자들이 쉽게 다가갈 수 없는 벽이 느껴질 수밖에 없다.

그러나 이 책은 현업에서 보안전문가로서의 저자의 경험을 바탕으로 기본적인 지식이 조금 부족하더라

도 리버싱을 이해하는 데 전혀 어려움이 없을 정도로 매우 친절하게 설명돼 있다. 또한 저자 특유의 감

각이 돋보이는 제목과 설명으로 자칫 지루하고 딱딱하게 느낄 수 있는 내용을 공부하고 싶은 호기심으로

바꾸고 있다. 이 책을 발판으로 실무 경험을 바탕으로한 좋은 내용의 국내 서적들이 많이 출간되어 보안

을 꿈꾸는 많은 이들의 길잡이가 되기를 희망한다.

마지막으로, 언제나 새로운 목표를 향해 도전하는 강병탁 님에게 마음속 깊은 응원의 박수를 보낸다.

- 이호웅

현) 안철수연구소 ASEC 센터장

Application Hacking 감수

Page 26: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXIV

한 권의 명저가 탄생했다. 기존의 책과는 차별화된 방식으로 리버스 엔지니어링에 접근하는 이 책은 리

버스 엔지니어링의 현대판 바이블이라고 생각한다. 저자의 노고에 무한한 박수를 보낸다.

- 이홍선, 넥슨 코리아 게임보안팀

실무자로서의 경험과 연구에서 비롯된 리버스 엔지니어링에 관한 다향한 기술, 기법들을 집대성한 주

옥같은 학습서다. 쉬운 예제와 친절한 설명으로 이 새로운 세계에 쉽게 적응할 수 있도록 도와주며 구

체적인 설명으로 기술에 대한 이해를 높여주고 이에 더해 기존에는 잘 다뤄지지 않았던 부분에 대한 전

문적인 노하우를 알려 주고 있어 중급 이상의 과정으로 나아가려는 이들이라면 시행착오를 줄이고 시

간을 절약하는 데 도움될 것이다.

- 이진석, 넥슨 코리아 게임보안팀

리버스 엔지니어링을 강병탁 님 특유의 위트있는 어투로 재미있게 풀어낸 책이다. 그동안 게임보안 일

을 하며 많은 책을 봐왔지만 이렇게 군더더기 없이 필요한 것만 설명해주는 책은 처음이다. 이제부터

이 책은 지금까지와는 다른 리버스 엔지니어링 입문서가 될 것이다.

- 김동현, 넥슨 코리아 게임보안팀

리버스 지니어링의 기초부터 고급기술까지 다루고 있는 책이다. 베타리더를 진행하면서 새롭게 알게

된 부분도 많고, 기초를 다시 다지는 계기가 되었다. 이 책을 읽는 독자들 또한 실력이 두 단계 업그레

이드될 것이라 장담하며, 리버스 엔지니어링의 세계를 경험해 보시기 바란다.

- 전동기, 넥슨 코리아 게임보안팀

베타리더 후기

Page 27: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXV

베타리더 후기

강력한 비급이 담겨 있을수록 어려운 내용으로 인해 주화입마에 빠지고 마는 경우가 많다. 하지만 이 책

은 그러한 주제를 특유의 위트와 재치로 풀어낸 비급서가 아닐까 생각한다.

- 최종학, 넥슨 코리아 게임보안팀

아직 많은 부분에서 부족한 내가 이 책을 통해 얻게 된 것, 그것은 저자가 수년간 게임 보안 업계에서 쌓

아온 실무 노하우였다. 해커들과 수없이 공방을 치뤄야 하는 나에게 이 책은 전세계 해커들과 맞짱을 뜰

수 있게 해준 무기였다.

- 김지호, 넥슨 코리아 게임보안팀

처음 시작조차 막막하고 높은 벽처럼 느껴졌던 리버싱 기술을 어떻게 접근해야 하는지, 그 기술의 핵심

이 무엇인지 보여주는 지침서라고 생각한다. 처음 리버싱을 공부했을 때 어셈블리 책을 읽다가 좌절했던

경험을 겪어본 한 사람으로서 지식과 실무 경험이 녹아 있는 이 책으로 보안 분야에 입문하기를 감히 추

천한다.

- 김창규, 넥슨 아메리카 정보보안팀

Page 28: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXVI

지금 이 책을 보고 계신 분들은 여러 유형의 사람들이 아닐까 한다. 리버스 엔지니어링이라는 학문을

처음 시작하게 된 분, 또는 리버스 엔지니어링이라는 것에 대해서는 이미 알고 있지만 얕은 지식에 불

과했던 수준에 깊이를 더하기 위해 새롭게 시작하신 분, 이미 리버스 엔지니어링의 고수 반열에 올라와

있지만 어디 한두 개 건질 만한 내용이 없을까 하여 책을 빠르게 훑어보는 분 등, 필자는 이 모든 분들

을 환영한다. 처음 시작하는 분에게나, 이미 가진 실력을 향상시키고 싶으신 분들 모두에게 이 책을 선

택한 것이 후회없는 선택이 되도록 만들어 줄 것을 약속한다. 이 책은 필자가 그간 리버스 엔지니어링

과 연관된 업무상 겪어왔던 수년간의 경험(나 경력 몇 년이야~ 라고 라고 내세우는 걸 그다지 좋아하

지 않아서 “경험”으로 대체했다. 보통 그런 발언을 서슴치 않는 분들은 헛된 경력이 많이 쌓여 있는 경

우가 많기도 하기 때문에) 또는 개인적으로 연구해서 파악한 내용에 대해 글로 묶은 내용이다. 보통의

책에 흔하게 나와 있는 내용 또는 인터넷에서 쉽게 검색해서 찾을 수 있는 딱딱하고 하품나는 공대 서

적 특유의 냄새는 모두 제거하고, 실제 경험을 토대로 얻은 지식을 말랑말랑한 표현과 농담을 섞어가며

위트있는 가르침을 내려주는 선배들에게 한 수 배우듯이 친근하게 썼다. 공대생들은 무뚝뚝한 사람투

성이며, 기술서적은 기계가 작성한 글을 보는 듯한 느낌이라는 고정관념에서 최대한 탈피하여 지식을

전달하려고 노력했다.

리버스 엔지니어링이란 무엇인가

타사 서적을 폄하하려는 의도는 아니지만, 흔히 볼 수 있는 “C++ 시작하기”, “엑셀 시작하기” 등과 같

은 누구나 아는 소재/주제의 책은 그 내용을 어렵지 않게 짐작할 수 있지만 리버스 엔지니어링이라는

것은 직접 접해본 사람이 아니라면(생각외로, 일반적인 애플리케이션 프로그래머 중에서도 리버스 엔

지니어링에 대해 모르는 사람이 아직도 꽤 많다) 리버스 엔지니어링이 어떤 것에 사용되고, 무엇을 위

한 학문인지 모를 수도 있다 싶어 먼저 개론적인 설명부터 시작하겠다.

리버스 엔지니어링이라는 것은 소스코드를 역추적하는 것을 말한다. 요즘에야 보안 트렌드가 많이 다

양해지면서 리버스 엔지니어링이라는 과목을 정식으로 개설하게 된 학교도 있지만 학번이 지긋하게 높

으신 분들은 소프트웨어공학 시간에 간략히 다루어진 정도로만 기억할 것이다. 예를 들어 어떤 C 코드

가 있다. 개발자가 코딩을 완료하고 빌드를 하게 되면 이 소스코드를 컴퓨터가 이해할 수 있는 언어인

시작하며

Page 29: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXVII

기계어로 번역하게 된다. 그렇게 컴파일이라는 과정을 거치며 오브젝트(obj ) 파일이 만들어지며, 여러

링크된 라이브러리와 이 오브젝트 파일을 결합해 최종적으로 EXE나 DLL 등이 생성된다. 이렇게 빌드

된 파일은 (일반적인 상식으로는) 당연히 원래의 소스코드를 알 수 없게 되는 형태가 된다.

하지만 여기서 리버스 엔지니어링이라는 기술을 사용하게 되면 빌드된 EXE, DLL 등의 바이너리 분석을

거쳐서 원래의 소스코드가 어떤 식으로 만들어져 있는지 파악하는 것이 가능해진다. PE Header 분석을

통해 어떤 라이브러리가 링크돼 있는지 분석하고, 버튼을 눌렀을 때 가동되는 코드의 흐름을 추적해 원

래의 코드가 어떤 식으로 제작돼 있는지 알 수 있게 된다. 물론 컴파일러가 오브젝트 파일을 만들고 바이

너리로 빌드된 이후의 내용을 어셈블리 코드로 재분석하는 것이라 원래의 소스코드와 완전히 일치하는 내

용이 나오는 것은 아니다. 따라서 분석가의 능력에 따라 “리버스 엔지니어링을 제대로 하긴 한 건지?”라는

의구심이 들 정도로 아주 적은 정보만 파악해낼 수도 있고, “혹시 이거 소스코드를 직접 보고 온 거 아니

야?”라는 감탄이 들 정도로 원래의 소스코드에 매우 근접한 결과치를 뽑아내는 결과가 나오기도 한다.

어떤 곳에 리버스 엔지니어링을 사용할 것인가

자, 그럼 이런 리버스 엔지니어링을 통해 어떤 것들을 할 수 있는지 알아보자. 원본 소스코드의 내용을

뽑아낼 수 있다는 결과론적 내용에 의거하면 파악할 수 없는 내용 또는 함부로 열어봐서는 안 되는 내용

에 대한 검증을 한다는 맥락에서 봤을 때 왠지 뒤가 구린 행위에 활용할 수 있지 않을까 생각이 든다. 정

답이다. 리버스 엔지니어링은 타인의 기술을 얻어내거나 타 프로그램의 행위에 끼어드는 데 사용된다.

그래서 리버스 엔지니어링은 해킹과 보안 분야에서 많이 사용될 수밖에 없는 면모를 지니고 있다.

모의해킹, 취약점 발견

대표적으로 애플리케이션의 취약점을 분석하는 데 리버스 엔지니어링이 이용될 수 있다. 소스코드가 없

는 상태에서 현재 서비스가 되고 있는 애플리케이션은 리버스 엔지니어링을 통해 소스코드의 구조를 분

석해야 한다. 버퍼 오버플로우가 일어날 가능성이 있는지, 코드 변조를 통해 허가되지 않은 액션이 가능

한지, 패킷 분석을 통해 암복호화 알고리즘에 취약점이 있지는 않은지 등이 대표적인 경우다. 또한 악성

시작하며

Page 30: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXVIII

코드를 검출하는 안티바이러스가 혹시 검출하지 못하는 형태의 로직이 있는지 취약점을 분석할 수 있

고, 키 입력을 보호해야 하는 키보드 보안 솔루션이 보호하지 못하는 영역을 찾아낼 수도 있다. 이러한

관점에서 살펴보면 리버스 엔지니어링은 모의해킹 등 보안 검수를 할 때, 분석가의 필수적 역량 중 하

나가 된다.

보안코드 개발

역으로 취약점 발견이나 보안검수 외에도, 리버스 엔지니어링을 이용해 보안기능 개발에도 활용할 수

있는데, 예를 들어 1000개의 악성코드가 등장하면 이를 모두 발견하기 위해서는 1000개의 시그니처

를 뽑아 이 악성코드를 탐지해야 한다(물론 일반적인 대응을 할 때). 하지만 만약에 이 악성코드에 공

통적인 비정상 행위가 있다는 것을 리버스 엔지니어링을 통해 발견했다면 그 한 가지를 탐지하는 하나

의 시그니처로 1000개의 악성코드를 한방에 차단하는 효과를 보여줄 수 있다. 즉, 해커의 침입 여부를

역분석해서 그에 상응하는 보안 모듈을 개발할 수 있다. 이렇게 발견된 취약점은 리버스 엔지니어링을

통해 원인 분석을 하게 되고, 강화된 모듈을 개발하는 것으로 마무리할 수 있다.

버그 수정

버그가 발생했을 때 그 원인을 발견하는 목적으로도 리버스 엔지니어링은 사용된다. 소스코드상에서

는 문제가 없지만 다른 모듈이 결합했을 때 또는 필드상에서 외부 프로그램과 충돌이 발생했을 때 또는

예상하지 못한 상황에서 예외가 발생했을 때 등에 대해 리버스 엔지니어링을 통해 원인을 파악할 수 있

고, 심지어 소스코드 없는 상태에서 수정도 가능하다. 아주 바람직한 사례는 아니지만 어떤 해커들은

프로그램의 버그를 찾아서 해당 벤더사에 수정 요청을 했는데 그 처리가 늦어질 경우, 직접 리버스 엔

지니어링을 통해 바이너리를 수정하여 버그를 고친 후 프로그램을 사용하기도 한다.

신기술 연구와 학습

리버스 엔지니어링은 소스코드 없이 소스코드를 파악하는 작업이므로 실행되는 프로그램만 있으면 그

것이 어떤 기술로 만들어져 있는지 분석해낼 수 있다. 어떤 진기한 프로그램이 세상에 등장했고, 그 기

술이 어떻게 구현돼 있는지는 책이나 인터넷 사이트를 뒤져도 알 수 없을 때 그것을 리버스 엔지니어링

시작하며

Page 31: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXIX

하면 기술에 대한 궁금즘을 해결할 수 있다. 리버스 엔지니어링은 이렇게 타 바이너리를 분석하며 신기

술 연구에 큰 도움을 가져다 준다.

개발 지식은 필수

자, 지금까지의 내용을 생각해 보면, 리버스 엔지니어링은 바이너리 분석을 통해 원본 소스코드의 내용

을 파악하는 것이 한 줄 요약이자 가장 핵심적인 기술임을 알 수 있다. 따라서 훌륭한 리버서가 되기 위

해 반드시 갖춰야 할 자질은 무엇일까? 그것은 바로 풍부한 개발 능력이다. 코드를 작성해본 경험이 있

어야 빌드된 바이너리가 원래 어떤 식으로 만들어졌는지 파악할 수 있기 때문이다. 보안 컨설턴트가 개

발 능력을 뭐 하러 쌓느냐고 반문하시는 분들이 종종 있는데, 보안업계에서 프로그래밍 스킬은 가장 기

본적으로 갖춰야 할 자질임과 동시에 필수적인 기반 기술이다. 진짜 고수 해커들은 웬만한 개발자보다

훨씬 더 훌륭한 코딩 능력을 보이는 경우도 많다. 필자 역시 개발자로 시작해서 보안 컨설턴트로 전향한

사례지만 필자 주변에는 컨설턴트에서부터 시작했으면서 필자보다 백배는 더 코드를 잘 작성하는 사람

이 수없이 많다. 보안쟁이들은 개발을 할 필요가 없다고 말한 사람이 누군지 모르겠지만 실제로 개발 능

력이 출중한 보안 컨설턴트나 모의해커들은 지천에 널려 있다.

또한 리버스 엔지니어링을 배울 때 첫걸음을 잘못 내디딘 나머지 동적 디버깅에만 의지해 점프가 나올

때만 어디로 갈 것인지 고민만 하며 트레이싱을 하는 사람이 있는데, 그렇게 점프코드에만 의지해 크래

킹만 연마한 사람들은 리버스 엔지니어링을 아무리 많이 해 봤자 코드 분석이나 알고리즘 파악 등에 대

한 지식은 쌓이지 않고 오직 점프 연산 같은 잡기술만 쌓고 만다. 이는 곧, 영어 문제를 풀 때 문제에 대

한 해답은 잘 찾으면서 정작 회화나 영작은 하지 못하는 사례와 일맥상통한다. 리버스 엔지니어링을 할

때 크래킹이라는 마력에 빠질 수 있는데(스타워즈 매니아들의 표현으로, 해킹에 빠진 이들을 가리켜 다

크 사이드 포스에 중독됐다고 한다) 이런 사람이 되지 않아야 한다.

따라서 리버스 엔지니어링을 하기 전에는 C/C++에 대한 코드 작성 능력, 각종 라이브러리를 컨트롤하

는 스킬, 또한 Win32 API와 시스템 프로그래밍에 대한 기반지식이 충분해야 한다(이 책의 중간 즈음에

나오는, 일상 생활에서의 역공학이라는 코너에서 이에 대한 얘기를 깊이 있게 설명했으니 해당 코너가

나오면 참고하기 바란다).

시작하며

Page 32: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXX

리버스 엔지니어링은 합법인가

리버스 엔지니어링은 이미 빌드된 바이너리를 역분석하는 과정을 거치며 공개돼서는 안 되는 원본 소

스코드를 추적하는 작업을 진행한다. 이런 면만 봐도 리버스 엔지니어링이라는 것은 과연 법적인 문제

가 없을까, 라는 생각이 드는 분도 당연히 계실 것으로 안다. 사실, 리버스 엔지니어링을 전파하는 입장

에서 이 문제에 대해 법적인 문제가 전혀 없다고 하기에는 가슴이 조금 찔리고, 불법이 맞다, 라고 하기

에는 “그럼 이 책은 불법을 저지르는 짓을 가르치는 것인가?”라는 명제에 대해 대답하기가 애매해진다.

필자 개인적인 가치관을 떠나서 합법이냐 불법이냐에 대한 잣대와 관련해서 현재의 분위기에 대해 간

략히 얘기하면, 일단 바이너리 파일 변조의 경우는 무조건 불법이다. 빌드된 바이너리는 어디까지나 바

이너리를 만든 회사에 지적재산권이 있다. 따라서 리버스 엔지니어링을 통한 바이너리 변조는 지적재

산권을 침해, 파괴하는 행위에 해당하므로 이것은 엄연한 불법이며, 이 책을 읽는 분들도 상용 소프트

웨어에 대해서는 절대 리버스 엔지니어링을 수행하지 않기를 바란다. 반면 메모리에 로딩된 코드에 대

해서 변조하는 것은 불법이다 합법이다가 아직도 의견이 분분한 편인데, 합법이라고 외치는 분들은 내

가 내 돈 주고 산 내 메모리에서 실행되는 내용을 좀 변경했다고 해서 어디가 문제냐는 논지를 펼치고

있고, 불법이라는 분들은 당신의 메모리를 변조했어도 그 메모리에 올라간 바이너리는 우리의 제품이

며, 결국 우리 회사의 자산을 침해하기 위한 의도로 자행한 일이므로 불법이라는 의견을 내세우고 있

다. 필자는 법률 수행가가 아니므로 어느 의견이 맞다는 가치 판단은 보류한다.

첨언해서 이야기하자면, DMCA (Digital Millenium Copyright Act )라는 기술이 있다. 이것은 저작

권 보호 기술을 보호하기 위한 법으로서, 보호 기술을 보호하는 법인데, DRM 기술을 이용한 바이너리

에 그것을 우회하거나 회피하기 위한 시도를 했을 때 법적인 처벌을 받는 것을 말한다. 이런 DMCA 개

념이 리버스 엔지니어링을 통한 법적 분쟁이 발생했을 때 중요한 개념으로 사용된다. 물론 아직까지는

리버스 엔지니어링이 법률에 대해서는 미개척 분야라 변호사의 역량대로 또는 판사의 판단대로 결과가

나올 수 있으므로 무엇이 답이다, 라고 정확히 결론내리기는 어려운 편이다. 따라서 이런 때에는 당연

히 이전의 사례를 살펴보기 마련이고, 결국은 과거에 어떤 선례가 있었는지 판례가 중요해진다. 물론,

완전히 동일한 판례는 아직 많지 않고 IT와 보안과 관련된 법적 분쟁에 대해서는 변호사들도 문외한

시작하며

Page 33: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXI

인 경우가 많으므로 기업 내 보안을 담당하는 보안 컨설턴트들도 이러한 법에 대해 풍부한 지식을 쌓아

두는 것이 좋다. 이와 관련해 좀더 자세한 내용을 공부하고 싶다면 Gerald R. Ferrera 외 6인이 작성한

‘CyberLaw: Text and Cases’라는 책을 추천하니 살펴보기 바란다.

이 책은 어떤 내용을 설명하는가

자, 그럼 서론은 이쯤 하고, 이 책에서 여러분들에게 전파할 지식은 어떤 내용일지 간략히 정리해 보겠

다. 이 책은 필자가 월간 마이크로소프트웨어에서 약 3년간 해킹/보안 칼럼에 연재한 내용을 근간으로

만들었다. 책에 정리된 약 60%의 내용은 완전히 새로 작성했고, 40%의 내용은 기존의 기고물을 보충하

거나 새로운 기술을 알릴 수 있는 데 주력해 철저히 개편했다. 설명이 불친절하거나 최소화됐던 내용은

바이블이라는 제목에 걸맞게 최대한 독자들이 알기 쉽고 흥미를 유발하며 따라 할 수 있게 각색해 두었다.

1부 _ 리버스 엔지니어링 기본

어셈블리, C, C++, DLL 등의 매우 기본적인 코드의 생성 흐름과 규칙을 정리한 내용이다. 리버스 엔지

니어링도 결국 정해진 문법 안에서 기본적인 규칙을 따르면 풀어낼 수 있는 것에 불과하기 때문에 그러

한 규범을 알아보자는 이야기라고 보면 될 것 같다. 차를 달이거나 마실 때의 방식이나 규칙을 말하는 다

도(茶道)라는 것이 있다. 한 잔에 불과한 맹물을 마실 때도 이 같은 엄격한 규율이 있듯이 리버스 엔지

니어링을 할 때도 기본적으로 알아둬야 할 규칙이 있다는 것을 이해하기 위한 내용이다.

2부 _ 리버스 엔지니어링 중급

PE Header는 리버스 엔지니어링을 공부하는 분들이 가장 고통스러워하기도 하고, 공부하기도 싫어했

지만 어쩔 수 없이 결국에는 익혀야만 되는 필수 요소 중 하나인데, 그런 선배 경험자들의 고충을 밑바탕

으로 이론에 불과한 내용을 철저히 배제하고 현업과 실무에 맞게 재각색해서 설명했다. “6장 흔히 사용

하는 패턴”에서는 1부 리버스 엔지니어링 기본에서 다루지 못한 코드가 생성되는 과정과 계속 반복해서

나오는 패턴에 대해 분석하며 어셈블리 언어의 감을 익히기 위한 내용을 준비했다. 그리고 마지막으로

MFC 리버싱을 통해 애플리케이션 분석의 집중도를 높일 수 있게 여러 가지 기법 소개를 준비했다.

시작하며

Page 34: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXII

3부 _ 연산 루틴 리버싱

일반적으로 수학 공부를 시작할 때 집합과 명제가 나오고 수와 식을 익히며 방정식을 공부한 뒤 함수,

지수로그, 삼각함수 등으로 넘어가는 과정이 있듯이, 사실 대부분의 리버스 엔지니어링 커리큘럼에서

가장 먼저 가르치는 것은(집합과 명제 코너에 해당하는 것은) 키젠 풀이나 시리얼 연산 방법에 대한

리버싱이다. 하지만 개인적으로 필자는 키젠 풀이를 그다지 좋아하지 않고 현업에서도 업무 연관성이

상당히 적기 때문에 리버스 엔지니어링에 있어 가장 불필요한 학습단계라고 본다. 따라서 우선순위를

낮추다 못해 아예 생략할까 하기도 했지만 그래도 리버스 엔지니어링한다는 사람치고 키젠 하나 못 푸

는 것도 우스운 일이므로 결국 타협하고자 한 게 중간 즈음에 내용을 준비하는 것이었다. F-Secure 백

신사의 리버스 엔지니어링 대회를 통해 연산 루틴을 분석하는 과정을 설명하는데, 흔히 볼 수 있는 점

프 패치를 중점적으로 설명하기보다는 왜 이 루틴을 분석해야 하고 어떤 접근 방식을 써야 하는지 등,

생각하는 리버스 엔지니어링에 초점을 맞춰 단순한 키 빼내기 해설이 되지 않도록 노력했다.

4부 _ 안티 리버스 엔지니어링

필자는 나무에 열리는 밤을 무척 좋아하는데, 특히 밤송이의 가시 속에 먹음직한 알밤이 들어있을 때

알밤을 빼 먹을 욕심으로 가시를 제거하면서 긴장감을 느끼는 아슬아슬한 순간을 은근히 즐겼던 것 같

다. 안티 리버싱은 리버스 엔지니어링을 막는 기술, 이른바 밤송이에 들어있는 가시 같은 역할이다. 이

것을 제거해야만 리버스 엔지니어링을 순탄하게 진행할 수 있고 결국에 알밤을 빼먹을 수 있는 결과까

지 도달하는 것이다. 안티 리버싱 기술은 계속해서 발전을 거듭해, 안티 리버싱을 분석, 격파하는 그 자

체가 하나의 학문으로 될 만큼 성장해오고 있다. 인터넷에 흔히 등장하는 안티 디버깅 코드는 실제로

가동되지 않는 것도 많고 예제 코드도 충분하지 않아 익히기에 어려운 감이 있지만, 이 4부에서는 모든

기법을 100% 실제 코드로 제작했으며 각 밤송이 가시 기술에 대한 강력함의 정도, 우회 방법 등의 한

계점, 그리고 보완 방법 등에 대해 다각도로 설명했다.

시작하며

Page 35: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXIII

5부 _ OllyDBG 플러그인

OllyDBG 플러그인은 리버스 엔지니어링을 좀 더 쉽게 하기 위해 만든 보조 도구라고 볼 수 있다.

OllyDBG에서는 누구나 플러그인을 제작할 수 있게 훌륭한 인터페이스를 제공하고 있으므로 SDK의

사용법과 자신만의 아이디어가 결합하면 리버스 엔지니어링을 몇 배 이상 효율적으로 증가시킬 플러그

인을 제작할 수 있다. 필자는 가끔 OllyDBG 자체보다 OllyDBG 플러그인 자체에 더욱 더 큰 감동을

느낄 때가 있다. 5부를 공부하고 난 후의 누군가가, 또 다른 사람들에게 감동을 줄 플러그인을 만들어 세

상에 알려주었으면 하는 바람이다.

6부 _ 보안 모듈 우회

보안 관계자에게 가장 두려운 것은 방어장치가 없는 상태에서 해킹을 하는 사람이 아니고 존재하는 보안

모듈을 뚫어서 해킹 시도를 하는 이들이다. 그리고 대부분의 취약점은 보안 설계 자체의 실수보다는 어

이없는 한두 줄의 취약점 때문에 발생하는 것들이 상당수를 이룬다. 세계 1, 2차대전 때 대형 항공모함

이 침몰하던 사례를 봐도 격렬한 전투에 의해 항공모함이 격추됐던 것이 아니라, 자신이 죽는 것을 각오

하고 취약점을 향해 덤비는 가미가제의 전투기 한두 대에 의해 침몰되는 경우도 허다했다는 점도 단순한

공격이 커다란 사고까지 이어질 수 있다는 사실을 반증한다. 6부에서는 일반적으로 보안 모듈을 우회하

는 기법 말고도 굉장히 단순한 한두 가지 취약점을 이용해 보안 모듈이 공격 당하는 여러 가지 사례를 보

여준다.

7부 _ 차원 높은 바이너리 창조

7부에서는 마지막 내용으로, 코드 후킹, 코드 변조, 난독화 등의 내용을 다루며 리버스 엔지니어링을 목

표로 하는 당신에게 “초짜”의 늪에서 “중수” 이상으로 가는 동력이 됐으면 하는 바람으로 작성했다. eax

는 리턴값, ecx는 카운터 라는 등의 시시껄렁하고 반복적인 지식의 답습에서 벗어나 리버싱의 깊이를 좀

더 맛볼 수 있게 노력했다. “6부 보안 모듈 우회” 편의 연장이라는 느낌을 살렸기 때문에 6부의 내용을

반드시 숙지한 후에 살펴보길 바란다.

시작하며

Page 36: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXIV

이 책은 어떤 분들에게 필요한가

서두에 간략히 소개했다시피 이 책은 리버스 엔지니어링을 처음 접하는 분들, 또는 어느 정도 수준을

높이고 싶은 분들을 대상으로 한다. 특히 친근한 설명과 가벼운 대화체를 사용했으므로 접근하는 데 벽

이 높지 않으리라 생각한다. 따라서 리버스 엔지니어링이 너무 어려워서 중도에 포기했던 분들은 대환

영이다.

그리고 필자의 인생관 중 하나인데, 굳이 리버스 엔지니어링에 국한된 내용은 아니지만 어떤 내용을 공

부하건 발전하는 본인을 느낀다면 그것에 기뻐하고 거침없이 본인에게 칭찬을 해준다면 정말 중요한

사기 증진이 되며 아주 간단하게 자기성찰을 할 수 있는 방법이 아닐까 한다. 처음부터 너무 높은 벽을

목표로 잡았다면 벽 근처에 가기도 전에 좌절하게 되고, 시작하기도 앞서 포기하고픈 마음이 더 쉽게

들 것이다. 한 초등학생이 방정식을 풀었다며 선생님이나 부모님께 자랑스럽게 칭찬받을 것을 기대하

면서 성적표를 들고왔을 때, “야, 달나라에 가려면 그 정도로는 어림도 없어”라고 핀잔을 주는 모습은

잠재력 있는 사람을 영원히 매장하는 행위가 될 수도 있다. 우주 항공선을 만든 사람도 방정식을 공부

하는 것이 어려웠을 수 있다. 결코 겁내지 말고 바이너리를 한 바이트 한 바이트 들여다 보며 리버스 엔

지니어링에 눈을 떠가는 자신을 흐뭇한 표정으로 비춰보기 바란다.

서두르지 말 것

실제로 공부를 시작해 보면 리버스 엔지니어링이라는 분야는 해야 할 것들이 너무도 많은 분야라고 느

껴질 때가 올 수도 있다. 이쯤에서 드리고 싶은 말씀은 절대 서두르지 말라는 것이다. 대나무가 하늘로

쭉쭉 휘어지지 않고 곧바로 올라갈 수 있는 것은 적당히 자랐을 때마다 매듭으로 봉하고, 또 자라면 또

매듭으로 봉해서 위로 올라갔기에 가능한 것이다. 인간도 마찬가지다. 어떤 대나무도 단 한번에 성장할

수 없는 것처럼, 하나의 목표를 휘지 않고 달성하기 위해서는 흔적의 매듭이 반드시 필요하다. 실력이

바로 늘지 않는다고 해서, 내용이 어렵다고 해서 본인을 자책하지 말고 조금씩 한걸음씩 매듭을 봉해

나간다면 이 책을 읽은 후에는 1년 후 반드시 훌륭한 대나무가 돼 있을 것이라고 말해주고 싶다.

시작하며

Page 37: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXV

생각하는 책이 되고 싶다.

많은 사람들이 학문이나 책을 접할 때 지식의 전달 수단에 충실한 책은 많이 볼 수 있지만 실질적인 조력

자나 어드바이서의 역할에 충실했던 책을 꼽아보라면 과연 얼마나 될까, 라는 생각이 든다. 요즘 같은 정

보의 홍수 시대에 데이터는 넘쳐난다. 하지만 어떤 정보를 어떻게 활용할 것이며, 정보를 대할 때의 자

세나 정보를 활용하는 사고력 등과 같은 것을 길러주는 것이 오히려 더 중요하지 않을까. 예를 들어 수

학 문제를 풀 때 이 문제는 이런 공식을 통해 답을 낸다는 결과주의적 접근에 의거해 공부를 하는 사람보

다는 왜 이런 답이 나오는지, 왜 이런 상황에는 그 공식을 써야만 하는지, 다른 공식을 썼을 때 답은 나오

지 않는 것인지, 이 문제를 접할 때 적용해 볼 수 있는 접근 방법에는 최소한 몇 가지가 있는지 등을 생각

하는 사람이 훨씬 더 다양한 사고력을 펼칠 수 있으며, 새로운 문제에 대한 해답 역시 쉽게 찾을 수 있다.

물론 전자에 해당하는 사람은 많은 문제를 공부했을 때 그만큼의 경험이 존재하기 때문에 이미 접해본

유형에 대해서는 별 무리 없이 풀어나가겠지만, 새로운 문제를 마주했을 때 후자에 속하는 사람보다 훨

씬 응용력이 떨어질 것이다. 수집된 데이터는 많겠지만 사고력을 기르는 연습이 부족했기 때문이다.

그런 부분에 의거해 이 책 역시 가급적이면 많은 양의 정보를 전달하기 위한 목적보다는 원리에 중점을

두고, 리버스 엔지니어링을 할 때의 “접근 방법”과 “사고력”을 우선적으로 설명하려고 노력했다. 점프문,

push 문, call 문 등 다양한 코드가 나오는데, 왜 이곳의 점프문을 봐야 하는 것인지, A 상황에 B 기법

을 써야만 하는 이유 등 독자로 하여금 스스로 질문을 던지고 한 차원 수준 높은 사고를 통해 해답을 찾

을 수 있게 최대한 배려했다. 즉, 필자가 말하고 싶었던 것은 물고기를 잡는 법에 대한 가르침, 아니 그

것을 넘어서 일반적인 물고기가 아닌 네 발 달린 물고기가 나왔을 때, 지렁이를 먹지 않는 물고기가 나

왔을 때 등 다양한 상황에 능동적으로 사고할 수 있게 접근 기술과 생각의 전환법을 알리려고 최대한

노력했다.

시작하며

Page 38: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXVI

정제되지 않은 표현에 대한 양해를 부탁한다

이 책에서는 딱딱한 정보전달 위주의 설명이 아닌 접근 방법과 사고력에 대해 선임에게 가르침을 받

듯 차근차근 설명을 듣는 분위기로 책을 구성했다, 라고 이야기했다. 모든 것을 갖춘 상태에서 예의범

절과 매너까지 갖추면 얼마나 좋겠느냐만은 위와 같은 모토로 문장이 나가다 보니 다소 직설적인 표현

이나 과도한 은유법 등이 책 곳곳에 등장하게 된 점은 어쩔 수 없던 부분이었던 것 같다. 따라서 어쩌

면 이 책에 등장하는 이런 거친 표현은 일부 독자에겐 눈살을 찌푸리게 할 수도 있다. 일반적으로 화술

의 재료는 풍성한 지식에서 나오지만, 그렇다고 해서 화술이라고 포장될 만큼 집대성된 지식이 흔해 빠

진 잡학을 의미하는 것은 아니라고 생각한다. 따라서 필요한 상황에 적합한 이야기를 하는 것이 중요하

고, 불필요하게 길지도 않으며 속 깊은 말을 써야 한다는 것 역시 중요하다고 생각한다. 필자가 비록 글

재주는 없지만 나름 위와 같은 철학을 가지고 글을 쓰고 있다는 점을 조금이라도 눈여겨봐 주었으면 하

며, 결코 도발적이며 파괴적인 언어로 일시적인 주목을 끌기 위함이 아니라는 점을 알아주길 바란다.

따라서 품위 없는 위트나 질 떨어지는 농담이 한두 번씩 나오더라도 너그러운 마음으로 이해해 주었으

면 하는 바람이다.

당부의 말

이 책의 제목은 바이블이고, 책의 두께도 상당히 두껍다. 처음 책을 쓸 때와의 마음과는 달리 이것도 다

루고 싶었고 저것도 다루고 싶었지만, 지면상의 한계에 부딪혀 내용의 난이도 조절에 실패해 싣지 못한

내용이 많았다. 개인적으로 매우 안타까운 부분이다. 또한 리버스 엔지니어링은 어둠의 예술이다. 해킹

과 보안이라는 것은 양면성이 존재하기 때문에 자칫 나쁜 곳에 사용하다가는 진짜 나쁜 악당이 될 수도

있다. 이 책을 읽는 사람들 가운데 리버스 엔지니어링을 크래킹이나 해킹에만 사용하는 분이 없기를 바

란다.

마지막으로 필자는 항상 재미 위주로 모든 현상을 접하려는 나름의 주관이 있다. 리버스 엔지니어링과

연관된 보안 업무를 할 때도 언제나 즐거웠으며, 새로운 코드를 만드는 기쁨, 해커가 만든 코드를 리버

스 엔지니어링해 보며 참지 못할 정도로 웃음을 터뜨린 적이 한두 번이 아니었던 것 같다. 이 학문을 본

시작하며

Page 39: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

XXXVII

인의 천직으로 여기고 앞으로 살아갈 삶의 원동력으로 삼겠다면 진심으로 즐기길 바란다. 괴로운 듯이

스트레스 받으며 코드를 분석하고 짜증내는 모습은 지양하고, 이 일을 통해 얻는 보람과 즐거움을 느끼

며, 그 즐거움을 주위 사람들에게 널리 전파해 주길 바란다.

감사의 말

본래는 더욱 일찍 출간했어야 할 책이었지만 여러 가지 사정이 겹쳐 다소 늦어졌다. 그와 더불어 주변에

서 필자의 책을 전설의 책이라 부르곤 했는데, 그 이유는 소문만 무성하고 실체는 존재하지 않아 그렇

기 때문이라는 후문이 있다(내용의 깊이가 있어 전설이라는 것이 절대 아니다). 아무튼 책이 늦어진 만

큼 주변에서 도와주시고 걱정해 주신 분들께 죄송한 마음을 감출 길이 없다. 그 모든 분들께 감사의 마음

을 전한다. 이 책을 감수해 주셨고 필자 인생의 나침반이자 영원한 스승님이신 김휘강 교수님, 새로운 도

전을 할 수 있게 언제나 뒤에서 후원해주신 채은도 부사장님, 김대훤 본부장님, 이희영 본부장님, 이경엽

부실장님, 신용석 센터장님, 어려운 미국생활에 큰 힘이 되어주셨던 다니엘 대표님, 이 책을 베타리딩 해

준, 가장 힘든 시절을 함께 했고 이제는 전우였다고 말할 수 있는 넥슨 코리아 게임보안팀 일동 이홍선,

이진석, 최종학, 김동현, 조경화, 전동기, 김지호, 전옥희, 권오석, 이예진, 김응주 그리고 이혜원, 새로

이 보안 업무를 준비해 가며 함께 고생중인 넥슨 아메리카 정보보안팀 김창규, 유동규, 감동적인 추천사

를 작성해주신 Matt 형님, 이호웅 실장님, 서우석 사장님, 보안팀의 영원한 동반자 우철이형, 소수정예

최강의 군단인 우리 고려대학교 정보보호학과 해킹대응연구실 일동, 언제나 큰 힘이 되어주는 김명현,

정국이형, 박사장님, 이유경, 노수민, 정박커플, 여성구 팀장님, 김상완 팀장님, 넥슨밴드 밴드원들, 돌

아이 모임(+진형), 그리고 번번히 마감일을 어겨 마음고생 많이 시켜드려 끝없이 죄송한 마음 금할 길

없는 김윤래 팀장님, 항상 응원해주시는 아버지, 어머니 그리고 장인어른과 장모님, 마지막으로 이 책을

집필하는 동안 옆에서 끊임없이 격려해주고 채찍질 해준, 사랑하는 나의 아내에게 이 책을 바친다.

시작하며

Page 40: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 기본

01

01 _ 리버스 엔지니어링만을 위한 어셈블리

02 _ C 문법과 디스어셈블링

03 _ C++ 클래스와 리버스 엔지니어링

04 _ DLL 분석

Page 41: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

어셈블리 책이나 개론서, 또는 각종 바이블을 찾아보면 사실 그런 식이다. 레지스

터(register)를 설명하고 플래그(flag)에 대한 굉장히 긴 표가 따라붙는다. 그리고

mov니 add니 하는 명령어에 대한 기계적인 설명이 굉장히 길게 늘어지기 시작하

는데, 대부분의 입문자가 여기서 머리를 쥐어짜거나 책을 덮어버리는 모습을 보이

며 좌절을 겪는다. 그들에게 필요한 건 어셈블리로 실제 프로그래밍을 하기 위한

지식이 아니라 리버스 엔지니어링을 위해 어셈블리 코드를 해석하는 방법이다. 다

시 말해, 리버서로 첫걸음을 내딛는 사람들에게 필요한 것은 많은 양의 어셈블리

문법이 아니라 리버스 엔지니어링이라는 조명을 밝혀줄 만한 한줄기 빛이다.

01 리버스 엔지니어링만을 위한

어셈블리

C 코드와 어셈블리 비교, 옵코드(opcode), 오퍼랜드(operand), 레지스터(register), 리틀 엔디언(little endian),

스택(stack), 함수의 호출, 리턴 주소(return address)

키 워 드

Page 42: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 2

어셈블리는 리버스 엔지니어링을 하기 위한 가장 기초적인 도구와도 같다. 비유하자면 영어 공부를 시

작했다면 알파벳, 일어 공부로 치자면 히라가나쯤 된다. 따라서 가장 먼저 시작해야 할 기초 이론이며,

필수로 수강해야 할 과목이다. 그러나 이제 막 시작한 초보에게 어셈블리란 가장 처음 느끼는 커다란

벽과 같은 느낌이다. 필자가 후배들에게 리버스 엔지니어링을 가르칠 때 가장 많이 들은 질문은 바로

다음과 같다.

“어셈블리는 대체 어떻게 접근해야 할지 모르겠어요.”

이 친구들이 이런 질문을 하는 심정이 이해되는 이유는, 사실 필자 역시 같은 고민을 해 본 시절이 있

었기 때문이다. 어려운 어셈블리를 대체 어떻게 접근해야 할까? 나는 어떻게 했을까? 그리고 요즘 리

버스 엔지니어링 입문자들은 리버스 엔지니어링을 처음 공부할 때 어셈블리와는 어떤 식으로 첫인사를

나누게 될까? 지식을 쌓고 문제를 해결하려면 일단 책이 가장 적당하니 어셈블리 책으로 먼저 접근해

본다.

필자가 처음 어셈블리를 접한 책은 황희융 선생님의 《MS-DOS 매크로 어셈블리》라는 책이었다. 이

책은 정말 훌륭하고 많은 내용이 담긴, 틀림없는 어셈블리의 바이블이다. 많은 부분을 이 책을 통해 알

게 됐으며, 어셈블리 지식을 쌓는 데 아주 큰 공헌을 한 책으로 기억한다. 하지만 단점은 이해하기가 쉽

지 않았으며, 어셈블리 프로그래밍이나 언어 자체를 위한 내용이 중심이라서 리버스 엔지니어링을 위

한 어셈블리 입문서로는 적합하지 않았다. 그래서 기계를 다루는 공대생이 아닌 이상 보안 업계 종사자

가 어셈블리에 입문하는 데 이 책을 추천하기가 여의치 않았다.

그렇다면 조금 수준이 떨어지는 책은 어떨까? 아쉽게도 그 밖의 책들도 크게 사정이 다르지 않았다. 일

부 번역서나 32비트 어셈블리만을 따로 추려 놓은 책도 역시 예외는 아니었다. 리버서를 표방하는 이

들에게 필요한 것은 어셈블리를 목적이 아닌 수단으로 활용하기 위한 방법이지만, 사실 그런 책은 없었

다. 대부분의 책에서는 순수하게 어셈블리 언어에 대해서만 다뤘다. 우리가 알고 싶어 하는 지식과는

접근법 자체가 완전히 방향이 다르며, 대체 어디서부터 시작해야 할지 감이 오지 않는 경우가 대부분이

다. 물론 사람에 따라 그것을 자기 입맛에 맞게 소화하며, 모래 속에서 바늘을 찾아가는 식으로 지식을

연마하는 사람들도 있었지만, 입문하는 모든 사람에게 그런 일취월장을 기대하기는 어려웠다.

그래서 리버스 엔지니어링을 처음 공부할 때 “리버스 엔지니어링만을 위한 어셈블리” 지식을 따로 마련

하는 건 어떨까, 라는 생각이 들었다. 불필요한 어셈블리 프로그래밍 기반의 지식을 배제하고, 코드 분

석을 할 때 각 레지스터의 어떤 부분에 주안점을 두고 살펴봐야 하고 컴파일러가 코드를 만들어내는 양

Page 43: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 3

상을 비롯해 어셈블리 명령어는 어디까지 암기해야 할지에 관한 내용을 주요 고민거리로 삼아 정리한

다면 입문자에게 아주 유용한 지침서가 되지 않을까 한다.

그래서 리버스 엔지니어링 바이블의 첫 시작으로, 순수 리버스 엔지니어링만을 위한 어셈블리를 설명

하는 코너를 마련했다. 어셈블리를 쉽게 이해하고 싶은 분, 그리고 아직도 이해가 되지 않는 분을 위해

더욱 친숙한 어셈블리의 세계로 안내하는 내용을 담고 있다. 이번 장은 말 그대로 “리버스 엔지니어링

만을 위한 어셈블리”라는 주제로 풀어간다. 이 장에서는 어셈블리 구문이나 모든 명령어를 설명하지 않

는다. 단지, 앞으로 새로운 명령어를 만나도 얼마나 쉽게 이해할 수 있느냐에 중점을 뒀다. 즉, 지금까

지 고민한 내용에 대해 어느 정도 해답에 근접한 방식으로 접근하는 것을 일차적인 목표로 삼고, 어셈

블리를 좀 더 쉽게 생각하자는 것을 이차적인 목표로 삼았다. 어셈블리에 이미 자신 있는 분들은 건너

뛰어도 무방하지만 다소 유치한 위트를 부분부분 섞어놓았으므로 재미로 읽어봐도 크게 손해 볼 것은

없을 것이다. 그럼 “리버스 엔지니어링만을 위한 어셈블리”라는 거창하지만 다소 별거 없는 듯한 주제

를 지금부터 시작한다.

어셈블리의 기본 구조

어셈블리는 매우 간단하다. 매우 쉽고 간결하다. 하지만 어셈블리가 어려운 이유는 바로 그 단순함에

있다. C/C++ 코드와 비교해서 얼마나 단순한 것일까? 예를 하나 들어보자. 냉장고에서 물을 꺼내 마

시는 작업을 한다고 생각해 보자. C/C++ 코드라면 다음과 같이 진행될 것이다.

void 물마심()

{

BOOL bOpen = 냉장고문오픈();

if (bOpen)

{

물을꺼냄();

마심();

}

}

다소 유치한 코드지만, 어쨌든 대략의 흐름은 저렇다. 냉장고문오픈( )을 통해 냉장고 문을 열고, 문을

여는 데 성공(bOpen == TRUE )하면 물을꺼냄( )을 이용해 물을 꺼내고 마심( )을 통해 물을 마시는

아주 일목요연한 코드가 된다. 하지만 이것을 어셈블리로 표현한다면 어떨까?

Page 44: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 4

__asm{

냉장고앞으로간다

냉장고문을잡는다

냉장고문을연다

오픈성공:

냉장고안을본다

손을든다

냉장고안에넣는다

물병을잡는다

물병을꺼낸다

뚜껑을연다

물을컵에따른다

컵을손에든다

컵에든것을마신다

C/C++ 코드와 비교했을 때의 가장 결정적인 차이는 뭘까? 그것은 한 번의 동작에 몇 가지 액션을 할

수 있느냐다. 그리고 어셈블리로 바뀌었을 때의 착안점은 “한 가지 동작”이다. 어셈블리는 한 번에 한

가지 동작밖에 하지 못한다. 그래서 냉장고에서 물을 꺼낼 때도 먼저 시선을 냉장고로 향해야 하며, 물

병을 꺼낼 손을 들어야 하고, 냉장고 안에 손을 넣는 것까지 세세하게 다 지정해야 한다. 이것이 바로

어셈블리의 단순하다는 명제에서 오는 맹점이다. 어셈블리는 이처럼 간단명료하기 때문에 코드 한두

줄만 봐서는 해당 프로시저가 무슨 목적으로 만들어졌는지 알 수 없다. 즉, 전체적인 흐름을 봐야지, 지

엽적인 부분만 봐서는 대체 무엇을 하는 코드인지 파악할 수 없다.

게다가 추가적인 난제로, 이렇게 많은 부분을 일일히 지정해야 하므로 코드가 굉장히 길어진다. 그래서

C/C++에서는 몇 줄 안 되는 코드가 어셈블리로 바꾸면 수십 수백 줄로 바뀌는 이유가 여기에 있다.1

그래도 크게 걱정할 필요는 없다. 이 책의 2부에서는 그러한 세부적인 코드에 연연하지 않고 대략적인

흐름만 파악해서 코드의 목적을 알아내는 여러 가지 방법을 설명하겠다. 일단 그 부분은 나중에 다시

살펴보기로 하고 먼저 어셈블리에 대해 알아둬야 할 만한 것부터 좀더 생각해 보자.ㅌ

1 물론 이것도 사실 간단해진 것이다. 그 전에는 0과 1로 된 바이너리였을 테니...

Page 45: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 5

어셈블리의 명령 포맷

어셈블리에도 여러 종류가 있지만 지금부터 설명할 어셈블리는 x86 CPU의 기본 구조인 IA-32를 기

본 플랫폼으로 삼아 소개할 예정이다. 그 이유는 사실상 대부분의 PC가 Intel CPU를 사용하고 있고

(AMD의 경우도 실제로 대부분의 코드가 Intel Processor와 호환되므로) 요즘 어셈블리를 익힌다는

것은 IA-32를 익힌다는 것과 동일한 표현으로 봐도 무방하기 때문이다. 따라서 이후부터 리버스 엔지

니어링을 위한 어셈블리는 IA-32에 초점이 맞춰져 있다는 것을 알아두자.

먼저 IA-32의 기본 형태는 매우 단순하다. 한 줄을 짤 때 단지 하나 또는 두 개의 코드가 있을 뿐이다.

"명령어 + 인자"

명령어는 어셈블리를 한 번이라도 본 적이 있다면 알 수 있는 mov나 push 같은 것을 말하며, 좀

더 우아한 표현으로 옵코드(opcode )라고 한다. 그리고 인자는 명령어 다음에 “어떤 장소로 값을

넣을 것인지”, 또는 “명령어에 해당하는 값” 등이 된다. 이것은 다시 고급스러운 표현으로 오퍼랜드

(operand )라고도 한다. 예를 들어 보자.

push 337

이 코드에서 옵코드는 push가 되며, 337이 오퍼랜드가 된다. push는 스택에 값을 넣으라는 명령어이

며, 337은 넣을 값으로서 인자로 볼 수 있다. 예를 하나 더 보자.

mov eax, 1

이번에는 오퍼랜드가 2개인 경우다. 이 코드에서 옵코드는 mov가 되며, 오퍼랜드는 eax와 1이 된다.

eax에 1을 넣으라는 얘기로, 앞의 오퍼랜드가 목적지 오퍼랜드가 되며, 뒤의 오퍼랜드가 출발지가 된

다. mov 문뿐 아니라 모든 오퍼랜드는 앞의 것이 destination이고 뒤의 것이 source라는 점을 잊지

않길 바란다(memcpy나 strcpy 등과 같다고 생각하자). 이런 식으로 어셈블리는 “명령어 1개 + 인자

1개 또는 2개” 구조로 한 줄의 코드가 끝난다. 3개가 넘는 것은 거의 없다. 그래서 어셈블리가 단순하

다고 하는 것이며, 그 단순함 때문에 어렵다는 얘기가 나오는 것이다. 냉장고에서 물을 꺼낼 때도 이 모

든 작업을 한 번에 해줄 수 없기 때문에 문을 열고 손을 올리는 등등 세부적인 명령이 따라붙을 수밖에

없으며, 그러한 명령 하나에는 인자가 2개까지밖에 지원하지 않는 탓에 간단한 처리를 위해서도 굉장

Page 46: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 6

히 많은 양의 코드가 필요하다. 어쨌든 모든 어셈블리는 이런 구조를 따른다. 대략 언어의 구조를 살펴

봤으므로 이번에는 어셈블리 입문자의 최대 난관이라는 레지스터에 대해 알아보자.

레지스터, 복잡한 설명은 그만

수많은 어셈블리 책에서는 이런 식으로 접근한다. “IA-32 레지스터(register )는 범용 레지스터가 8

개 있으며, 각 역할은 다음과 같다. EAX는 정수 연산, ECX는 카운팅….” 사실 필자는 처음 어셈블리

를 공부할 때 레지스터 자체를 이해하는 데 너무도 오랜 시간이 걸렸던 기억이 있다. 그리고 레지스터

가 뭔지 이해했을 때 이렇게 생각했다. “만약 레지스터가 그냥 ‘변수’에 불과하다, 라는 말을 누군가 해

줬다면 얼마나 좋았을까…” 그것이 포인트다. 레지스터를 어렵게 생각하지 말자! 레지스터는 그냥 변수

일 뿐이다.

물론 변수라는 것에 원론적으로 접근했을 때 실제 변수와는 개념 자체가 완전히 다르지만, 쉽고 친근하

게 접근하기 위해서는 변수라고 생각하는 것이 가장 좋다. 그럼 이렇게 생각해 보자.

“변수는 변수인데, CPU가 사용하는 변수다.”

다만 CPU가 사용하는 변수라서 개수가 몇 개 안 되고, 그래서 메모리의 힘을 빌려서 연산을 시작하는

것이라고 생각하자. 그리고 C/C++ 코드를 작성할 때 변수로 더하고 빼고 나누는 연산을 할 수 있는 것

처럼, 어셈블리에서도 역시 연산을 할 때는 레지스터끼리 연산을 하게 된다. 레지스터는 변수이며, 그

변수끼리 계산을 할 수 있다. 대략 이런 전제를 머리에 깔아두고 EAX, EBX, ECX, EDX에 대한 설명

을 들어보면 좀 더 간단하게 레지스터가 뭔지 이해할 수 있을 것이다.

자, 그럼 EAX 등의 레지스터가 무슨 역할을 하는지 지금부터 알아보자. 레지스터는 EAX, EBX,

ECX, EDX, ESI, EDI, EBP, ESP로 총 8개가 있다. 한 가지 알고 넘어가야 할 것은 EAX부터 EDX까

지 A, B, C, D 순으로 늘어나기 때문에 보통은 이 순서대로 알고 있는 편인데, 사실 EBX는 여분의 레

지스터가 생겨난 것이며, 실제로는 EAX, EDX, ECX, EBX 등의 순서로 기억하는 것이 맞다. 물론 뭐

가 제대로 된 순서인지 안다는 것만으로 어셈블리 실력이 느는 것은 아니다. 하지만 적어도 A, B, C,

D의 의미가 알파벳 순서가 아니라 그 나름의 약어로 표현한 것이라고 알아두면 좀 더 깊이 있는 지식

이 되지 않을까 한다.

Page 47: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 7

EAX

EAX로 가장 많이 들은 설명으로는 “산술 계산을 하며, 리턴값을 전달한다”였다. 물론 이것은 맞는 말

이며, EAX를 축약했을 때 가장 제대로 된 정의라고 볼 수 있다. 하지만 처음에는 쉽게 와닿지 않는

다. EAX를 쉽게 생각해 보자. EAX는 단지 “변수”이며, 조금 더 구체적인 표현으로는 “가장 많이 쓰는

변수”라고 할 수 있다. 그리고 변수라서 당연히 계산식에 사용되며, 더하기, 빼기, 곱셈, 나눗셈 등에

EAX가 자주 등장한다. 그리고 함수의 리턴값이나 return 100, return FALSE 등의 코드를 사용할 때

이러한 100이나 FALSE에 해당하는 값이 바로 EAX에 기록된다고 생각하면 된다. 별거 없다. 결국 변

수의 하나일 뿐이다. 단지 CPU에서 사용하는 변수라서 어떤 개념서에서도 변수라는 부연 설명을 하진

않지만 전혀 와닿지 않는 분들은 그냥 쉽게 변수라고 생각하자. 그것을 이해할 만한 쉬운 예제는 이후

등장할 EDI까지 대략적으로 설명한 후 풀어나가겠다. EAX의 A는 Accumulator의 약자다.

EDX

이것 역시 변수의 일종이라고 생각하자. EAX와 역할은 같되, 리턴 값의 용도로는 사용되지 않는다.

EDX 역시 각종 연산에 쓰이며, 더하고 빼고 곱하는 용도로 이용된다. 가끔은 곱하기나 나누기 등에서

좀더 복잡한 연산이 필요할 때 덤으로 쓰이기도 한다. 하지만 역시 변수의 용도로 쓰인다고 생각하자.

EDX에서 D는 Data의 약자다.

ECX

C의 약자부터 얘기하는 것이 더 이해하기 쉽겠다. 이것은 Count의 약자로, 루프문을 수행할 때 카운

팅하는 역할을 한다. for 문에서 int i라고 선언할 때 i의 역할이라고 생각하면 쉽다. 다만 보통 우리가

사용하는 for 문에서는 i++;를 많이 사용하며, i가 특정 조건에 도달할 만큼 커지면 루프를 중단하는데,

반대로 ECX는 미리 루프를 돌 값을 넣어놓고(예를 들어, 5바퀴를 돈다면 먼저 5를 넣어놓고), i--;를

한다. 즉, 감소시키며 루프 카운터가 0이 될 때까지 카운팅한다. 카운팅할 필요가 없을 때는 변수로 사

용해도 무방하다.

Page 48: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 8

EBX

EBX도 사실 별거 없다. 어떤 목적을 가지고 만들어진 레지스터가 아니므로 레지스터가 하나쯤 더 필

요하거나 공간이 필요할 때 등 적당한 용도를 프로그래머나 컴파일러가 알아서 만들어서 사용하고 있

다. EAX, EDX, ECX가 부족할 때 사용하기도 한다.

ESI, EDI

역시 마찬가지로 CPU가 사용하는 변수의 일종이라고 생각하면 쉽다. 다만, EAX ~ EDX는 주로 연산

에 사용되지만 ESI는 문자열이나 각종 반복 데이터를 처리 또는 메모리를 옮기는 데 사용된다. 보통 이

런 설명이 많다. “ESI는 시작지 인덱스(Source Index ), EDI는 목적지 인덱스(Destination Index )

로 사용된다” 이 말 역시 제대로 축약한 의미이며, 정확한 설명이라고 볼 수 있다. 하지만 역시 쉽게

와닿지 않는다. 이렇게 생각하자. memcpy (void *dest, void *src, size_t count )는 두번째 인자

(source )에서 첫번째 인자(destination )로 메모리를 복사한다. 마찬가지로 ESI와 EDI 역시 source

와 destination으로, ESI에서 메모리를 읽어 EDI로 복사한다고 생각하면 간단하다. 실제로 strcpy ( )

나 memcpy ( )에서도 ESI와 EDI를 이용한다(나중에 다시 설명하겠지만 물론 복사할 메모리 크기가

아주 크지 않은 경우는 굳이 ESI, EDI를 사용하지 않는다). Source와 Destination을 기억해 두자.

그리고 경우에 따라 al, ah 등의 레지스터도 보이는데, 이것은 16비트 레지스터로 크기가 반 정도 작다

고 생각하면 쉽다. 아래 그림을 보자.

EAX 등의 레지스터는 32비트, 즉 4바이트의 크기다. 하지만 AX는 16비트, 즉 2바이트이며 AH와 AL

은 각각 8비트, 즉 1바이트의 크기다. 예를 들어, EAX가 0xaabbccdd라면 ccdd는 ax에 해당하고,

cc는 ah이며, dd는 al에 해당한다. 실제 디버거 화면을 보면서 알아보자.

Page 49: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 9

지금 EAX는 0x78563412라는 변수가 담겨져 있다. 자, 이제 아래와 같은 코드를 가동해 보자.

mov ah, byte ptr ds:[esi]

mov al, byte ptr ds:[esi]

mov ax, word ptr ds:[esi]

먼저 간단하게 해당 3줄의 어셈코드가 어떤 의미인지 해석해 보자. esi 주소에 담긴 값을 바이트 단위

로 (즉 1바이트만 가져와서) ah에 넣으라는 것이 첫 번째 라인이고, 같은 값을 al에 넣으라는 것이 두

번째 라인이다. 마지막 라인은 ax에 값을 넣으라는 코드다. 위 그림을 다시 보면서 아래의 디버깅 화면

과 비교해 보자. ah와 al은 각각 1바이트이며, ax는 워드로 2바이트의 크기를 차지한다는 것을 감안하

며 실제 트레이싱을 하겠다(현재 ESI에 담긴 0x401020 번지에는 83 EC 40 56라는 값이 담겨있다.

따라서 이 값들이 eax에 옮겨지는 모습이 지금부터 포착될 것이다).

esi : 0x401020 – 83 EC 40 56

첫 번째 라인이 실행된 후에 EAX를 1바이트 단위로 잘라서 4바이트로 생각해 볼 때 두 번째 바이트인

0x34 값이 0x83로 바뀌며, 0x78563412가 0x78568312으로 변경되었다. ah에 해당하는 위치에 새

로운 값이 들어갔다.

Page 50: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 10

esi : 0x401020 – 83 EC 40 56

두 번째 라인이 실행된 후에는 네 번째 바이트인 0x12가 0x83으로 바뀌었다. 이곳이 al에 해당하는 곳

이다. 그래서 EAX는 0x78568383이 되었다.

esi : 0x401020 – 83 EC 40 56

세 번째 라인이 실행된 후에는 0x8383이었던 값이 EC83으로 바뀌었다. ax에 해당하는 위치로 2바이

트가 바뀌었음을 알 수 있다. 이제 EAX, AX, AL, AH 등이 어떤 관계인지 쉽게 알 수 있지 않은가?

자, 그러면 이와 같은 16비트 레지스터가 각 EAX, EDX…별로 어떤 것이 있는지 표로 정리해 보자. 외

우기 힘들다는 분도 계신데, A, B, C, D로 시작하는 각 레지스터명에 L과 H만 붙이면 된다는 점을 착

안하면 굳이 외우지 않고 레지스터 이름만 봐도 충분히 어떤 녀석인지 판별할 수 있다(L은 low, H는

high의 약자다).

32비트 16비트 상위 8비트 하위 8비트

EAX AX AH AL

EDX DX DH DL

ECX CX CH CL

EBX BX BH BL

Page 51: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 11

리틀 엔디언

바이트 저장 순서는 엔디언(endian)이라고 한다. 쉽게 설명하면 우리가 흔히 사용하는 순서의 숫자는 빅 엔

디언(big endian)이라고 하며, 이것의 반대 방향은 리틀 엔디언(little endian)이라고 한다. 쉽게 예를 들어보자.

0x12345678이라는 DWORD 값이 있다. DWORD는 4바이트 값이며, 0x12345678이라는 숫자는 1바이트씩 총 4바

이트 값을 저장하게 된다. 그렇다면

12 34 56 78

로 4바이트가 된다. 그리고 이것은 빅 엔디언 방식이다. 반면 리틀 엔디언은 오른쪽부터 읽는 방식이다. 쉽게 생각

해서 한자를 읽을 때 오른쪽으로 읽을 때가 있는데, 그것과 비슷하다. 0x12345678을 리틀 엔디언으로 읽는다면

78 56 34 12

가 된다. 이것은 인텔 CPU에서 채택한 방법이고 포인터 컨트롤 속도와도 연관이 있지만, 복잡한 설명은 그만두고

오른쪽부터 읽는 것을 리틀 엔디언, 보통의 순서대로 읽는 것을 빅 엔디언이라고 생각하면 된다. 리버스 엔지니어

링을 할 때 대부분의 2바이트 또는 4바이트 값은 리틀 엔디언을 사용한다고 생각하면서 바이너리를 해석하는 습

관을 들이자.

그럼 여기까지 해두고 이번에는 실제로 EAX ~ EDX가 변수에 불과하다는 증거를 실제 코드로 살펴보

자. 다음 코드를 보면 Plus ( )라는 간단한 함수가 있다. a, b라는 인자를 두 개 받아 더한 결과를 반환

하는 아주 단순한 코드다.

int Plus(int a, int b)

{

return a + b;

}

이것을 빌드한 후 디스어셈블해 보면 아래와 같다.

mov eax, dword ptr ss:[esp+8]

mov ecx, dword ptr ss:[esp+4]

add eax, ecx

retn

이미 알고 있는 분들은 알겠지만 아직 어셈블리를 마저 공부하지 않은 사람들을 위해 설명을 덧붙이면,

첫 번째 파라미터 a는 [esp+4]에 담겨 있으며, 두 번째 파라미터 b는 [esp+8]에 담겨 있다. 이것을 각

Page 52: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 12

각 eax와 ecx에 mov로 담는다.

add eax, ecx

그리고 add 명령어는 두 오퍼랜드의 값을 더해서 첫 번째 오퍼랜드에 담는다는 내용이다. 앞에서 연산

은 레지스터를 이용한다고 했다. 만약 메모리 스택 안에서 연산이 가능했다면 다음과 같이 연산을 시도

했을 것이다.

add [esp+8] , [esp+4]

하지만 분명히 어셈블리에서 연산은 레지스터를 이용한다고 했다. 따라서 위와 같은 코드는 존재할 수

없다. 메모리끼리는 연산할 수가 없다. 따라서 [esp+8]과 [esp+4]에 들어 있는 값을 레지스터인 eax

와 ecx에 담고 그것끼리 add 연산을 시작했다.

retn

그리고 eax에는 이미 리턴값인 두 인자의 덧셈 결과가 담겨 있다. 이대로 리턴해 버리면 된다. 자, 간

단하지 않은가? 그럼 이 상태에서 레지스터의 역할을 보기 위해 몇 가지 바꿔 보자. 다음 코드로 이어

진다.

mov ebx, dword ptr ss:[esp+8]

mov edx, dword ptr ss:[esp+4]

add edx, ebx

mov eax, edx

retn

구조는 똑같지만 레지스터가 바뀌었음을 알 수 있다. 첫 번째 파라미터인 [esp+4]를 ecx에 넣었던 것

을 edx에 넣도록 바꾸고, 두 번째 파라미터인 [esp+8]를 eax에 넣었던 것을 ebx에 넣도록 바꿨다. 그

리고 이것을 edx에 더한 다음 그것을 다시 eax에 넣고 리턴하게 했다. 결과는 어떨까? 비교를 위해 앞

에서 본 Plus ( ) 함수와 함께 실행되도록 위 어셈블리 구문을 이용해 PlusAsm ( )라는 함수를 만들어

실제로 호출해 보았다.

Page 53: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 13

어셈블리로 구현한 PlusAsm()

#include <windows.h>

#include <stdio.h>

int Plus(int a, int b)

{

return a + b;

}

__declspec(naked) PlusAsm(int a, int b)

{

__asm

{

mov ebx, dword ptr ss:[esp+8]

mov edx, dword ptr ss:[esp+4]

add edx, ebx

mov eax, edx

retn

}

}

void main(int argc, char *argv[])

{

int value = Plus(3,4);

printf("value: %d\n", value);

int value2 = PlusAsm(3,4);

printf("value2: %d\n", value2);

return;

}

Page 54: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 14

결과가 똑같다는 것을 알 수 있다. esi와 edi를 사용해도 마찬가지다. 결과는 동일하다.

mov esi, dword ptr ss:[esp+8]

mov edi, dword ptr ss:[esp+4]

add esi, edi

mov eax, esi

retn

물론 컴파일러가 이런 식으로 레지스터를 목적에 부합하지 않게 엉망으로 사용해 가며 코드를 만들어

내진 않을 것이다. 각 레지스터에도 당연히 각기 고유한 목적이 있으므로 그 목적에 맞게 사용되는 것

이 사실이다. 하지만 그 목적 자체를 너무 중요하게 생각하다 보니 기본 개념 자체를 이해하지 못하고

있는 경우가 있어서 이런 웃기는 코드를 한번 만들어서 레지스터가 별거 아니라는 내용을 증명해 보았

다. 레지스터는 이처럼 단순하다. 레지스터는 단지 CPU가 사용하는 변수에 불과하다. 연산과 값 처리

를 위해 존재하므로 그냥 보이는 그대로 해석하면 된다. 레지스터를 결코 어렵게 생각하지 말자.

__declspec(naked)

맨 처음 등장한 코드 앞에 접두어로 붙은 것이 무엇인지 궁금한 분이 계실지도 모르겠다. naked라는 의미는 ‘나체

가 되었다’라는 뜻으로 코드가 벌거벗은 형태라고 생각할 수 있다. 실제로 함수 하나를 만들어서 빌드해 보면 컴파

일러는 내부적으로 해당 함수에서 변수를 몇 개 사용하고, 구조체를 몇 개 사용하는지 등에 대한 내용을 분석해 관

련 데이터 덩어리를 사용할 수 있는 만큼의 스택을 준비한다. 그래서 이후 함수의 구조에서도 나오겠지만 함수의

엔트리 포인트에서는 개발자가 작성한 코드의 첫 줄이 등장하는 것이 아니라 컴파일러가 자체적으로 생성한 스택

을 확보하는 작업에 대한 코드부터 등장한다. naked는 그것을 방지하기 위한 접두어다. naked를 사용하면 이제

부터 이 함수 안에서는 부수적인 코드를 전혀 사용하지 않을 것이라고 지정하게 되며, 컴파일러는 이 함수 안에 어

떤 자체적인 코드도 생성하지 않는다. 심지어 리턴값조차 컴파일러가 만들어주지 않는다. 따라서 naked 함수를

만들려면 개발자가 스택이나 변수 할당, 레지스터 사용 등의 모든 처리 내용을 모두 작성해야 한다. 보통 이번 예

제처럼 순수 어셈블리로 만들어진 코드를 작성할 때 이와 같이 naked를 사용한다. 앞으로도 naked 함수는 이 책

곳곳에 등장할 것이다. 다시 한번 나올 때 또 설명하도록 하겠다.

Page 55: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 15

외울 필요가 없는 어셈블리 명령어

지금까지 설명한 내용을 제대로 숙지했다면 어셈블리를 처음 보는 사람이더라도 대략 어셈블리가 어떤

식으로 굴러가는지 대략은 파악했을 것이다. 이제는 옵코드에 해당하는 명령어를 살펴보자. 필자는 개

인적으로 악기를 좋아하는데, 악기나 음악에 대한 부분을 프로그래밍과 결부시켜 비교하는 작업을 즐

겨 쓰는 편이다(아마 책을 읽다 보면 앞으로도 계속 나올 것이다). 많은 사람들이 통기타를 한번 배우

고 싶어한다. 통기타는 코드 몇 개만 외우면 바로 연주가 가능할 정도로 친숙하지만, 코드가 그 상황에

잘 잡히지 않는다는 어려움이 있다. 그래서 통기타를 처음 익히는 사람들을 보면 일단 코드를 잡는 것

이 먼저라고 보고 기계적으로 코드 책을 펼쳐보면서 여섯 줄의 기타 위에 손가락을 올려가며 각 손가락

이 짚는 자리를 외우려 애쓴다. 결과는 어떨까? 필자의 경험상 이런 식으로 접근하는 통기타 입문자들

은 90% 이상 중도에 포기하는 모습을 많이 봤다. 실제로 통기타 책에 나오는 각종 코드는 입문자가 어

떤 노래를 연주하느냐에 따라 평생 한 번도 짚어보지 않을 코드가 수두룩하다. 그러나 책에 나온 순서

대로 기계적으로 코드를 외워 가고 있으니 힘겹게 외운 코드가 나중에 잘 쓰이지도 않아서 결국은 잊어

버리게 되고, 오히려 진짜 중요한 코드를 익히는 데에 써야 할 시간을 엉뚱한 곳에 낭비하고 만다.

// scan all dll if tr->dwPid = dwDebugeePid; tr->dwScanOption = DC_MEMCMP; tr->hDlg = hDlgWnd; tr->hShowText = hShowText; tr->dwModuleCount = dwModuleCount; HANDLE hThread = CreateThread(NULL, 0, ScanAllDllThread, (LPVOID)tr, 0, &dwThreadId); }

(SendMessage(hSelectAllDll, BM_GETCHECK, 0, 0) == BST_CHECKED) { // …… PTHREAD_PARAM tr; tr = (PTHREAD_PARAM)malloc(sizeof(THREAD_PARAM));

// scan all dll if (SendMessage(hSelectAllDll

필자는 바로 이러한 경우가 어셈블리 명령과 유사하다고 생각한다. 어셈블리 명령어는 정말 너무나 많

다. 그래서 사실 그것을 다 암기하기란 사실상 불가능에 가깝다. 필자도 당연히 다 외우고 있지 못하다.

심지어 어셈블리 입문서나 인터넷에서 찾을 수 있는 어떤 리버스 엔지니어링 관련 자료에서 소개한 필

수라고 붙여놓은 어셈블리 명령어 중에서도 필자가 모르는 것이 수두룩하다. 기타 코드도 마찬가지로

책에는 필수 코드라고 나와 있지만 실제로는 거의 쓰지도 않는 코드일 때도 많다. 물론 그렇게 잘 몰라

도 기타를 연주하거나 리버스 엔지니어링 관련 업무를 하는 데는 크게 지장이 없다.

Page 56: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 16

그렇다고 모든 명령어와 코드를 무시하라는 이야기는 아니다. 가장 기본적이며 필수적인 몇 개만 확실

히 이해해 두고, 나머지는 필요할 때마다 찾아서 보는 방법으로도 충분하다는 의미다. 그리고 기본적으

로 익혀야 할 옵코드는 구조가 서로 다른 것들끼리 외워두는 것이 좋다. 필수 명령어만 머릿속에 집어

넣고 나머지는 실제 리버스 엔지니어링을 할 때 해당 어셈블리 구문이 나오기 전까지는 굳이 펼쳐보지

않아도 무방하다.

PUSH, POP

스택에 값을 넣는 것을 PUSH, 그리고 스택에 있는 값을 가져오는 것이 POP이다. 프로그래밍해본 사

람치고 스택을 모르는 분은 없으므로 굳이 설명할 필요는 없는 부분이다(PUSHAD, POPAD는 모든

레지스터를 PUSH하고 POP하라는 명령어다). 오퍼랜드는 push eax, push 1, push edx… 등과 같

이 1개만 있으면 된다.

MOV

지금까지 설명한 내용을 열심히 읽었다면 역시 아주 쉬운 명령어일 것이다. MOV는 단지 값을 넣는 역

할을 한다. 가령 MOV eax, 1은 eax에 1을 넣는 코드가 되고, MOV ebx, ecx는 ebx에 ecx를 넣는

코드가 된다.

LEA

LEA는 MOV와 헷갈려 하는 분들이 많아서 좀더 예를 들어가며 상세히 설명하겠다. LEA는 아주 단순

하다. 주소를 가져오라는 얘기다. MOV가 값을 가져오는 것이라면 LEA는 주소를 가져온다. 따라서

LEA는 가져올 src 오퍼랜드가 주소라는 의미로 대부분 [ ]로 둘러싸여 있다. 몇 가지 예를 들어보자.

가정 : 레지스터와 메모리에 다음과 같은 값이 들어 있다.

esi : 0x401000 (esi에는 0x401000이라는 값이 들어 있다)

*esi : 5640EC83 (esi가 가리키는 번지에는 5640EC83라는 값이 들어 있다)

esp+8 : 0x13FF40

*(esp+8) : 33

lea eax, dword ptr ds:[esi]

: esi가 0x401000이므로 eax에는 0x401000이 들어온다.

Page 57: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 17

mov eax, dword ptr ds:[esi]

: esi가 0x401000이므로 eax에는 0x401000 번지가 가리키는 5640EC83이라는 값이 들어온다.

lea eax, dword ptr ss:[esp+8]

esp+8은 스택이며, eax에는 0x13FF40라는 값이 들어온다

mov eax, dword ptr ss:[esp+8]

esp+8은 스택이며, eax에는 0x13FF40가 가리키는 값인 33이 들어온다

MOV와의 차이점을 확실히 알 수 있을 것이며, 사실 MOV보다 간단하다.

ADD

위의 예제에서도 나왔다시피 src에서 dest로 값을 더하는 명령어다.

SUB

ADD와 반대되는 뺄셈 명령어로, ADD와 쌍으로 생각하자.

INT

인터럽트를 일으키는 명령어다. 뒤의 오퍼랜드로 어떤 숫자가 나오느냐에 따라 각기 다른 처리가 일어

난다. MS-DOS 시절에는 애플리케이션에서 즉시 인터럽트를 일으킬 수 있는 무풍지대의 공간이라

INT로 갖가지 응용이 가능했지만 현재의 32비트 시대에서는 애플리케이션 레벨에서의 인터럽트는 한

계가 있고, ring0 레벨로 내려가기 전까진 거의 사용할 일이 없는 명령어가 되었다(네이티브 API를 호

출할 때 이용되지만 그 내용은 이 장의 수준을 벗어나므로 나중에 다루겠다). 아마 리버스 엔지니어링

을 하다 보면 가장 많이 만나는 것은 INT 3 명령어로 옵코드가 0xCC인 DebugBreak ( ) 정도일 것이

다.

CALL

함수를 호출하는 명령어다. CALL 뒤에 오퍼랜드로 번지가 붙는다. 해당 번지를 호출하고 작업이 끝나

면 CALL 다음 번지로 되돌아온다. 왜냐하면 CALL로 호출된 코드 안에서는 반드시 RET를 만나게 되

어 다시 호출한 쪽으로 돌아오기 때문이다. 나중에 “함수의 구조” 부분에서 좀 더 자세히 설명한다.

Page 58: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 18

INC, DEC

INC는 i++;이고 DEC는 i--;라고 생각하자.

AND, OR, XOR

dest와 src를 연산한다. 다만 XOR은 dest와 src를 동일한 오퍼랜드로 처리 가능한데, 예를 들어 XOR

EAX, EAX를 수행하면 EAX가 0이 된다. 같은 값으로 XOR을 하면 0이 되기 때문에 XOR로 같은 오

퍼랜드를 전달했을 때 이것은 변수를 0으로 초기화하는 효과를 줄 수 있다. 그 밖에 AND, OR, XOR

를 모르는 분은 없을 것으로 생각한다. 이 책은 전산개론 서적이 아니므로 만약 비트 연산에 대해 모르

는 사람은 인터넷을 참고하자.

NOP

아무것도 하지 말라는 명령어다. 해킹이나 리버스 엔지니어링에서 가장 많이 쓰이는 명령어이기도 하

다.

CMP, JMP

비교해서 점프하는 명령어

이 정도만 훑어봐도 이후 나오는 어셈블리 명령어는 사실 그때그때 검색하면서 찾는 것으로도 충분히

보완할 수 있다. 영어 원서를 보면서 사전 없이 100% 모두 읽을 수 있는 사람이 있는가? 한국어로 된

책을 볼 때도 사전이 없으면 무슨 뜻인지 모를 때가 있다. 그런 상황을 위안 삼아 모르는 명령어가 나올

때 굴하지 말고 구글을 띄워 어셈블리 명령어를 검색하자. 인텔 매뉴얼을 가지고 있어도 좋다. 잘 찾아

보면 사전처럼 전체 명령어가 정리된 훌륭한 문서를 발견하는 행운을 맛볼 수도 있다.

리버스 엔지니어링에 필요한 스택

스택(stack )은 이미 자료구조에서 충분히 배웠을 것이다. 스택 하면 생각나는 것은 LIFO (Last In

First Out )으로 먼저 넣은 것이 나중에 나오고, 나중에 넣은 것이 먼저 나오는 구조라고 할 수 있다. 리

버스 엔지니어링을 위해 스택에 관해 알아야 할 지식은 일단 다음과 같다. 하나씩 알아보자.

Page 59: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 19

1. 함수 호출 시 파라미터가 들어가는 방향

2. 리턴 주소

3. 지역 변수 사용

다음 장에서 좀더 상세히 설명하겠지만 함수 안에서 스택을 사용하게 되면 보통 다음과 같은 코드가 함

수의 엔트리 포인트에 생성된다.

push ebp

mov ebp, esp

sub esp, 50h

이 코드를 한번 해석해 보자. 먼저 ebp 레지스터를 스택에 넣는다. 그리고 현재 esp의 값을 ebp에 넣

는다. 자, 이제 ebp와 esp가 같아지면서 이제 이 함수에서 지역변수는 ebp에서부터 얼마든지 계산할

수 있다. ebp를 기준으로 오프셋을 더하고 빼는 작업으로 스택을 처리할 수 있게 된다는 이야기다.

그리고 sub esp, 50h는 esp에서 50h만큼을 뺀다는 의미인데, 스택은 LIFO 특성으로 인해 아래로 자

란다. 따라서 특정 값만큼 뺀다는 것은 그만큼 스택을 사용하겠다는 이야기가 된다. 즉, 50h만큼 지역

변수를 사용하겠다고 해석할 수 있다.

그렇다면 이제 생각해 보자, ebp가 현재 함수에서 스택의 맨 위가 되었고, 첫 번째 번지가 되었다. 그

리고 사이즈를 빼가며 자리를 확보하고 있으므로 결국 지역 변수는 “-” 마이너스의 형태로 계산이 가

능하다. 4바이트 단위로 움직이는 변수라고 가정했을 때 ebp-4라면 첫 번째 지역변수가 될 것이고,

ebp-8은 두 번째 지역변수가 될 것이다. 즉 ebp-x 형태로 변수를 계산할 수 있다.

함수의 호출

그렇다면 이번에는 파라미터에 대해 알아보자. 예를 들어, HelloFunction이라는 함수가 있다.

DWORD 타입으로 3개의 인자를 받는 함수 타입이다.

DWORD HelloFunction(DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)

Page 60: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 20

이 함수를 다음과 같이 호출했다고 해보자.

HelloFunction 호출

main()

{

DWORD dwRet = HelloFunction(0x37, 0x38, 0x39);

if (dwRet)

// ……

}

위 코드를 리버스 엔지니어링해 보면 다음과 같다.

push 39h

push 38h

push 37h

call 401300h

함수의 인자는 스택에 값을 LIFO 순서대로 넣기 때문에 실제 소스 코드에서 호출한 것과는 반대로 들

어간다. 따라서 위와 같은 양상을 보인다. call 401300h 안으로 들어가서 생각해 보면 mov esp, ebp

코드를 거치기 때문에 아까 지역 변수를 봤을 때는 ebp-x 등과 같이 마이너스로 스택에 보관된 변수

를 사용했는데, 파라미터를 push로 넣어 놓았기 때문에 이 값들에 접근하려면 ebp에서 오프셋을 더

하는 방식으로 계산해야 한다. 즉 파라미터는 ebp+x 형태로 계산할 수 있다. ebp+8이 첫 번째 인자

인 37h 이며, ebp+0xc가 두 번째 인자인 38h, ebp+0x10이 세번째 인자인 39h가 된다.

리턴 주소

그렇다면 거론하지 않은 ebp+4에는 무엇이 있을까? ebp+4에는 이 함수가 끝나고 돌아갈 리턴 주소

가 담긴다. 직접 눈으로 확인해 보자. HelloFunction 함수 안에 다음과 같이 리턴 주소를 가져오는 어

셈블리 코드를 삽입해 보자.

리턴 주소 출력

DWORD HelloFunction(DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)

{

Page 61: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

01. 리버스 엔지니어링만을 위한 어셈블리 21

DWORD dwRetAddr = 0;

__asm

{

push eax

mov eax, [ebp+4]

mov dwRetAddr, eax

pop eax

}

printf(“dwRetAddr: %08x\n", dwRetAddr);

}

결과를 보면 dwRetAddr은 “i f (dwRet ) ”의 위치를 출력한다는 사실을 알 수 있다.

HelloFunction ( )을 호출한 뒤 호출한 쪽의 다음 번지가 바로 리턴 주소다.

지금까지의 내용은 아래 그림으로 요약할 수 있다.

이상으로 리버스 엔지니어링에 필요한 어셈블리를 간략하게 살펴봤다. 설명을 누락한 부분은 플래그

부분인데, 이 부분은 기계적인 설명이 들어가기보다는 2부, “리버스 엔지니어링 중급”에서 실전 코드와

함께 좀더 상세하게 다룰 예정이니 성격이 급하신 분들은 이후 장을 건너뛰고 바로 그쪽부터 살펴보고

와도 무방하다.

Page 62: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

프로그래밍의 기본은 C 문법이며, 리버스 엔지니어링의 기본은 C 코드가 어셈블리

로 바뀌었을 때 어떻게 해석하느냐다. 어셈블리 코드를 눈으로 살펴볼 때 머릿속에

서는 그와 동시에 어셈 코드 덩어리가 C 코드로 변환되는 현상이 일어나야 한다.

C에도 문법이 있듯이 리버스 엔지니어링에 사용되는 어셈블리 코드에도 정해진 문

법이 있다. 마구잡이로 흐트러져 있는 듯한 어셈블리 코드의 홍수 안에서 C 코드를

추출해 내는 과정이 몸에 배어야 한다.

02 C 문법과 디스어셈블링

함수의 기본 구조, 함수의 호출 규약(__cdecl, __stdcall, __fastcall, __thiscall), 조건문, 반복문, 구조체와 API

호출

키 워 드

Page 63: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 23

대부분의 사람들은 어셈블리보다 C 소스에 강한 법이다. 문법 사용이 제한적이며, 언제나 하나의 명령

어로 수많은 문장을 조합해서 코딩해야 하는 어셈블리는 확실히 보통 사람들에게 친숙해지기 어려운

면이 있다. 특히 C 문법을 먼저 공부한 사람들에게는 C 코딩의 스타일대로만 생각하도록 굳어져 있기

때문에 어셈블리 방식으로 생각하기가 쉽지 않은 편이다. 하지만 어셈블리는 앞서 살펴본 대로 생각 외

로 간단한 언어이며, 접근 방식에 따라 오히려 더 쉽게 읽어내려갈 수도 있다. 그 이유는 뭘까? 사실 C

코드에서 자주 사용되는 코드가 항상 비슷하듯이 어셈블리 역시 코드에 일정한 패턴이 있기 때문이다.

따라서 그런 유형과 패턴을 잘 알아놓는다면 어셈 코드를 역분석하는 리버스 엔지니어링 작업도 아주

어려운 작업으로 느껴지지 않을 수 있다. 이번 장에서는 C 문법뿐 아니라 어떤 언어에서든 가장 기본적

으로 등장하는 조건문과 반복문 등에 대해 어셈블리로 어떻게 표현되는지 살펴보겠다. 함수 규약, 구조

체 등에 대해서도 그 구조를 알아보고, 개발자들이 흔히 사용하는 코드 패턴이 어셈블리로 어떻게 변환

되는지 그 규칙을 몸소 느낄 수 있을 것이다.

함수의 기본 구조

리버스 엔지니어링의 기본을 익히기 위해서는 먼저 함수에 대해 알아야 한다. 소스 코드는 당연히 수많

은 함수로 구성돼 있으며, 각 함수의 역할을 파악하는 것이 필수다. 함수가 하는 기능이 무엇인가에 대

한 굵은 가지부터 파악하는 것으로 시작해, 세세하게는 파라미터가 몇 개이고, 리턴값으로 어떤 것들이

전달되는지까지도 분석할 수 있어야 한다. 그리고 그러한 작업이 가능하려면 함수의 몸체가 어셈블리

코드로 어떻게 구성되는지부터 습득할 필요가 있다.

리버스 엔지니어링을 할 때 가장 골치 아픈 것 중 하나가 개발자가 직접 코딩한 부분이 아닌 빌드 시에

컴파일러가 자동으로 생성해내는 코드를 필터링하는 작업이다. 따라서 훌륭한 리버서가 되려면 이러한

군더더기 코드는 가볍게 넘어갈 수 있는 능력이 필요하다. 지면에서 그러한 내용을 다 다룰 수는 없지

만 최소한 함수를 제작할 때 앞뒤로 붙는 코드에 대해 간단하게나마 살펴볼 필요가 있다. sum ( )이라

는 간단한 함수와 그것을 디스어셈블링한 코드를 살펴보자.

함수의 기본 구조

int sum(int a, int b)

{

int c = a + b;

return c;

}

Page 64: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 24

push ebp

mov ebp, esp

push ecx

mov eax, [ebp+arg_0]

add eax, [ebp+arg_4]

mov [ebp+var_4], eax

mov eax, [ebp+var_4]

mov esp, ebp

pop ebp

retn

단지 2줄의 코드인데도 (선언부를 포함해도 3줄), 디스어셈블된 코드를 보니 10줄이나 된다. 이것이

리버스 엔지니어링의 가장 기본적인 어려움이다. 짧은 코드도 어셈블리 구문으로 바뀌면 코드의 양도

길어지고, 더불어 특유의 문법으로 인해 가독성도 매우 떨어진다. 그래서 컴파일러가 자동으로 생성하

는 코드를 걸러내고 중요한 곳만 포착해내는 능력이 필요하다.

자, 시작부터 1장, “리버스 엔지니어링만을 위한 어셈블리”에서 예습한 내용이 나온다. 일단 자세한 의

미 파악은 나중으로 돌리고 예제 코드의 앞뒤 구문을 보자. push ebp와 pop ebp로 끝나는 코드가

보인다. 결론부터 얘기해서 이것이 바로 함수의 처음과 끝이다. 스택을 사용하지 않아도 될 만큼 간단

한 함수라면 예외이긴 하지만 대체로 함수를 만들면 대부분 이러한 인터페이스를 갖게 된다. 그 이유를

생각해보자

push ebp

mov ebp, esp

ebp는 스택 베이스 포인터다. 그리고 push ebp를 통해 지금까지의 베이스 주소를 스택에 보관한다.

그리고 mov ebp, esp를 통해 현재의 스택 포인터인 esp를 ebp로 바꾼다. 즉, 지금까지의 기준이 될

스택 베이스 포인터를 일단 백업해 두고, 새로운 포인터를 잡는 것이다. 함수 안에서 스택을 통해 계속

메모리를 이용할 것이므로 함수의 시작 번지에서는 항상 이 같은 초벌 작업을 진행한다. 다시 말해 함

수의 시작은 곧 새로운 스택을 사용한다고 생각할 수 있다. 그래서 스택 베이스 포인터를 보관해 놓고,

현재의 스택 포인터를 베이스로 잡아두며 새 삶을 시작한다.

그리고 종료 코드는 다음과 같이 나타난다.

Page 65: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 25

mov esp, ebp

pop ebp

함수가 종료되면 지금까지 사용한 스택 위치를 다시 원래대로 돌려놓는다. 그 부분이 push ebp로 시

작해 pop ebp로 끝나는 코드다. 대부분의 함수는 저렇게 시작과 종결을 맺는다. 따라서 저런 모양을

보면 ‘아 함수의 시작과 끝이구나’라고 생각하면 된다. 스택을 사용하지 않는 간단한 함수의 경우에는

이 같은 패턴을 밟지는 않지만 대부분의 함수는 push ebp를 통해 함수의 명줄을 계산한다.

함수의 호출 규약

리버스 엔지니어링을 하기 위해 디버거를 켜고 바이너리를 올려놓았다. 어셈블리가 눈앞에 주르륵 펼

쳐진다. 가장 먼저 뭘 생각해야 할까?

“아, 지금 보는 이 함수의 역할은 무엇이고 파라미터는 이런 구조로 넘어가는구나!”

우리가 리버스 엔지니어링을 할 때 반드시 필요한 것 중 하나는 각 함수의 역할을 파악하는 것이다. 코

드의 목적을 알아낸다면 리버스 엔지니어링 작업의 50%는 달성한 것이나 다름없다. 그리고 그것의 선

행 작업으로 함수가 어떻게 생겼고 인자가 몇 개인지 등에 대한 정보를 추출해 낼 수 있어야 한다. 이제

부터 그 내용을 확인할 수 있는 함수 호출 규약에 대해 알아보자.

함수 호출 규약에는 여러 가지 방식이 있다. 대표적으로 __cdecl, __stdcall, __fastcall, __thiscall

네 가지가 있다. 여기서 우리가 확인할 것은 디스어셈블된 코드를 보고 이것이 어떤 콜링 컨벤션

(calling convention )에 해당하는지 파악하는 것이다. 이를 확인하는 목적은 리버스 엔지니어링을

할 때 call 문을 보고 이 함수의 인자가 몇 개이고 어떤 용도로 쓰이는지를 분석하기 위해서다. 간단한

하나의 함수를 각 호출 규약별로 정의해서 빌드해 보자. 아래에 디스어셈블된 코드가 나와 있다. 먼저

__cdecl부터 확인해 보자.

__cdecl을 사용한 함수의 디스어셈블

int __cdecl sum(int a, int b)

{

int c = a + b;

return c;

}

Page 66: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 26

int main(int argc, char* argv[])

{

sum(1,2);

return 0;

}

sum:

push ebp

mov ebp, esp

push ecx

mov eax, [ebp+arg_0]

add eax, [ebp+arg_4]

mov [ebp+var_4], eax

mov eax, [ebp+var_4]

mov esp, ebp

pop ebp

retn

main:

push 2

push 1

call calling.00401000

add esp, 8

함수 본체 말고 call calling.00401000이라고 돼 있는, 함수를 호출하는 곳을 살펴보는 것이 함수 역

분석의 1차 과제다(이 소스에서는 main ( ) 함수에 해당한다). 항상 call 문의 다음 줄을 살펴서 스택

을 정리하는 곳이 있는지 체크해야 한다. 이 코드처럼 add esp, 8과 같이 스택을 보정하는 코드가 등

장한다면 그것은 __cdecl 방식의 함수라고 생각할 수 있다(__cdecl 방식은 함수 밖에서 스택을 보정

한다). 그리고 해당 스택의 크기로 함수 파라미터의 개수까지 확인할 수 있다. 인자는 4바이트씩 계산

되므로 스택을 8바이트까지 끌어올린다는 점에서 파라미터가 2개인 함수라는 점까지 파악할 수 있다.

자, 지금까지 알아낸 정보를 정리해 보자.

1. __cdecl 방식

call calling.00401000 밑에 add esp, 8을 하는 것으로 봐서 함수를 호출한 곳에서 스택을 보정하는 __cdecl 방식

임.

2. 파라미터는 2개

add esp, 8 그리고 push 문이 2개라는 점을 봐서 4바이트 파라미터가 두 개라는 것을 확인.

Page 67: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 27

3. 리턴 값이 숫자

함수의 맨 마지막 부분에 eax에 들어가는 값이 숫자라는 것을 봐서 리턴 값은 주소 같은 값이 아닌, 숫자임을 확인.

3 )번을 빼더라도 이처럼 스택을 처리하는 코드에서 두 가지 정보를 알 수 있다는 것을 확인했다. 이번

에는 위 함수를 __stdcall 방식으로 바꿔서 다시 디스어셈블해 보자. 다음을 보자.

__stdcall을 사용한 함수의 디스어셈블

int __stdcall sum(int a, int b)

{

int c = a + b;

return c;

}

sum:

push ebp

mov ebp, esp

push ecx

mov eax, [ebp+arg_0]

add eax, [ebp+arg_4]

mov [ebp+var_4], eax

mov eax, [ebp+var_4]

mov esp, ebp

pop ebp

retn 8

main:

push 2

push 1

call calling.00401000

똑같은 C 코드인데도 __cdecl를 붙였느냐 __stdcall를 붙였느냐에 따라 어셈블리 코드가 미묘하게 달

라졌다. 이전 예제와의 차이점으로 add esp, 8을 사용한 코드가 보이지 않는다는 것을 알 수 있다. 이

것은 main ( ) 안에서 sum ( )을 사용한 뒤 어떠한 스택 처리도 없다는 이야기다. 대신 sum ( )의 본체

의 후반부의 리턴문에 그냥 retn이 아닌 retn 8을 했다는 사실을 알 수 있다. 즉, 이 경우에는 함수 안

에서 스택을 처리한다는 것을 알 수 있다. 이런 식으로 __stdcall 방식은 함수 안에서 스택을 처리한다.

그래서 8바이트의 스택 보정과 파라미터가 2개라는 판단은 함수 내부에서 확인해야 한다. 대표적으로

Win32 API는 __stdcall 방식을 이용한다. Win32 API 하나를 직접 분석해 보자.

Page 68: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 28

MessageBoxA ( )를 디스어셈블한 모습이다. 77D30830 번지에서 retn 10을 한다는 것을 알 수 있

다. retn 10은 16진수이며, 10진수로는 16즉, 4 x 4가 된다. 여기서 볼 수 있듯이, 또 한 가지 참고할

만한 점은 retn을 보면 알 수 있는 정보들이다. retn이 보이고 retn 10 같은 별도의 숫자가 보이지 않

는 상태에서 call 후에 add esp, x도 보이지 않는다면 이 함수는 __stdcall 방식이자 파라미터가 없는

경우라고 볼 수 있다.

아무튼 MessageBox ( )의 경우 인자가 4개이므로 정확히 16바이트만큼 스택 처리를 한다. 아래는

MessageBox ( )의 프로토타입이다. 역시 인자가 4개라는 것을 다시 확인할 수 있다.

int MessageBox(

HWND hWnd, // handle to owner window

LPCTSTR lpText, // text in message box

LPCTSTR lpCaption, // message box title

UINT uType // message box style

);

이번에는 __fastcall을 살펴보자. 다음 예제를 보자.

__fastcall을 사용한 함수의 디스어셈블

int __fastcall sum(int a, int b)

{

int c = a + b;

return c;

}

sum:

push ebp

mov ebp, esp

sub esp, 0Ch

mov [ebp+var_C], edx

Page 69: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 29

mov [ebp+var_8], ecx

mov eax, [ebp+var_8]

add eax, [ebp+var_C]

mov [ebp+var_4], eax

mov eax, [ebp+var_4]

mov esp, ebp

pop ebp

retn

main:

push ebp

mov ebp, esp

mov edx, 2

mov ecx, 1

call sub_401000

xor eax, eax

pop ebp

retn

sub esp, 0Ch로 스택 공간을 확보하고 edx 레지스터를 사용한 것을 알 수 있다. __fastcall은 함수의

파라미터가 2개 이하일 경우, 인자를 push로 넣지 않고 ecx와 edx 레지스터를 이용한다. 메모리를 이

용하는 것보다 레지스터를 사용하는 것이 속도가 훨씬 빠르다. 따라서 __fastcall을 사용하는 경우에는

인자가 2개 이하면서 빈번히 사용되는 함수에 쓰이는 편이다. 그러므로 리버스 엔지니어링을 할 때 함

수 호출 전에 edx와 ecx 레지스터에 값을 넣는 것이 보이면 __fastcall 규약의 함수라고 생각할 수 있

다.

다음으로는 __thiscall이다.

Class CTemp

{

public:

int MemberFunc(int a, int b);

};

mov eax, dword ptr [ebp-14h]

push eax

mov edx, dword ptr [ebp-10h]

Page 70: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 30

push edx

lea ecx, [ebp-4]

call 402000

__thiscall은 주로 C++의 클래스에서 이용되는 방법이다. 특징으로는 현재 객체의 포인터를 ecx에 전

달한다는 것이 있다. 이 같은 구조를 설명하자면 클래스에 대한 이론적인 내용을 되새겨보는 것이 좀더

효과적일 듯하다. 먼저 클래스는 객체지향 프로그래밍의 개념이며, 하나의 클래스만 정의해 두면 얼마

든지 여러 개의 독립적인 객체를 만들 수 있다. 따라서 모양은 완전히 동일한 클래스더라도 실제 오브

젝트 입장에서 생각해 보면 이 클래스는 서로 다른 메모리 번지에 존재하게 된다. 그리고 그것을 각각

구분하기 위해서는 현재 자신이 어떤 객체를 이용하고 있는지 구분해줄 값이 필요하다. 그것이 C++에

서는 this 포인터로 사용되고 있다. 이쯤에서 감이 오지 않는가? 바로 ecx로 전달되는 값이 this 포인

터가 된다. 그리고 해당 클래스에서 사용하고 있는 멤버 변수나 각종 값은 다음과 같이 ecx 포인터에

오프셋 몇 번지를 더하는 식으로 사용할 수 있다.

ecx+x

ecx+y

ecx+z

이것이 __thiscall의 특징이다. 물론 인자 전달 방법이나 스택 처리 방법은 __stdcall과 동일하다. 이번

장에서는 C 문법과 리버스 엔지니어링을 다루고 있으므로 일단은 이만큼만 살펴보고, __thiscall은 다

음 장의 C++ 리버스 엔지니어링 부분에서 좀더 상세하게 다룰 예정이니 잠시 후에 다시 알아보자.

알아두기

간단한 C 코드를 콘솔에서 코딩하고 디스어셈블할 때 책이나 인터넷 등에서 나온 것과 조금 다르거나 뭔가 코

드가 생략되는 것 같다는 질문을 받을 때가 종종 있다. 그 이유는 컴파일러 최적화 옵션을 어떻게 사용하느냐에

따라 바이너리의 크기와 생성되는 코드의 모습이 미묘하게 달라지기 때문이다. Optimizations가 기본적으로는

Maximize Speed로 되어 있지만(VC 6.0 기준) 그대로 사용하면 콘솔에서 간단한 함수를 작성하는 경우 최적화 옵

션 때문에 push ebp, mov ebp, esp 등의 코드가 생성되지 않는다. 또 이 경우에는 몸체만 있고 사용하지 않는 함

수는 컴파일러가 필요없는 코드라고 간주해서 아예 빌드에서 제외해 버리기도 한다. 따라서 자신이 작성한 코드를

원본 그대로 확인하고 싶다면 Optimizations 옵션을 Default로 변경해야 한다.

Page 71: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 31

if 문

지금까지 함수의 규약을 대강 살펴봤으니, 이번에는 모든 언어에서 기본 중의 기본이 되는 조건문에 관

해 알아보자. 먼저, C 코드의 조건문이 디스어셈블됐을 때 어떻게 바뀌는지 살펴보겠다. 다음 코드는

간단한 조건문의 예다. 이 코드가 바이너리로 바뀐 뒤 디스어셈블된 부분을 유심히 살펴보자.

조건문 디스어셈블링

int Temp(int a)

{

int b = 1;

if (a == 1)

{

a++;

}

else

{

b++;

}

return b;

}

int main(int argc, char* argv[])

{

Temp(1);

}

.text:00401000 push ebp

.text:00401001 mov ebp, esp

.text:00401003 push ecx

.text:00401004 mov dword ptr [ebp-4], 1

.text:0040100B cmp dword ptr [ebp+8], 1

.text:0040100F jnz short loc_40101C

.text:00401011 mov eax, [ebp+8]

.text:00401014 add eax, 1

.text:00401017 mov [ebp+8], eax

.text:0040101A jmp short loc_401025

Page 72: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 32

.text:0040101C loc_40101C:

.text:0040101C mov ecx, [ebp-4]

.text:0040101F add ecx, 1

.text:00401022 mov [ebp-4], ecx

.text:00401025

.text:00401025 loc_401025:

.text:00401025 mov eax, [ebp-4]

.text:00401028 mov esp, ebp

.text:0040102A pop ebp

.text:0040102B retn

자, 지금까지는 매우 간단한 코드였지만 이제 양이 좀 많아졌다. 하지만 겁먹을 필요는 전혀 없다. 리

버스 엔지니어링의 난제는 코드량이 방대하다는 것이지만, 사실 한줄한줄씩 분석해 가면 어셈블리보다

쉬운 언어는 없기 때문에 누구나 충분히 해석할 수 있다는 사실에 자신감을 갖고 코드를 들여다보자.

지금부터 두 코드를 비교해가며 한 줄씩 천천히 살펴보려고 한다. 분석을 마치고 나면 리버스 엔지니어

링에 좌절하던 사람들의 고통이 사실 알고보니 별거 아니었음을 알게 될 것이다. 그럼 첫 줄부터 따라

가 보자.

push ebp

mov ebp, esp

이젠 여기가 함수의 머리부분이라는 것은 쉽게 파악할 수 있을 것이다.

push ecx

다음으로 보이는 코드로 ecx를 스택에 보관한다. C 소스를 보면 현재 지역 변수는 int b로 1개뿐이

다. 이처럼 변수의 숫자가 적은 경우에는 굳이 스택을 확보할 필요없이 레지스터만 이용해 연산을 처리

한다. 이 b라는 변수를 앞으로 ecx 레지스터에서 사용하기 위해 push 문으로 기존 값을 일단 보관해

놓는 것이다. 맨 처음 “리버스 엔지니어링을 위한 어셈블리”에서 레지스터는 변수에 불과하다고 설명

했다. ecx를 변수로 사용하기 위해 보관해둔다고 가정하자. 참고로 보통 함수의 초반부에 레지스터를

push 문으로 스택에 넣는 코드가 등장한다면 앞으로 이 레지스터를 이 함수에서 계속 연산 목적으로

사용할 것이기 때문이라고 생각하면 된다.

mov dword ptr [ebp-4], 1

Page 73: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 33

다음 코드부터는 스택에 직접 값을 넣는다. [ebp-4]도 앞으로 ecx와 더불어 연산으로 사용될 b 변수

에 해당하는 값이다. 즉, int b=1;라는 초기화 코드에 해당하는 부분이다.

cmp dword ptr [ebp+8], 1

jnz short loc_40101C

mov eax, [ebp+8]

add eax, 1

자, 이곳이 바로 직접적인 if 문이다. if (a == 1 )에 해당하는 코드이며, [ebp+8]은 첫 번째 파라미

터를 가리킨다. 보통 스택을 사용할 때는 “ebp 마이너스” 형식만 봐왔는데 처음으로 플러스가 등장했

다. 이전 장의 내용을 기억하는가? 다시 복습해 보자. [ebp+8]이 첫 번째 파라미터임을 확인하는 방법

은 간단하다. 함수가 호출되면 스택을 사용하는 규약이 있는데, 맨 아래에는 함수가 끝나고 돌아갈 리

턴 주소인 [ebp+4], 그리고 첫 번째 파라미터는 [ebp+8], 두 번째 파라미터는 [ebp+C] 등으로 4바이

트씩 늘어난다는 점을 알아둬야 한다. 따라서 [ebp+8]은 Temp (1 )로 넣어준 첫 번째 인자라는 사실

을 알 수 있다. 아무튼 이와 같은 식으로 그 값이 1인지 비교(cmp )한다. 그래서 그 결과가 0이면(즉

if (a == 1 )에 해당하면) 바로 아랫줄로 가서 [ebp+8]를 eax에 넣고 인자였던 a에 1을 더하게 된다.

eax에 넣고 add를 한 이유는 메모리에서는 바로 연산이 되지 않기 때문에 레지스터를 이용한 것이다.

mov eax, [ebp-4]

mov esp, ebp

pop ebp

retn

그리고 0x401025 번지로 가서 eax에 b 변수의 값을 넣어주고(mov eax, [ebp-4]) 리턴해서 함수

를 끝낸다. eax에는 함수의 리턴값이 들어가기 때문에 void 형이 아닌 함수의 후반부에는 항상 eax

값을 설정하는 코드가 등장한다. 그리고 cmp dword ptr [ebp+8], 1로 다시 돌아가서 결과가 non

zero라면(즉 else라면) b 변수에 1을 더한 후 끝낸다.

우리의 목적은 cmp 이후에 jnz 등이 어떻게 넘어가는지 살펴보는 것이기 때문에 뒷부분까지 분석하진

않았다. 조건문은 이렇게 간단히 구분할 수 있다. 궁극적으로 jnz, jz 등을 처리하기 위한 코드가 대부

분이며, 변수의 처리를 위해 레지스터를 이용한다는 사실을 확인할 수 있다. 현재 위 예제 코드에는 지

역 변수가 많은 경우는 다루지 않았지만, 그러한 경우에도 마찬가지로 스택을 이용해 각 역할에 해당하

는 변수를 x바이트씩 나눠가며 한줄한줄 트레이스하면 실제 소스와 유사한 수준으로 분석할 수 있다.

Page 74: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 34

반복문

루프문은 for나 while, goto 등이 있지만, 컴퓨터가 보기에는 결국 카운터 레지스터를 이용한 반복 행

위일 뿐이다(실제로도 while 문을 for 문 등으로 얼마든지 바꿔서 코딩할 수 있지 않은가?). 따라서

for나 while로 구분할 필요가 없고, 현재 트레이스 중인 코드가 반복문이라는 것만 알아낼 수 있으면

된다. 반복 구문이 어떤 식으로 구성되는지 이번에도 실제 C 소스와 비교해 가면서 살펴보자. 다음 예

제를 보자.

반복문 디스어셈블링

int loop(int c)

{

int d;

for (int i=0; i<=0x100; i++)

{

c--;

d++;

}

return c+d;

}

.text:00401000 push ebp

.text:00401001 mov ebp, esp

.text:00401003 sub esp, 8

.text:00401006 mov dword ptr [ebp-8], 0

.text:0040100D jmp short loc_401018

.text:0040100F mov eax, [ebp-8]

.text:00401012 add eax, 1

.text:00401015 mov [ebp-8], eax

.text:00401018 cmp dword ptr [ebp-8], 100h

.text:0040101F jg short loc_401035

.text:00401021 mov ecx, [ebp+8]

.text:00401024 sub ecx, 1

.text:00401027 mov [ebp+8], ecx

.text:0040102A mov edx, [ebp-4]

.text:0040102D add edx, 1

Page 75: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 35

.text:00401030 mov [ebp-4], edx

.text:00401033 jmp short loc_40100F

.text:00401035 mov eax, [ebp+8]

.text:00401038 add eax, [ebp-4]

.text:0040103B mov esp, ebp

.text:0040103D pop ebp

.text:0040103E retn

인자를 하나 받고, 0x100번 만큼 해당 인자에서 1씩 뺀 후, 별도 변수에 0x100번 만큼 1씩 더하는 간

단한 코드다. 기본 코드는 이미 앞에서 모두 살펴봤으니 여기서는 for 문의 핵심 코드만 살펴보자.

.text:0040100F mov eax, [ebp-8]

.text:00401012 add eax, 1

.text:00401015 mov [ebp-8], eax

0x40100F 번지가 for 문에서 i++에 해당하는 부분이다. 현재 지역변수 i에 해당하는 코드는[ebp-8]

에 위치해 있고, 그것을 eax 레지스터를 이용해 1을 더하고, 그 값을 다시 i 지역변수인 [ebp-8]에 넣

었다.

.text:00401018 cmp dword ptr [ebp-8], 100h

.text:0040101F jg short loc_401035

그리고 0x401018 번지가 for 문에서 넣었던 반복문의 시작이다. 방금 설명한 대로 dword ptr [ebp-

8]이 int i로 선언한 지역변수이며, 이 값이 현재 0x100인지 비교해 0x100보다 크면 0x401035 번지

로 점프한다.

.text:00401035 mov eax, [ebp+8]

.text:00401038 add eax, [ebp-4]

.text:0040103B mov esp, ebp

.text:0040103D pop ebp

.text:0040103E retn

0x401035 번지에서는 return c + d에 해당하는 코드가 된다. 메모리끼리 바로 연산을 수행할 수 없

으므로 각각 c에 해당하는 [ebp+8]을 eax에 넣고, 그 eax와 d 변수에 해당하는 [ebp-4]를 더한다.

그리고 eax에 결과값이 들어 있는 상태에서 리턴한다.

Page 76: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 36

.text:00401021 mov ecx, [ebp+8]

.text:00401024 sub ecx, 1

.text:00401027 mov [ebp+8], ecx

.text:00401030 mov [ebp-4], edx

.text:00401033 jmp short loc_40100F

만약 jg short loc_401035의 조건에 부합하지 않는다면(즉, 0x100이 되지 않는다면) 바로 아래로 내

려가 for 문 안의 코드를 수행할 것이다. 이때 0x401033 번지에서 보이는 것처럼 다시 위 코드로 올라

가는 경우, 그리고 그 위치에 해당하는 코드가 적당한 값을 더하거나 빼면서 어떤 특정한 값과 cmp한

다면 이 디스어셈블한 코드는 반복문으로 봐도 좋다. 대부분의 for 문에는 이러한 패턴과 규칙이 있으

므로 확실히 기억해 두자. 기본 반복문은 이렇다. 몇 가지 변칙 패턴이 있긴 한데, 그와 관련된 내용은

2부, “리버스 엔지니어링 중급”에서 좀더 자세히 다루겠다.

구조체와 API Call

C 문법에서 또 중요한 것이 구조체의 사용이다. 구조체의 각 멤버 변수가 어떤 식으로 사용되는지 살

펴볼 필요가 있다. 더불어 함수를 사용할 때의 규약을 위에서도 잠시 살펴봤지만 인자가 들어가는 상

황에서는 디스어셈블된 코드가 어떻게 변경되는지 알아둬야 한다. 리버스 엔지니어링을 할 때는 스택

포인터만 보고 구조체의 크기가 얼마이고 이 API의 인자로는 어떤 것이 들어가는지 파악하는 것이 필

수다. 양쪽 모두를 간단히 알아볼 수 있는 것과 동시에 분석하기에 크게 부담되지 않는 예제 코드를 하

나 살펴보자. 다음 예제코드는 STARTUPINFO와 PROCESS_INFORMATION 구조체를 이용해

CreateProcess ( )로 새 프로세스를 생성하는 코드다. 구조체도 2개가 사용되며, CreateProcess ( )의

파라미터 개수도 적당하며, 코드 후반부에는 구조체 멤버 변수를 활용하는 부분까지 있다. 분석하기에

부담도 없으며 초보자가 공부하기에 적격인 예제다. 다음 코드를 보자.

프로세스 생성 코드

void RunProcess()

{

STARTUPINFO si;

PROCESS_INFORMATION pi;

ZeroMemory( &si, sizeof(si) );

Page 77: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 37

si.cb = sizeof(si);

ZeroMemory( &pi, sizeof(pi) );

// Start the child process.

if( !CreateProcess( NULL,

"MyChildProcess",

NULL,

NULL,

FALSE,

0,

NULL,

NULL,

&si,

&pi )

)

{

printf( "CreateProcess failed.\n" );

return;

}

// Wait until child process exits.

WaitForSingleObject( pi.hProcess, INFINITE );

// Close process and thread handles.

CloseHandle( pi.hProcess );

CloseHandle( pi.hThread );

}

C 코드부터 간단히 해석해 보면 STARTUPINFO와 PROCESS_INFORMATION 구조체를 선

언한 뒤 CreateProcess ( )를 호출한다. 그러면 두 구조체에는 생성된 새 프로세스와 관련된 값

이 들어오며, 해당 구조체의 멤버 변수인 프로세스 핸들을 이용해 프로세스가 종료될 때까지

WaitForSingleObject ( )로 대기한다. 그리고 프로세스가 종료되면 관련 핸들을 닫아 주는 것이 전부

다. 다음은 코드를 보자. 위 코드를 디스어셈블한 코드다.

디스어셈블한 프로세스 생성 코드

0x401000 PUSH EBP

0x401001 MOV EBP,ESP

Page 78: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 38

0x401003 SUB ESP,54

0x401006 PUSH 44

0x401008 PUSH 0

0x40100A LEA EAX,DWORD PTR SS:[EBP-54]

0x40100D PUSH EAX

0x40100E CALL calling.004011A0

0x401013 ADD ESP,0C

0x401016 MOV DWORD PTR SS:[EBP-54],44

0x40101D PUSH 10

0x40101F PUSH 0

0x401021 LEA ECX,DWORD PTR SS:[EBP-10]

0x401024 PUSH ECX

0x401025 CALL calling.004011A0

0x40102A ADD ESP,0C

0x40102D LEA EDX,DWORD PTR SS:[EBP-10]

0x401030 PUSH EDX

0x401031 LEA EAX,DWORD PTR SS:[EBP-54]

0x401034 PUSH EAX

0x401035 PUSH 0

0x401037 PUSH 0

0x401039 PUSH 0

0x40103B PUSH 0

0x40103D PUSH 0

0x40103F PUSH 0

0x401041 PUSH calling.00407030

0x401046 PUSH 0

0x401048 CALL DWORD PTR DS:CreateProcessA

0x40104E TEST EAX,EAX

0x401050 JNZ SHORT calling.00401061

0x401052 PUSH calling.00407040

0x401057 CALL calling.0040116F

0x40105C ADD ESP,4

0x40105F JMP SHORT calling.00401081

0x401061 PUSH -1

0x401063 MOV ECX,DWORD PTR SS:[EBP-10]

0x401066 PUSH ECX

0x401067 CALL DWORD PTR DS:WaitForSingleObject

0x40106D MOV EDX,DWORD PTR SS:[EBP-10]

0x401070 PUSH EDX

Page 79: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 39

0x401071 CALL DWORD PTR DS:CloseHandle

0x401077 MOV EAX,DWORD PTR SS:[EBP-C]

0x40107A PUSH EAX

0x40107B CALL DWORD PTR DS:CloseHandle

0x401081 MOV ESP,EBP

0x401083 POP EBP

0x401084 RETN

다시 얘기하지만, 디스어셈블된 코드로 바뀌었을 때 내용이 많다고 해서 결코 겁먹을 필요가 없다. 한

줄씩 분석하다 보면 머릿속에서 C 소스로 바뀌어가는 모습이 차츰 그려질 것이다. 먼저 첫 줄부터 살펴

보자.

함수의 시작

0x401000 push ebp

0x401001 mov ebp, esp

이제 많이 본 코드이므로 가볍게 넘어가자. 함수의 시작이라고 생각하면 된다.

스택 확보

0x401003 sub esp, 54

중요한 부분이다. 지금까지 살펴본 코드는 변수가 몇 개 되지 않았기 때문에 레지스터로 충분히 충당할

수 있었지만 지금은 구조체가 등장한 상황이다. 따라서 레지스터만으로는 이 크기를 다 감당할 수 없으

며 스택을 늘려서 공간을 확보해야 한다. 그럼 0x54바이트만큼 스택을 늘린 이유는 무엇일까? 우리가

사용한 구조체는 2개다. 두 구조체의 레이아웃을 보자.

STARTUPINFO와 PROCESS_INFORMATION 구조체

typedef struct _STARTUPINFO {

DWORD cb;

LPTSTR lpReserved;

LPTSTR lpDesktop;

LPTSTR lpTitle;

DWORD dwX;

DWORD dwY;

DWORD dwXSize;

DWORD dwYSize;

Page 80: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 40

DWORD dwXCountChars;

DWORD dwYCountChars;

DWORD dwFillAttribute;

DWORD dwFlags;

WORD wShowWindow;

WORD cbReserved2;

LPBYTE lpReserved2;

HANDLE hStdInput;

HANDLE hStdOutput;

HANDLE hStdError;

} STARTUPINFO, *LPSTARTUPINFO;

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess;

HANDLE hThread;

DWORD dwProcessId;

DWORD dwThreadId;

} PROCESS_INFORMATION;

멤버 변수의 개수를 세어서 데이터 타입의 바이트 크기대로 계산해 보면 STARTUPINFO 구조체의 크

기는 0x44바이트이고 PROCESS_INFORMATION은 0x10바이트다. 두 값을 더하면 0x54바이트라

는 숫자가 나온다. 그렇다. sub esp, 54로 늘려놓은 스택 크기는 바로 저 두 구조체의 메모리 공간을

할당하기 위한 것이다. 리버스 엔지니어링할 때 저 스택 확보 코드만 보고 구조체라는 것을 바로 판단

할 수는 없겠지만(왜냐하면 구조체가 아닌 지역 변수만으로 0x54 바이트만큼 사용할 수도 있으므로)

적어도 0x54바이트만큼의 메모리를 사용한다는 점은 파악할 수 있어야 한다. 지금은 일단 0x54바이

트만큼의 메모리 덩어리를 쓴다고 생각하며 다음으로 넘어가 보자.

0x401006 PUSH 44

0x401008 PUSH 0

0x40100A LEA EAX,DWORD PTR SS:[EBP-54]

0x40100D PUSH EAX

0x40100E CALL calling.004011A0

0x40102A ADD ESP,0C

ZeroMemory( &si, sizeof(si) );

Page 81: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 41

이 코드가 바로 ZeroMemory ( )에 해당하는 코드다. STARTUPINFO 구조체의 크기는 위에서 살펴

봤듯이 0x44바이트였다. 0x40100A에서는 STARTUPINFO 구조체인 [EBP-54]의 포인터를 eax에

넣고 ZeroMemory ( )의 인자로 전달한다. 그런데 push 문이 3개인 것으로 보아 파라미터는 3개라고

분석되는데, ZeroMemory ( )의 인자는 2개뿐이다. 이유는 무엇일까? 이는 ZeroMemory ( )가 매크

로 함수라서 바이너리로 변환된 실제 프로토타입과 다르기 때문이다. 즉 다음 코드와 같이 인자가 3개

인 memset ( )으로 전처리된 구문이며, 궁극적으로는 memset ( )을 이용하게 된다. 따라서 바이너리

로는 memset ( )의 인자 개수대로 변환된 것이다.

ZeroMemory 전처리문

#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))

#define ZeroMemory RtlZeroMemory

어쨌든 0x44바이트만큼을 0으로 바꾸는 것으로 봐서 그만한 크기로 데이터를 초기화하는 것은 구조체

라는 예상에 좀더 근접한다. 다음으로 등장하는 CALL calling.004011A0 함수 호출은 memset ( )에

해당하는 함수다. 여기서 함수의 규약 부분에서 배운 내용이 다시 등장한다. 함수를 호출한 후 ADD

ESP,0C로 스택을 보정하는 것으로 봐서 memset ( ) 함수는 __cdecl 규약의 함수라고 생각할 수 있다.

memset ( )의 인자가 3개이므로 4 * 3 = 12로, 0xC바이트만큼 스택을 정리해 준다.

구조체의 첫 번째 멤버 변수 처리

0x401016 MOV DWORD PTR SS:[EBP-54],44

이번에는 MOV 명령어가 등장했다. 본격적으로 어떤 값을 넣는다고 생각할 수 있다. [EBP-54]

는 이미 특정 구조체의 선두 번지라는 1단계 분석을 마친 상태다. 그곳에 4바이트만큼 0x44라

는 값을 넣고 있으니, 이는 구조체의 첫 번째 멤버 변수에 0x44를 넣으라는 것으로 판독할 수 있

다. STARTUPINFO 구조체의 첫 번째 멤버 변수는 DWORD cb다. 따라서 이 코드는 si.cb =

sizeof (si )가 된다.

PROCESS_INFORMATION 초기화

0x40101D PUSH 10

0x40101F PUSH 0

0x401021 LEA ECX,DWORD PTR SS:[EBP-10]

0x401024 PUSH ECX

0x401025 CALL calling.004011A0

0x40102A ADD ESP,0C

Page 82: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 42

STARTUPINFO 구조체의 ZeroMemory ( )에 해당하는 코드와 동일하므로 자세한 설명은 생략한다.

다만 push로 넣는 크기만 0x10바이트로 다르며, 앞에서는 0x44바이트, 이번에는 0x10바이트 규모

를 초기화했다. 그리고 맨 처음 sub esp, 54라는 코드로 인해 메모리 덩어리는 0x54바이트의 공간을

사용한다는 것을 확인할 수 있었다. 이 정도쯤이면 다음과 같이 한방에 눈치를 채야 한다.

“이 함수 안에서는 크기가 각각 0x44바이트와 0x10바이트인 구조체 두 개를 사용한다. 그리고 모두 memset()으로 초기

화한 것이 전체 스택의 크기와 일치하는 것으로 봐서 별도의 지역변수는 존재하지 않는다.”

이번 구조체는 PROCESS_INFORMATION에 대한 초기화임을 알 수 있다. 이 구조체는 [EBP-10]

에 위치해 있다.

CreateProcess

0x40102D LEA EDX,DWORD PTR SS:[EBP-10]

0x401030 PUSH EDX

0x401031 LEA EAX,DWORD PTR SS:[EBP-54]

0x401034 PUSH EAX

0x401035 PUSH 0

0x401037 PUSH 0

0x401039 PUSH 0

0x40103B PUSH 0

0x40103D PUSH 0

0x40103F PUSH 0

0x401041 PUSH calling.00407030

0x401046 PUSH 0

0x401048 CALL DWORD PTR DS:CreateProcessA

이것이 바로 CreateProcess ( )를 호출하는 코드다. 앞에서 구체적으로 설명하진 않았지만 CALL 문

이전에 사용되는 PUSH가 어떤 것인지 눈치가 빠른 사람은 이미 이해했으리라 생각한다. CALL 문 이

전에 호출되는 PUSH는 바로 함수의 인자다. 그리고 이것은 원래 순서와 반대로 들어간다. 그 이유는

스택의 LIFO (Last In First Out ) 특성 때문이다. 따라서 쉽게 생각해서 CALL 문 밑에서부터 거꾸로

위치를 세면 그것이 인자가 전달되는 순서가 된다. 0x40102D 번지를 보자. [EBP-10]의 번지를 EDX

에 넣고 PUSH를 함으로써 CreateProcess ( )의 마지막 인자에 넣는다. [EBP-10]는 어디일까? 스택

을 54h만큼 뺐고 거기서 -44h만큼 제외한 위치는 앞에서 계산한 STARTUPINFO 구조체의 크기와

일치한다. 따라서 STARTUPINFO 구조체의 크기만큼 빼면, 그 위치는 PROCESS_INFORMATION

이다. 마찬가지로, [EBP-54]는 STARTUPINFO의 위치다. 즉, 두 개의 포인터를 넣는 &si, &pi라는

코드가 된다.

Page 83: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

02. C 문법과 디스어셈블링 43

NULL 리턴 시 에러 처리

0x40104E TEST EAX,EAX

0x401050 JNZ SHORT calling.00401061

0x401052 PUSH calling.00407040

0x401057 CALL calling.0040116F

0x40105C ADD ESP,4

0x40105F JMP SHORT calling.00401081

함수의 리턴값은 EAX에 들어오므로 CreateProcess ( )의 리턴값을 체크해서 0이 아니면 점

프해서 계속 진행하고, 그렇지 않으면 “CreateProcess failed.\n”을 출력하는 코드다. CALL

calling.0040116F은 printf ( )가 된다.

대기 후 클로즈 루틴

0x401061 PUSH -1

0x401063 MOV ECX,DWORD PTR SS:[EBP-10]

0x401066 PUSH ECX

0x401067 CALL DWORD PTR DS:WaitForSingleObject

0x40106D MOV EDX,DWORD PTR SS:[EBP-10]

0x401070 PUSH EDX

0x401071 CALL DWORD PTR DS:CloseHandle

0x401077 MOV EAX,DWORD PTR SS:[EBP-C]

0x40107A PUSH EAX

0x40107B CALL DWORD PTR DS:CloseHandle

위 코드를 보면 WaitForSingleObject ( )에 두 개의 인자를 넣고 대기하는 코드가 보인다. 첫 번

째 인자에는 핸들이 들어가야 하는데, 첫 번째로 넣는 [EBP-10]은 아까 얘기했다시피 PROCESS_

INFORMATION 구조체이고 거기서 DWORD PTR SS:로 4바이트를 넣으니 이것은 즉 첫 번째 인자

인 HANDLE hProcess가 된다. 그리고 두 번째 인자로는 -1을 전달하는데, 이것은 INFINITE로 선

언돼 있는 기정의 값이다. 이 값은 어디에 선언돼 있을까? 플랫폼 SDK에 설치돼 있는 WinBase.h 파

일의 선언부를 찾아보자.

Page 84: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 44

이처럼 헤더를 통해 인자를 찾는 법은 나중에 살펴볼 “MFC 리버스 엔지니어링”에서 계속 설명하

기로 하고, 다음 부분을 마저 살펴보자. WaitForSingleObject ( )가 리턴된 이후 각각의 인자로

CloseHandle ( )을 실행하는데, [EBP-10]은 방금 확인한 프로세스의 핸들이다. 그럼 [EBP-C]는 무

엇일까? 0x10이 PROCESS_INFORMATION니까 거기서 0xC바이트만큼 이동하기 위해서는 4바이

트를 증가시켜야 한다. 구조체 순서를 살펴보니 그것은 HANDLE hThread 다. 즉 스레드의 핸들을

닫는 코드로 판독할 수 있다.

함수 종료

0x401081 MOV ESP,EBP

0x401083 POP EBP

0x401084 RETN

스택을 원래대로 복구해 놓고 함수를 종료한다.

설명이 좀 길었지만 생각 외로 어렵지 않았을 것이다. 실제로 디버거나 디스어셈블러 등이 문자열이나

API Call 등에 대해서도 상세한 정보가 될 수 있게 표기해 주므로 그쪽으로 방향을 잡아서 분석하는 것

도 도움이 될 수 있다. 더불어 리버스 엔지니어링 경험이 늘어날수록 API 콜이나 파라미터의 PUSH 구

조만 봐도 이것이 어떤 역할을 하는 함수이고, 현재 사용되는 스택에 어떤 모양의 구조체가 있는지 파

악하는 수준까지도 도달할 것이다.

결론

이상으로 C 문법의 기본인 함수의 구조와 콜링 컨벤션, 조건문, 반복문, 구조체, API CALL 등에 대해

살펴봤다. 어느 정도 정형화된 것만 골라 봤는데, 이것만으로는 사실 부족한 부분이 많다. 리버스 엔지

니어링에 능숙해지려면 일단 코딩 경험이 풍부해야 하며, 더불어 많은 코드를 디스어셈블해 봐야 한다.

어셈블리에 원래 강한 사람이 아니라면 C 소스와 역분석된 코드를 매치시키는 가장 좋은 방법은 역시

원본 소스코드와 비교해 가면서 직접 확인해 보는 것이다. 변환된 부분을 그냥 눈으로 훑지 말고 레지

스터 변수 하나라도 가벼이 여기지 않는다는 자세로 스택 처리도 바이트 단위까지 샅샅히 조사해야 한

다. 그러다 보면 디스어셈블된 코드를 보는 눈이 한 단계씩 업그레이드될 것이다.

Page 85: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

call, 조건문, 점프 구문 등으로 어느 정도 직관적인 예측을 할 수 있었던 C 문법에

비해 C++는 멤버 함수, private 함수, 생성자, 소멸자, 상속 등 분석하기가 어려운

다양한 개념이 많은 편이다. 이번 장에서는 리버스 엔지니어링을 할 때 객체지향

개념을 어떻게 확인할 수 있을지 C++ 문법에 견주어 하나씩 소개한다.

03 C++ 클래스와

리버스 엔지니어링

클래스 멤버 변수 분석, this 포인터 분석, 전역 변수, 동적 할당 했을 때의 코드 분석, 생성자와 소멸자 코드,

캡슐화, 다형성

키 워 드

Page 86: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 46

사실 C 코드의 경우에는 함수, 조건문, 반복문이라는 일정한 규칙이 있고, 이것은 어셈블리에서 call

문, Jmp 문 등으로 쉽게 대입된다. 하지만 C++로 넘어오면 얘기가 달라진다. 일단 C++에서는 클래

스가 등장하고, 멤버 변수와 멤버 함수가 필요하며, 캡슐화나 상속 등의 개념까지도 필요해진다. 이러

한 것들이 개발자 입장에서는 좀더 쉽고 간편한 코딩을 위해 기반이 마련된 것이지만, 리버스 엔지니어

링을 할 때는 오히려 걸림돌이 되는 사항이다. 일단 컴파일러가 빌드를 하고 나면, 결국 0과 1의 조합

으로 코드가 만들어지고, 어셈블리로 읽을 때 현재 보고 있는 함수가 그냥 함수인지 특정 클래스의 멤

버 함수인지, 그리고 이 변수는 전역변수인지 생성자는 어떤 식으로 구성돼 있는지 등 파악하기 어려운

부분이 많아진다. 즉 구조적 뼈대가 C++과 어셈블리는 1:1로 매칭되지 않으므로 직관적으로 파악하

기가 쉽지 않은 편이다. 그렇다면 객체지향 프로그래밍은 바이너리 코드로 바뀌었을 때 어떻게 접근해

야 할까?

C++ 분석의 난해함

C++ 바이너리를 디스어셈블했을 때 해당 어셈블리 코드는 이것이 멤버 변수이고 저것은 클래스 선언

부라고 친절하게 가르쳐 주지 않는다. 메모리 사용량과 함수의 구조적인 부분을 살펴보면서 클래스라

는 것을 리버서 스스로 판단해야만 한다. 그리고 그것은 절대로 쉬운 작업이 아니다. C 코드를 분석할

때처럼 눈에 확 들어오는 정형화된 모습이 없기 때문이다. 또 오히려 어떤 부분은 클래스 멤버 함수인

지 그냥 함수인지 상당히 파악하기 힘든 경우도 있다. 그렇다면 C++ 코드를 분석하기 위해 대체 어떤

것을 준비해야 할까?

먼저 가장 필요한 것은 리버서의 풍부한 코딩 경험이다. C++로 작성된 코드의 특징을 알고 나서 해당

바이너리를 객체지향적 프로그래밍에 근거한 단서를 하나씩 발견해 나가는 작업이 필요하다. 그리고

다음으로 필요한 것은 C++을 디스어셈블할 때의 “감”이다. 현재 코드는 클래스의 어떤 부분이고, 어떤

역할을 하고 있다는 것 정도는 파악할 수 있는 힘을 길러야 한다. 리버서의 코딩 경험은 가르쳐서 되는

것이 아니므로 스스로 쌓으라고 얘기하겠지만, C++ 리버스 엔지니어링 시에 “감”을 얻을 수 있는 방법

들은 정형화된 것이 있으므로 학습을 통해 얼마든지 업그레이드할 수 있다. 자, 그럼 C++ 리버스 엔지

니어링의 “감”을 익히는 방법을 지금부터 소개한다.

Page 87: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 47

클래스 뼈대

Employee 클래스

#include "stdafx.h"

#include "windows.h"

#include "tchar.h"

class Employee

{

public :

int number;

char name[128];

long pay;

void ShowData();

void Test();

};

void Employee::ShowData()

{

printf("number: %d\n", number);

printf("name: %s\n", name);

printf("pay: %d\n", pay);

Test();

return;

}

void Employee::Test()

{

printf("Test fuction\n");

return;

}

// Employee kang;

int main(int argc, char* argv[])

{

Employee kang;

Page 88: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 48

printf("size: %X\n", sizeof(Employee));

kang.number = 0x1111;

_tcscpy(kang.name, _T("강병탁"));

kang.pay = 0x100;

kang.ShowData();

return 0;

}

고용인이라는 매우 간단한 클래스를 만들었고, 각 클래스의 멤버 변수에 다양한 값을 넣어보았다.1 그

리고 하나의 멤버 함수를 만들고 그 함수를 호출하는 코드까지 추가했다. 지금부터 이 클래스를 리버스

엔지니어링해 보며 객체지향 프로그래밍에 근거한 각 특성을 파악해 보겠다. 먼저 이 코드를 빌드한 후

어셈블리 코드로 변환된 코드를 보자. ShowData ( ), Test ( ), main ( )로 크게 3개의 함수가 있으므로

각 함수에 해당하는 디스어셈블 코드를 분류했다.

ShowData()

.text:00401000 sub_401000 proc near ; CODE XREF: _main+46 p

.text:00401000

.text:00401000 var_4 = dword ptr -4

.text:00401000

.text:00401000 push ebp

.text:00401001 mov ebp, esp

.text:00401003 push ecx

.text:00401004 mov [ebp+var_4], ecx

.text:00401007 mov eax, [ebp+var_4]

.text:0040100A mov ecx, [eax]

.text:0040100C push ecx

.text:0040100D push offset aNumberD ; "number: %d\n"

.text:00401012 call sub_4010BA

.text:00401017 add esp, 8

.text:0040101A mov edx, [ebp+var_4]

1 디스어셈블된 코드에서 확인하기 쉽게 16진수로 표기했다. 가끔 이런 리버싱용 예제 코드는 10진수로 만들어 놓고, 디스어셈블하며 계산기를 꺼내

다시 16진수로 바꾸는 사람들이 있다. 우리의 목표는 16진법 공부가 아니므로 예제 애플리케이션을 만들 때는 바이너리상에서 찾기 쉽게 처음부터

16진수로 코딩하는 습관을 들여놓자.

Page 89: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 49

.text:0040101D add edx, 4

.text:00401020 push edx

.text:00401021 push offset aNameS ; "name: %s\n"

.text:00401026 call sub_4010BA

.text:0040102B add esp, 8

.text:0040102E mov eax, [ebp+var_4]

.text:00401031 mov ecx, [eax+84h]

.text:00401037 push ecx

.text:00401038 push offset aPayD ; "pay: %d\n"

.text:0040103D call sub_4010BA

.text:00401042 add esp, 8

.text:00401045 mov ecx, [ebp+var_4]

.text:00401048 call sub_401051

.text:0040104D mov esp, ebp

.text:0040104F pop ebp

.text:00401050 retn

Test()

.text:00401051 sub_401051 proc near ; CODE XREF: sub_401000+48 p

.text:00401051

.text:00401051 var_4 = dword ptr -4

.text:00401051

.text:00401051 push ebp

.text:00401052 mov ebp, esp

.text:00401054 push ecx

.text:00401055 mov [ebp+var_4], ecx

.text:00401058 push offset aTestFuction ; "Test fuction\n"

.text:0040105D call sub_4010BA

.text:00401062 add esp, 4

.text:00401065 mov esp, ebp

.text:00401067 pop ebp

.text:00401068 retn

main()

.text:00401069 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401069 _main proc near ; CODE XREF: start+AF p

.text:00401069

Page 90: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 50

.text:00401069 var_88 = dword ptr -88h

.text:00401069 Dest = byte ptr -84h

.text:00401069 var_4 = dword ptr -4

.text:00401069 argc = dword ptr 8

.text:00401069 argv = dword ptr 0Ch

.text:00401069 envp = dword ptr 10h

.text:00401069

.text:00401069 push ebp

.text:0040106A mov ebp, esp

.text:0040106C sub esp, 88h

.text:00401072 push 88h

.text:00401077 push offset aSizeX ; "size: %X\n"

.text:0040107C call sub_4010BA

.text:00401081 add esp, 8

.text:00401084 mov [ebp+var_88], 1111h

.text:0040108E push offset Source ; "강병탁"

.text:00401093 lea eax, [ebp+Dest]

.text:00401099 push eax ; Dest

.text:0040109A call _strcpy

.text:0040109F add esp, 8

.text:004010A2 mov [ebp+var_4], 100h

.text:004010A9 lea ecx, [ebp+var_88]

.text:004010AF call sub_401000

.text:004010B4 xor eax, eax

.text:004010B6 mov esp, ebp

.text:004010B8 pop ebp

.text:004010B9 retn

C++ 코드를 처음 리버스 엔지니어링 해보는 이들에게 클래스를 분석할 때 가장 먼저 무엇부터 고민해

야 할지 물어보면 다음과 같이 답하곤 한다.

“클래스 선언부가 어디에 있는지부터 찾아야 하지 않을까요?”

남이 만든 C++ 소스 코드를 볼 때는 클래스 선언부부터 찾아볼 수도 있겠지만, 아쉽게도 프로그래밍

과 리버스 엔지니어링은 완전히 다른 것이기에 클래스 선언부를 찾으려 했다가는 평생 아무것도 찾지

못할 것이다. 클래스 선언부의 모습은 전혀 없다. 당연하다. 선언부의 코드는 개발자의 이해를 증가시

키기 위해 편의에 맞춘 C++의 문법일 뿐이며, 그것이 컴파일됐을 때 뼈대의 모습은 보이지 않는다. 마

Page 91: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 51

치 변수를 여러 개 선언해도 디스어셈블된 상태에서는 그 형태를 알기 힘든 것처럼 클래스도 마찬가지

로 인터페이스가 쉽게 관찰되리라는 안일한 생각은 하지 않는 것이 좋다.

이럴 때는 컴파일러가 빌드하는 순서를 생각해 봐야 한다. 간단하게 생각해서 코드상의 맨 윗줄부터,

즉 최초로 등장하는 함수부터 디스어셈블된다고 보면 된다. 위의 디스어셈블된 코드를 봐도 맨 처음 등

장하는 것은 바로 Employee 클래스의 ShowData ( )라는 함수다. 컴파일러는 우리가 클래스를 만들

어 놓아도 해당 클래스의 직접적인 몸통이 있는 함수부터 빌드한다. 따라서 그 부분에 착안해 더듬어

가야 한다.

알아두기

이번 장의 디스어셈블된 코드를 보면 스택 부분이 var_4, var_88 등으로 표현돼 있다는 것이 의아하게 여겨질 수

도 있다. 이것은 IDA가 만들어낸 코드의 해석이라고 보면 된다. IDA는 디스어셈블된 코드를 구체적으로 상세화해

서 표현하기로 유명한 툴이다. 예를 들어 아래와 같은 코드를

mov dword ptr ss:[esp+10], 1

다음과 같은 식으로 변환한다.

mov [esp+var_10], 1

그리고 그런 부분은 IDA의 각 함수의 맨 위에 설명돼 있다.

var_F4 = byte ptr -0F4h

var_90 = byte ptr -90h

var_50 = byte ptr -50h

var_C = dword ptr -0Ch

var_4 = dword ptr -4

var_88이 클래스 혹은 구조체라는 생각까지 왔다. 그렇다면 당연히 해당 메모리 영역에 값을 넣을 것

이다. 0x401084 번지를 보면 var_88 번지에 직접 0x1111을 넣고 있다. 해당 객체의 첫 번째 멤버

변수가 되며, 원본 소스상에서 kang.number = 0x1111;에 해당하는 코드다. 중요한 부분은 그다음

0x4010A9 번지의 코드다(일단 직관적으로 필요한 부분만 설명하기 위해 두 내용 사이의 중간 코드는

생략한다). 해당 객체의 변수에 해당하는 var_88의 번지를 ecx에 넣고 sub_401000 함수를 호출하고

있다. 보통 클래스의 멤버 함수를 호출할 때는 현재 메모리 덩어리의 포인터를 ecx에 넣고 함수 호출을

Page 92: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 52

하므로 이런 코드로 클래스임을 짐작할 수 있다. 만약 ecx에 포인터를 넣는 것이 왜 클래스인지 가물가

물한 분들은 이전 장에서 다룬 함수의 호출 규약을 다시 참고하길 바란다.

어쨌든 ecx에 들어간 var_88은 당연히 C++에서의 this 포인터가 된다. 이처럼 클래스 멤버 변수는

ecx에 객체의 포인터를 넣고 함수 호출을 하는 형태가 일반적이다. 함수 호출 직전 ecx에 어떤 값을

넣는 모습을 눈여겨봐야 한다. 초보 리버서들은 ecx를 카운터라고만 외우고 있지만 능숙한 리버서라면

이런 루틴이 왔을 때 한눈에 클래스 멤버 함수라는 것을 온몸으로 느낄 수 있어야 한다. 그런데 혹시,

실제 개발할 때는 거의 사용하지도 않는 this 포인터를 꼭 찾아야 하나, 라는 생각이 들지 않는가? 그

답은 조금 뒤에 나온다.

이전에 나왔던 ShowData ( )를 다시 보자. sub_401000은 ShowData ( )에 해당하는 함수가 된다.

그리고 ecx에는 this 포인터가 전달돼 왔으므로 이를 염두에 두고 한줄씩 살펴보자.

클래스의 첫 번째 멤버 변수 사용

.text:00401004 mov [ebp+var_4], ecx

.text:00401007 mov eax, [ebp+var_4]

.text:0040100A mov ecx, [eax]

.text:0040100C push ecx

.text:0040100D push offset aNumberD ; "number: %d\n"

.text:00401012 call sub_4010BA

ecx에 들어온 포인터는 IDA가 해석해준 var_4 변수에 들어가며, 이제부터 이 변수의 위치를 늘려가

며(즉 포인터를 증가시켜 가며) 클래스의 해당 변수를 사용하게 된다. 앞 장에서 이야기한 대로 this

포인터를 전달하고, 해당 포인터에 오프셋을 더하는 방법으로 클래스의 멤버 변수를 컨트롤하게 된다.

개발자들은 잘 알다시피 코드를 작성할 때 this 포인터는 클래스 멤버 함수 내에서는 생략하고 잘 표기

하지 않지만 실제로는 내부적으로 this가 사용된다는 사실을 잘 알 것이다. 다음 코드는 그와 관련된 내

용이다.

클래스의 두 번째 멤버 변수 사용

.text:0040101A mov edx, [ebp+var_4]

.text:0040101D add edx, 4

.text:00401020 push edx

.text:00401021 push offset aNameS ; "name: %s\n"

Page 93: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 53

첫 번째 변수는 int number였으며, 두 번째 변수는 char name[128]이었던 것으로 기억할 것이다.

int 형이 4바이트니까 var_4를 edx에 넣고, 거기서 4바이트를 증가시켜 다음 변수를 가리키게 한다는

것을 확인할 수 있다. 그리고 문자열이 들어가는 것으로 보아 현재 변수의 크기를 확인할 필요도 없이

다음 변수는 수십 바이트만큼 더 증가시킬 것이라는 예측도 할 수 있다.

클래스의 세 번째 멤버 변수 사용

.text:0040102E mov eax, [ebp+var_4]

.text:00401031 mov ecx, [eax+84h]

.text:00401037 push ecx

.text:00401038 push offset aPayD ; "pay: %d\n"

.text:0040103D call sub_4010BA

.text:00401042 add esp, 8

.text:00401045 mov ecx, [ebp+var_4]

.text:00401048 call sub_401051

역시 예측한 대로 mov ecx, [eax+84h] 코드가 보인다. eax를 84바이트만큼 더 증가시킨다. 첫 번째

멤버 변수가 4바이트였고, 0x84에서 4를 빼면 0x80, 즉 10진수로 128바이트가 된다. 문자열이 들어

가는 배열의 크기는 128바이트라는 것을 알 수 있다. char name[128]과 일치한다. 그리고 0x401045

번지에서 클래스임을 확정짓는 결정적인 단서가 또 등장한다. 이 메모리 덩어리의 시작 번지였던

var_4를 ecx에 넣고 sub_401051 함수를 호출하고 있다. this 포인터를 전달하며 해당 클래스의 또

다른 멤버 함수를 호출한다는 것을 파악할 수 있다. sub_401051 함수는 Test ( )다. 왜 this 포인터에

신경 쓰는지 조금은 파악이 되는가? 이처럼 ecx를 사용하는 this 포인터는 해당 바이너리 코드와 확보

된 스택이 클래스인지 구조체인지 파악하는 데 큰 도움이 된다.

클래스의 수명과 전역변수

다음으로 생각해 볼 것은 클래스의 수명이다. 클래스를 선언하고 나면 개발자는 해당 클래스의 각 멤버

에 접근할 수 있으며, 목적에 맞게 각 멤버를 사용할 수 있다. 그렇다면 그 클래스의 수명은 언제까지일

까? 당연히 이것은 클래스를 어디에 선언했느냐에 따라 달라진다. 맨 처음 코드 같은 경우에는 main ( )

안에서 Employee 클래스를 선언했다. 그러므로 main ( ) 함수가 끝날 때 클래스의 객체는 사라진다.

그러면 리버스 엔지니어링으로 확인할 때는 어떠한 점을 중점적으로 살펴봐야 할까? Employee 클래

스 객체의 유효범위(scope )를 전역으로 바꾼 다음 다시 살펴보자. // Employee kang;이라고 주석

Page 94: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 54

처리된 부분에서 주석 표시를 없애고 main ( ) 안의 Employee kang; 코드는 주석 처리한 후 다시 빌

드해 보자. 다음은 빌드한 결과다.

클래스 객체를 전역으로 만든 뒤 살펴본 main()

.text:00401049 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401049 _main proc near ; CODE XREF: start+AF p

.text:00401049

.text:00401049 argc = dword ptr 8

.text:00401049 argv = dword ptr 0Ch

.text:00401049 envp = dword ptr 10h

.text:00401049

.text:00401049 push ebp

.text:0040104A mov ebp, esp

.text:0040104C mov dword_409908, 1111h

.text:00401056 push offset Source ; "강병탁"

.text:0040105B push offset Dest ; Dest

.text:00401060 call _strcpy

.text:00401065 add esp, 8

.text:00401068 mov dword_40998C, 100h

.text:00401072 mov ecx, offset dword_409908

.text:00401077 call sub_401000

.text:0040107C xor eax, eax

.text:0040107E pop ebp

.text:0040107F retn

달라진 부분이 어디인가? 다음 두 부분이라고 볼 수 있다.

.text:0040104C mov dword_409908, 1111h

.text:00401068 mov dword_40998C, 100h

.text:00401072 mov ecx, offset dword_409908

원래 이 코드는 어땠을까? 클래스를 전역으로 선언하기 전의 코드를 다시 발췌했다.

.text:00401084 mov [ebp+var_88], 1111h

.text:004010A2 mov [ebp+var_4], 100h

.text:004010A9 lea ecx, [ebp+var_88]

Page 95: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 55

원래는 객체의 포인터가 스택에 있었지만 전역으로 선언한 이후에는 dword_40998C 같은 .data

섹션에 있음을 알 수 있다. ecx에 전달되는 this 포인터도 마찬가지다. 즉, dword_40998C,

dword_409908 등은 해당 클래스의 멤버 변수이며, .data 섹션에 들어 있는 것으로 보아 전역 변수임

을 확인할 수 있다. 이처럼 클래스를 전역으로 만들면 public:에 선언된 각종 멤버 변수는 모두 전역변

수가 된다. C 언어를 처음 배울 때 전역변수를 남발하지 말라고 배웠는가? 전역변수를 만들지 않으려

고 갖은 애를 쓰는 학생들을 본 적이 있다. 그냥 전역변수를 사용한 것이나 C++ 클래스에서 public으

로 선언한 변수나 실제로 내부적으로는 다 똑같은 구조가 된다는 점을 기억하기 바란다.

객체의 동적 할당과 해제

이번엔 malloc이나 new 등으로 클래스의 객체를 동적으로 할당했을 때를 살펴보자. 클래스를 동적으

로 할당한 후 번지 접근은 어떻게 살펴볼 수 있는지 확인하는 것이 과제다. 사실 외형상으로는 크게 다

르지 않지만 굳이 차이점을 생각해 보자면 크게 두 가지로 축약할 수 있다. 우선 처음부터 스택을 클래

스 크기만큼 확보하는 것이 아니고, 해당 포인터를 하나 선언해서 사용한다는 점이다. 그리고 메모리

할당 코드가 필요하다는 점이다. 따라서 클래스의 크기는 스택 크기가 아닌 new를 할 때 확인할 수 있

다. 코드를 작성할 때 new 연산자에는 크기를 기록할 필요가 없지만, 내부적으로는 크기를 계산해서

집어넣는 작업이 일어난다. 따라서 컴파일러가 만든 코드가 디스어셈블된 부분이 보이는 것은 물론이

다. 동적 할당으로 변경해서 코드를 작성한 main ( ) 함수를 보자. 다음 두 코드를 보자.

동적 할당으로 변경한 main

int main(int argc, char* argv[])

{

Employee *pkang;

pkang = new Employee;

pkang->number = 0x1111;

_tcscpy(pkang->name, _T("강병탁"));

pkang->pay = 0x100;

pkang->ShowData();

delete pkang;

return 0;

}

Page 96: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 56

동적 할당으로 변경한 main()을 디스어셈블한 코드

.text:004010A9 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:004010A9 _main proc near ; CODE XREF: start+AF p

.text:004010A9

.text:004010A9 lpMem = dword ptr -0Ch

.text:004010A9 var_8 = dword ptr -8

.text:004010A9 var_4 = dword ptr -4

.text:004010A9 argc = dword ptr 8

.text:004010A9 argv = dword ptr 0Ch

.text:004010A9 envp = dword ptr 10h

.text:004010A9

.text:004010A9 push ebp

.text:004010AA mov ebp, esp

.text:004010AC sub esp, 0Ch

.text:004010AF push 88h ; unsigned int

.text:004010B4 call ??2@YAPAXI@Z ; operator new(uint)

.text:004010B9 add esp, 4

.text:004010BC mov [ebp+var_8], eax

.text:004010BF mov eax, [ebp+var_8]

.text:004010C2 mov [ebp+var_4], eax

.text:004010C5 push 88h

.text:004010CA push offset aSizeX ; "size: %X\n"

.text:004010CF call sub_401121

.text:004010D4 add esp, 8

.text:004010D7 mov ecx, [ebp+var_4]

.text:004010DA mov dword ptr [ecx], 1111h

.text:004010E0 push offset Source ; "강병탁"

.text:004010E5 mov edx, [ebp+var_4]

.text:004010E8 add edx, 4

.text:004010EB push edx ; Dest

.text:004010EC call _strcpy

.text:004010F1 add esp, 8

.text:004010F4 mov eax, [ebp+var_4]

.text:004010F7 mov dword ptr [eax+84h], 100h

.text:00401101 mov ecx, [ebp+var_4]

.text:00401104 call sub_401030

.text:00401109 mov ecx, [ebp+var_4]

.text:0040110C mov [ebp+lpMem], ecx

.text:0040110F mov edx, [ebp+lpMem]

Page 97: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 57

.text:00401112 push edx ; lpMem

.text:00401113 call sub_401152

.text:00401118 add esp, 4

.text:0040111B xor eax, eax

.text:0040111D mov esp, ebp

.text:0040111F pop ebp

.text:00401120 retn

.text:00401120 _main endp

new 연산자에서 push 88h를 인자로 전달함으로서 클래스 크기인 0x88바이트만큼 할당한다는 것을

알 수 있으며, eax에 들어오는 할당한 영역은 var_8에 넣는다. 이것이 바로 Employee *pkang;에

해당하는 pkang 포인터다. 이제부터 이 포인터를 이용해 pkang -> number 등의 작업을 진행한다.

그래서 해당 메모리나 스택에 직접 값을 넣지 않고, new 로 메모리를 할당해서 받은 heap 공간에 값

을 넣게 된다. 따라서 다음과 같은 차이점이 생긴다.

예를 들어, 0x100을 넣는 코드도 동적 할당을 받기 전에는 멤버 변수에 값을 넣을 때 이와 같이 스택에

바로 값을 삽입했다.

.text:004010A2 mov [ebp+var_4], 100h

하지만 동적 할당한 후에는 스택에 직접 값을 넣는 부분은 없고 아래처럼 eax+84h라는 오프셋을 더하

는 모습을 볼 수 있다. 즉, 객체가 가리키는 번지에 오프셋을 더하는 방식으로 해당 클래스의 멤버 변수

를 찾고 값을 넣는 작업이 이뤄진다.

.text:004010F4 mov eax, [ebp+var_4]

.text:004010F7 mov dword ptr [eax+84h], 100h

Page 98: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 58

생성자와 소멸자

C++ 입문서를 보면 가장 먼저 등장하는 것이 생성자와 소멸자이며, 우리 또한 순서상으로는 이것부터

다뤘어야 할 것 같지만 이 책에서는 C++의 문법을 설명하는 것이 목표가 아니라서 순서를 조금 달리

했다. 즉, 디스어셈블된 코드상에서 클래스의 구조를 파악하고 this 포인터를 이해하는 것이 우선적으

로 해야 할 일이었기에 생성자와 소멸자를 찾는 작업은 약간 후반부에 배치했다.

그럼 이제부터 생성자와 소멸자를 살펴보자. 코드는 방대하지만 설명할 부분은 그다지 많지 않으므로

금방 이해할 수 있게 구구절절한 설명 없이 직접 눈으로 디스어셈블된 코드를 살펴보는 것이 좋겠다.

먼저 지금까지 사용한 Employee 클래스에 생성자와 소멸자를 넣어 보자.

생성자와 소멸자 추가

class Employee

{

public :

int number;

char name[128];

long pay;

Employee();

~Employee();

void ShowData();

void Test();

};

Employee::Employee()

{

printf("constructor!! \n");

}

Employee::~Employee()

{

printf("Desstructor!! \n");

}

당연히 main ( ) 함수의 코드는 바뀌지 않았다. 그러나 이제 디스어셈블해 보면 main ( ) 함수에 수많은

코드가 추가돼 있는 모습을 확인할 수 있다. 이는 컴파일러가 클래스의 생성자와 소멸자를 처리하기 위

해 내부적으로 생성해서 집어넣은 코드다. 다음 코드를 보자.

Page 99: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 59

main 함수의 디스어셈블링 코드

.text:004010DC ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:004010DC _main proc near ; CODE XREF: start+AF p

.text:004010DC

.text:004010DC var_28 = dword ptr -28h

.text:004010DC var_24 = dword ptr -24h

.text:004010DC var_20 = dword ptr -20h

.text:004010DC var_1C = dword ptr -1Ch

.text:004010DC var_18 = dword ptr -18h

.text:004010DC var_14 = dword ptr -14h

.text:004010DC var_10 = dword ptr -10h

.text:004010DC var_C = dword ptr -0Ch

.text:004010DC var_4 = dword ptr -4

.text:004010DC argc = dword ptr 8

.text:004010DC argv = dword ptr 0Ch

.text:004010DC envp = dword ptr 10h

.text:004010DC

.text:004010DC push ebp

.text:004010DD mov ebp, esp

.text:004010DF push 0FFFFFFFFh

.text:004010E1 push offset unknown_libname_9 ; Microsoft VisualC 2-8/net runtime

.text:004010E6 mov eax, large fs:0

.text:004010EC push eax

.text:004010ED mov large fs:0, esp

.text:004010F4 sub esp, 1Ch

.text:004010F7 push 88h ; unsigned int

.text:004010FC call ??2@YAPAXI@Z ; operator new(uint)

.text:00401101 add esp, 4

.text:00401104 mov [ebp+var_18], eax

.text:00401107 mov [ebp+var_4], 0

.text:0040110E cmp [ebp+var_18], 0

.text:00401112 jz short loc_401121

.text:00401114 mov ecx, [ebp+var_18]

.text:00401117 call sub_401091 ; 생성자

.text:0040111C mov [ebp+var_24], eax

.text:0040111F jmp short loc_401128

.text:00401121

.text:00401121 loc_401121: ; CODE XREF: _main+36 j

.text:00401121 mov [ebp+var_24], 0

Page 100: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 60

.text:00401128

.text:00401128 loc_401128: ; CODE XREF: _main+43 j

.text:00401128 mov eax, [ebp+var_24]

.text:0040112B mov [ebp+var_14], eax

.text:0040112E mov [ebp+var_4], 0FFFFFFFFh

.text:00401135 mov ecx, [ebp+var_14]

.text:00401138 mov [ebp+var_10], ecx

.text:0040113B push 88h

.text:00401140 push offset aSizeX ; "size: %X\n"

.text:00401145 call sub_4011EE

.text:0040114A add esp, 8

.text:0040114D mov edx, [ebp+var_10]

.text:00401150 mov dword ptr [edx], 1111h

.text:00401156 push offset Source ; "강병탁"

.text:0040115B mov eax, [ebp+var_10]

.text:0040115E add eax, 4

.text:00401161 push eax ; Dest

.text:00401162 call _strcpy

.text:00401167 add esp, 8

.text:0040116A mov ecx, [ebp+var_10]

.text:0040116D mov dword ptr [ecx+84h], 100h

.text:00401177 mov ecx, [ebp+var_10]

.text:0040117A call sub_401030

.text:0040117F mov edx, [ebp+var_10]

.text:00401182 mov [ebp+var_20], edx

.text:00401185 mov eax, [ebp+var_20]

.text:00401188 mov [ebp+var_1C], eax

.text:0040118B cmp [ebp+var_1C], 0

.text:0040118F jz short loc_4011A0

.text:00401191 push 1

.text:00401193 mov ecx, [ebp+var_1C]

.text:00401196 call sub_4011C0 ; 소멸자

.text:0040119B mov [ebp+var_28], eax

.text:0040119E jmp short loc_4011A7

.text:004011A0

.text:004011A0 loc_4011A0: ; CODE XREF: _main+B3 j

.text:004011A0 mov [ebp+var_28], 0

.text:004011A7

.text:004011A7 loc_4011A7: ; CODE XREF: _main+C2 j

Page 101: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 61

.text:004011A7 xor eax, eax

.text:004011A9 mov ecx, [ebp+var_C]

.text:004011AC mov large fs:0, ecx

.text:004011B3 mov esp, ebp

.text:004011B5 pop ebp

.text:004011B6 retn

.text:004011B6 _main endp

코드가 상당히 방대해졌다. 중요한 부분만 집어서 살펴보자. 먼저 초기 부분이다.

초기 코드

.text:004010DF push 0FFFFFFFFh

.text:004010E1 push offset unknown_libname_9 ; Microsoft VisualC 2-8/net

runtime

.text:004010E6 mov eax, large fs:0

.text:004010EC push eax

.text:004010ED mov large fs:0, esp

기존에는 없었던 fs 레지스터를 이용한 익셉션 처리 코드가 만들어져 있다. 생성자가 추가되면서 컴파

일러가 내부적으로 예외 관련 코드를 생성했음을 알 수 있다.

생성자

.text:00401117 call sub_401091 ; 생성자

.text:0040111C mov [ebp+var_24], eax

.text:0040111F jmp short loc_401128

.text:00401121

.text:00401121 loc_401121: ; CODE XREF: _main+36 j

.text:00401121 mov [ebp+var_24], 0

.text:00401128

.text:00401128 loc_401128: ; CODE XREF: _main+43 j

.text:00401128 mov eax, [ebp+var_24]

.text:0040112B mov [ebp+var_14], eax

.text:0040112E mov [ebp+var_4], 0FFFFFFFFh

.text:00401135 mov ecx, [ebp+var_14]

.text:00401138 mov [ebp+var_10], ecx

Page 102: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 62

call sub_401091이 생성자에 해당하는 함수 호출이다. 우리는 생성자만 만들었고 생성자는 당연히 리

턴값이 없는 함수지만, 이 함수는 분명히 리턴값이 있으며 해당 리턴값을 이용해 클래스 객체를 사용한

다는 것을 알 수 있다. 이처럼 할당 코드 직후에 어떤 함수가 등장하고, 그 함수가 리턴하는 값을 컨트

롤하며 여러 변수를 제어하는 모습이 보인다면 그 변수는 클래스의 객체이고 그 함수는 클래스의 생성

자라고 볼 수 있다.

알아두기

생성자를 찾는 노하우를 조금만 더 얘기해 볼까 한다. 먼저 클래스를 찾는다. 이 클래스는 엄청나게 방대하다. 그

래서 스택에 어지럽게 널브러진 각종 값이 눈에 쉽게 들어오지는 않을 것이다. 하지만 역으로 생각해서 클래스가

방대하다는 점을 이용해 보자. 덩치가 클수록 당연히 변수도 많아진다. 변수가 많아지고 코드가 길어지면 당연히

초기화하는 작업 역시 많이 필요해진다. 경험상 생성자를 어떤 용도로 사용했는가? 아래와 같은 코드를 즐겨 넣지

않았을까?

dwMoney = 0;

dwHuman = 0;

dwCount = 0;

dwSize = 0;

memset(lpBuffer, 0, BUFFER_SIZE);

bMoney = FALSE;

bSucccess = FALSE

pTest = NULL;

이런 부분을 생각해서 생성자를 찾을 때는 집단으로 여러 변수를 0으로 초기화하는 코드를 찾아보길 권장한다. 그

리고 그 동네에 memset()이나 malloc() 등의 친구들이 함께 거주하고 있다면 해당 함수는 90% 이상 생성자라고

생각할 수 있다. 물론 아닌 코드도 있었지만 필자의 경험상 이런 식으로 접근하는 방법이 성공할 확율이 높았다.

소멸자

.text:00401193 mov ecx, [ebp+var_1C]

.text:00401196 call sub_4011C0 ; 소멸자

.text:0040119B mov [ebp+var_28], eax

.text:0040119E jmp short loc_4011A7

.text:004011A0

.text:004011A0 loc_4011A0: ; CODE XREF: _main+B3 j

.text:004011A0 mov [ebp+var_28], 0

Page 103: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 63

소멸자는 보통 몸체의 맨 뒷부분에 위치하는 편이다. call sub_4011C0이 소멸자에 해당하는 함수 호

출이며, 역시 그 전에 클래스의 객체의 포인터를 ecx에 넣고 있다. 실제 모듈에서도 이런 경우 함수의

안으로 들어가 보면 각종 변수의 메모리를 해제하거나 Close 관련 API를 호출하는 모습이 등장하는

편이다. 이렇게 생성자와 소멸자의 코드를 살펴봤다.

캡슐화 분석

캡슐화(encapsulation )된 구조는 리버스 엔지니어링할 때 어떻게 파악해야 할까? 사실 여기엔 정답

이 없는 것 같다. private 연산자 등은 컴파일러 내부에서 개발자에게 주의를 주는 것이지 바이너리로

변경되면 그것도 역시 평범한 함수가 될 뿐이기 때문이다. 따라서 기존 함수와 다를 바 없다. 직접 살펴

보자. Employee 클래스 private 함수를 넣어 보았다. 아래의 클래스는 수정한 소스코드이고 이어서

등장하는 두 코드는 디스어셈블한 코드다.

private 함수 추가

class Employee

{

public :

int number;

char name[128];

long pay;

Employee();

~Employee();

void ShowData();

void Test();

private:

void TestPrivate();

};

void Employee::TestPrivate()

{

printf("Private fuction\n");

return;

}

Page 104: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 64

private 함수와 해당 함수 호출부를 디스어셈블한 코드

.text:00401000 sub_401000 proc near ; CODE XREF: sub_401030+50 p

.text:00401000

.text:00401000 var_4 = dword ptr -4

.text:00401000

.text:00401000 push ebp

.text:00401001 mov ebp, esp

.text:00401003 push ecx

.text:00401004 mov [ebp+var_4], ecx

.text:00401007 push offset aPrivateFuction ; "Private fuction\n"

.text:0040100C call sub_4011EE

.text:00401011 add esp, 4

.text:00401014 mov esp, ebp

.text:00401016 pop ebp

.text:00401017 retn

.text:00401068 push offset aPayD ; "pay: %d\n"

.text:0040106D call sub_4011EE

.text:00401072 add esp, 8

.text:00401075 mov ecx, [ebp+var_4]

.text:00401078 call sub_4010C4

.text:0040107D mov ecx, [ebp+var_4]

.text:00401080 call sub_401000

ecx에 객체의 포인터를 넣고 함수를 호출하는 것이 일반 멤버 함수를 호출하는 경우와 다를 바 없다.

즉, 바이너리상에서는 이것이 private 함수인지 일반 멤버 함수인지 파악할 방법이 없다. 그래서 분석

상의 한계가 있다. 아쉽지만 캡슐화된 부분을 리버스 엔지니어링으로 100% 찾기는 불가능에 가깝다.

다형성 구조 파악

리버스 엔지니어링을 할 때 다형성(polymorphism )의 경우는 조금 특이하므로 확실히 기억해 둬야

한다. 가상 함수의 경우는 일반적인 함수가 만들어지는 것과는 달리 별도의 테이블이 생성된다. 그리고

이러한 가상 함수는 상속과 관련돼 있어서 테이블에 대한 정보를 생성자와 소멸자에서도 관리한다. 직

접 코드를 보자. Employee 클래스의 Test ( ) 멤버 함수를 가상 함수로 변경했다. 디스어셈블한 생성

자를 살펴보자.

Page 105: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

03. C++ 클래스와 리버스 엔지니어링 65

virtual 함수 추가

class Employee

{

public :

int number;

char name[128];

long pay;

Employee();

~Employee();

void ShowData();

void virtual Test();

};

가상 함수 테이블 정보가 추가된 생성자

.text:00401094 push ebp

.text:00401095 mov ebp, esp

.text:00401097 push ecx

.text:00401098 mov [ebp+var_4], ecx

.text:0040109B mov eax, [ebp+var_4]

.text:0040109E mov dword ptr [eax], offset off_4070C0

.text:004010A4 push offset aConstructor ; "constructor!! \n"

.text:004010A9 call sub_4011FE

.text:004010AE add esp, 4

.text:004010B1 mov eax, [ebp+var_4]

.text:004010B4 mov esp, ebp

.text:004010B6 pop ebp

.text:004010B7 retn

mov dword ptr [eax], offset off_4070C0라는 코드가 추가돼 있으며, 소멸자에도 같은 코드가 만

들어져 있음을 확인할 수 있다(지면상 코드는 생략한다). 이것이 바로 가상 함수의 테이블이다. 가

상 함수는 상속 문제가 있어서 이렇게 따로 관리되고 있으며, 그 테이블은 .rdata 섹션에 위치한다.

따라서 .rdata 섹션에 보관된 함수 포인터를 발견할 때는 가상 함수라 분석할 수 있다. PE (Portable

Executable )를 아시는 분들은 .rdata니 .data니 하는 것들이 매우 익숙하겠지만, 아직 PE Header를

공부하지 않으신 분은 설명도 없이 갑자기 나오는 용어에 어리둥절할지도 모르겠다. 일단 1부에서 리

버스 엔지니어링에 대한 코드 숙지법을 살펴보고 나서 PE 부분은 2부에서 설명할 예정이니 급하신 분

들은 그쪽부터 읽어보길 바란다.

Page 106: 리버스 엔지니어링 바이블 (재출간판) : 코드 재창조의 미학

리버스 엔지니어링 바이블 66

sub_4010D9가 Employee : :Test ( ) 함수다. .rdata 섹션에 보관돼 있다.