'리버싱핵심원리'에 해당되는 글 21건
- 2016.02.11 Reverse Engineering Chap 49
- 2016.02.03 Reverse Engineering Chap 48
- 2016.02.03 Reverse Engineering Chap 47
- 2016.02.03 Reverse Engineering Chap 46
- 2016.02.03 Reverse Engineering Chap 50-51
- 2016.02.03 Reverse Engineering Chap 21-22
- 2016.01.30 Reverse Engineering Chap 45
- 2016.01.27 Reverse Engineering Chap 34
- 2016.01.27 Reverse Engineering Chap 3-6
- 2016.01.27 Reverse Engineering Chap 33
Chapter 49 IA-32 Instruction
Instruction이란 쉽게 말해서 CPU가 알아들을 수 있는 '기계어(Machine Language)'이다. IA-32 Instruction은 IA-32(Intel Architecture 32비트) 계열의 CPU에서 사용되는 Instruction을 말하는 것이다.
다음은 리버싱에서 자주 사용되는 용어이다.
C/C++(또는 어셈블리) 언어를 이용하여 PE(Portable Executable) 파일을 생성하면 소스코드는 기계어를 변환된다. 리버서는 이러한 기계어를 해석하여 동작원리를 파악하는 사람이다. 하지만 2진수로 표현된 기계어를 사람이 그냥 보는 것은 너무 힘든 작업이다. 그래서 보통 16진수로 변환해서 본다. 16진수로 보면 가독성은 좋아지지만, 아직 사람이 코드를 인식하기에는 무리가 있다. 그래서 최종적으로 (디버거에 탑재된) Disassembler를 이용하여 기계어를 디스어셈블리 코드로 변환한다.
다음은 여태 익히 보아오던 Ollydbg의 화면이다. Ollydbg에는 IA-32용 Disassembler가 탑재 되어있다.
위 그림을 보면 한줄에 하나의 명령어(Instruction)가 표시되어 있다. (A) 영역이 기계어로 표시된 IA-32 Instruction이다. 그리고 그에 해당하는 각각의 디스어셈블리 코드가 (B)에 나타나있다. 메모리(혹은 파일)에서는 실제로 (C)처럼 되어있다.
이런식으로 디버거에 탑재된 Disassembler는 (C) 영역의 Hex code를 해석하여 Instruction 단위로 쪼개서 표시(A)하고, 각 Instruction별로 디스어셈블리 코드(B)를 표시한다. 사람 눈으로 보기에는 C<A<B 순으로 보기 좋으며, 리버서들은 보통 (B) 형태의 디스어셈블리 코드를 보면서 분석을 수행한다.
Decompiler는 기본적으로 Disassembler와 같은 개념이다. 다만 Disassembler는 무조건 디스어셈블리 코드로 변환 해 주는데 반해, Decompiler는 원본 소스코드 형태로 변환 시켜주는 차이가 있다.(해당 언어제 맞는 Decompiler를 사용해야함). 물론 원본 소스코드와는 차이가 있으나 기술이 발전하면서 그 차이가 좁혀지고 있다.
위는 IDA Pro의 Hex-Rays Decompiler 플러그인을 사용하여 기계어 코드를 C언어로 해석한 것이다. 기계어 코드가 C언어 소스코드로 해석 되었음을 알 수 있다.
IA-32 Instruction 포맷은 다음과 같다.
위 그림처럼 총 6개의 항목으로 구성 되어있다. 여기서 Opcode 항목은 반드시 존재해야하며, 나머지 항목은 옵션이다. 이제 위 6가지 항목에 대하여 간단히 알아 볼 것이다.
1.Instruction Prefixes
Instruction Prefixes는 옵션 항목으로, 뒤에 특정한 Opcode가 나올 때 사용되어 Opcede의 의미를 보조하는 역할을 합니다. 간단한 예는 다음과 같다.
Prefix 항목은 1바이트 크기를 가진다.
2.Opcode
Opcode는 필수 항목이며 실제적인 명령어를 나타낸다.
Opcode는 1~3바이트 크기를 가진다. 일반적인 응용프로그램 디버깅에서 주로 1바이트 크기의 Opcode가 대부분이고 가끔 2바이트 크기의 Opcode들이 있다. 3바이트 크기의 Opcode도 있으나 접하는 경우가 거의 없다. Opcode는 보통 Operand를 갖는 경우가 많다. Operand의 종류는 Register, Memory Address, Constant 이다. 이러한 Opcode의 Operand를 결정하기 위해 보통 ModR/M과 SIB가 뒤따라오는 경우가 있다. Opcode는 그 종류가 매우 많이 때문에 일반적으로는 Intel Manual의 Opcode Map을 보면서 해석을 해야 한다.
3.ModR/M
ModR/M은 옵션 항목으로, 주로 Opcode를 도와서 Operand를 설명(Operand의 개수, 종류[Registers, Address, Contant])하는 수단으로 사용된다.
ModR/M은 1바이트 크기를 가지며 다음 그림과 같이 비트 단위로 분리되어 사용된다.
4.SIB
SIB(Scale-Index-Base)는 옵션 항목으로, ModR/M을 보조할 때 사용된다. Opcode의 Operand가 Memory Address인 경우 ModR/M과 함께 사용된다.
SIB는 1바이트 크기를 가지며 ModR/M과 같이 비트 단위로 분리되어 사용된다.
5. Displacement
Displacement는 옵션 항목으로, Opcode의 Operand가 Memory Address인 경우 Displacement(변위)를 나타낼 때 사용된다.
Displacement의 크기는 1,2,4바이트로 다양하다.
6.Immediate
Immediate는 옵션항목으로, Opcode의 Operand가 Constant인 경우에 그 Constant를 Immediate라고 말한다.
Immediate의 크기는 1,2,4바이트로 다양하다.
이제 본격적으로 바이너리 코드를 어셈블리로 해석하는 실습을 할 것이다. 실습에 앞서 바이너리 코드를 어셈블리로 해석하기 위해서는 IA-32 Instruction에 대한 정확한 이해가 필요하다. IA-32에 대한 설명은 제조사인 Intel에서 제공한 공식 매뉴얼에 자세히 설명되어있다.
http://www.intel.com/products/processor/manuals
위 링크로 접속하여 아래 두 파일을 다운 받으면 된다.
Intel@ 64 and IA-32 Architectures Software Developer’ s Manuals Volume 2A.pdf
Intel@ 64 and IA-32 Architectures Software Developer’ s Manuals Volume 2B.pdf
이제 본격적으로 IA-32 Instruction을 해석 하는 방법에 대해 학습 할 것이다.
1.Opcode Map
첫 실습 예제로 1바이트 Opcode를 골랐다.
41 INC ECX
앞에서 다운 받으라고 한 Opcode 매뉴얼(혹은 Intel Manual Vol. 2B)의 'Table A-2. One-byte Opcode Map'을 보면 된다.
Instruction을 해석 할 대 가장 먼저 확인해야 할 것이 바로 Table A-2 One-byte Opcode Map이다. 찾으려 하는 Opcode 41을 4와 1로 쪼갠 후 Opcode Map에서 각각 행과 열로 하여 찾으면 된다. 이 경우 명령어는 INC이며, Operand는 ECX로 고정된다.(REX.B는 64비트 전용이므로 무시한다.) 따라서 Instruction 41은 INC ECX 명령어를 의미한다. 이것을 위에서 설명했던 IA-32 Instruction Format으로 표시하면 다음과 같다.
2. Operand
이번에는 Operand의 형태를 파악하는 연습을 해 볼 것이다.
68 A0B44000 PUSH 0040B4A0
앞에서와 같은 방법으로 Instruction의 첫바이트 68을 Table A-2 Opcode Map에서 찾는다.
위 그림을 보면 Opcode 68은 PUSH Iz라고 표시되어 있다. 즉 Operand를 하나 가지는 PUSH 명령어이다.
Operand 타입을 나타내는 Iz의 의미를 알아야 정확한 해석이 가능하다. 대문자 I는 Addressing Method를 의미하고 소문자 z는 Operand Type을 의미한다. 이들은 각각 앞에서 만든 Opcode 매뉴얼의 A.2.1 Codes for Addressing Method과 A.2.2 Codes for Operand Type에 설명되어있다. 다음은 자주 사용되는 Addressing Method를 정리한 것이다.
Addressing Method를 의미하는 대문자 I는 Immediate이다. 상수의 크기를 알려면 Operand Type을 참고해야 한다. 다음은 자주 사용되는 Operand Type을 정리 한 것이다.
Operand Type을 의미하는 소문자 z는 32bit 모드에서 DWORD(32bit) 크기이다. 따라서 이 모든 정보를 종합하면 Opcode 68은 PUSH Iz 명령어 형식이고, Operand 형식을 나타내는 Iz 기호는 4바이트(32비트) 상수를 의미하므로 68 뒤의 4바이트(0040B440)를 더 읽어서 최종적으로 PUSH 0040B4A0으로 해석이 된다. IA-32 Instruction Format으로 표시하면 다음과 같다.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 48 (0) | 2016.02.03 |
---|---|
Reverse Engineering Chap 47 (0) | 2016.02.03 |
Reverse Engineering Chap 46 (0) | 2016.02.03 |
Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
Chapter 48 SEH
48.1. SEH
EEH는 Windows 운영체제에서 제공하는 예외 처리 시스템입니다.
48.2. SEH예제 실습
먼저 한 간단한 예제를 통해 SEH의 기본 동작 원리와 안티 디버깅에서 SEH가 어떻에 사용되는지 살펴보겠습니다.
이 예제 파일을 실행해 보고 아래 그림과 같이 메지지 박스를 나왔습니다.
이 프로그램은 정상적으로 실행된 것처럼 보이지만 사실 프로세스 내부에서 예외가 발생했습니다. 그런데 SEH를 통해 예외가 잘 처리되어서 정상 실행될 수 있습니다.
이 파일을 디버깅하고 그대로 실행하면 아래 그림과 같이 예외가 발생하면서 실행이 중지되고 status창에서 경고문이 나타납니다.
401019주소에서 EAX 레지스터의 값은 0입니다. MOV DWORD PTR DS:[EAX], 1 명령어의 의미는 메모리 주소 0에 값1을 입력하는 것입니다. 그런데 주소 0이 할당되지 않은 공간인데 마음대로 접근할 수 없어서 예외가 발생했습니다.
그래서 Shift+f9 단축 키로 계속 진행해 보고 아래의 메시지 박스가 나타납니다.
이 메시지 박스를 보면 알 수 있는데 이와 아까 그냥 실행시켰을 때 나타난 메시지 박스가 다릅니다. 그래서 두 가지 실행 방법은 예외 처리하는 방식이 서로 다릅니다.
48.3. OS의 예외 처리 방법
48.3.1. 일반 실행의 경우 예외 처리 방법
프로세스가 실행할 때 예외가 발생하면 OS는 그 프로세스 자신이 예외를 처리하게 만듭니다. 포로세스 코드안에 SEH같은 예외 처리하는 코드가 있으면 예외를 잘 처리한 후 계속 진행할 수 있습니다. 그런데 SEH가 없으면 OS는 기본 예외 처리기를 동작시키고 프로세스를 종료시킵니다.
49.3.2. 디버깅 실행의 경우 예외 처리 방법
디버깅 중에 예외가 발생하면 OS는 먼저 디버거에게 처리를 맡깁니다. 그리고 디버거 실행이 중지되고 다른 방법을 취해야 디버깅을 계속할 수 있습니다. 아래는 자주 쓰는 몇 가지 방법입니다.
1) 예외 직접 수정: 코드, 레지스터, 메모리
이 경우에는 디버거를 통해서 문제가 발생한 코드, 메모리, 레지스터 등을 직접 수정하고 정상적인 진행할 수 있습니다.
2)예외를 디버기에게 넘겨서 처리
디버기 내부에 예외를 처리할 수 있는 SEH가 있으면 예외 통지를 디버기에게 보내서 자체 해결하게 만듭니다. 이와 일반 실행의 경우 똑같습니다.
3)OS 기본 예외 처리기
디버거와 디버기는 발생한 예외를 처리할 수 없으면 OS의 기본 처리기에서 처리하고 디버깅이 중지됩니다.
48.4. 예외
그 중에 대표적인 예외 다섯 가지가 있습니다.
1) EXCEPTION_ACCESS_VIOLATION(C0000005)
존재하지 않거나 접근 권한이 없는 메모리 영역에 접근할 때 발생하는 예외입니다.
2) EXCEPTION_BREAKPOINT(80000003)
실행 코드에 break point가 설치한 후 CPU가 그 주소를 실행하려 할 때 이 예외가 발생합니다.
3) EXCEPTION_ILLEGAL_INSTRUCTION(C000001D)
CPU가 해석할 수 없는 Instruction을 만날 때 발생하는 예외입니다.
4) EXCEPTION_INT_DIVIDE_BY_ZERO
정수 나눗셈 연산에서 분모가 0이면 이 예외가 발생할 것입니다.
5) EXCEPTION_SINGLE_STEP(80000004)
Flag 레지스터의 TF 비드를 1로 세팅하면 CPU를 Single Step 모드로 변경할 것입니다. CPU가 이 모드에서 명령어 하나를 샐행 후 이 예외를 발생시켜서 실행을 멈춥니다.
48.5. SEH 상세 설명
48.5.1. SEH체인
SEH는 체인 형태로 구성되어 있습니다. 첫 번쩨 예외 처리기에서 예외를 처리하지 못하면 다음 예외 처리기로 예외를 넘거줍니다.
아래의 코드는 SEH로 구성된 구조체입니다.
Next 멤버는 다음 구조체 포인터입니다.
Handler 멤버가 예외 처리기 함수입니다.
48.5.2. 함수 정의
아래의 코드는 예외 처리기 함수 정의입니다.
4개의 파라미터를 받으며 EXCEPTION_DISPOSITION이라는 열거형을 리턴합니다.
1) EXCEPTION_RECORD
아래의 코드는 처리기 함수의 첫 번째 파라미터인 EXCEPTION_RECORD 구조체 정의입니다.
ExceptionCode 멤버는 발생한 예외의 종류를 의미합니다.
ExceptionAdress 멤버는 예외가 발생한 코드 주소를 나타냅니다.
1) CONTEXT
아래의 코드는 처리기 함수의CONTEXT 구조체 정의입니다.
CONTEXT 구조체는 CPU레지스터 값을 백업할 때 사용합니다. CPU가 다른 스레드를 실행하려면 레지스터들의
값을 현재 스레드의 CONTEXT 구조체에 백업해야 합니다. 그 후에 CPU가 다시 예전의 스레드를 실행할 때 CONTEXT구조체에 백업된 레지스터 값을 실제 CPU 레지스터에 덮어씁니다.
48.5.3. SEH설치 방버
C언어에서 __try, __except, __finally 키워드로 구현할 수 있습니다.
어셈블리 언어에서는 아래의 코드로 추가할 수 있습니다.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 49 (0) | 2016.02.11 |
---|---|
Reverse Engineering Chap 47 (0) | 2016.02.03 |
Reverse Engineering Chap 46 (0) | 2016.02.03 |
Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
chap 47. PEB
47.1. PEB
<PEB 접근 방법>
TEB에 있는 멤버 중 TEB.ProcessEnvironmentBlock 은 PEB 구조체의 주소를 나타낸다. ProcessEnvironmentBlock 은 TEB 구조체 시작으로부터 30오프셋 만큼 떨어져 있다. TED 구조체는 FS 세그먼트 셀렉터가 가리키고 있다. 따라서 다음과 같이 표현할 수 있다.
위의 공식을 어셈블리어로 표현하면 다음 두가지 방법으로 나타낼 수 있다. (두가지 방법 모두 TEB.ProcessEnvironmentBlock 멤버를 참조하고 있음)
47.2. PEB 중요 멤버 설명
PEB의 내용은 OS에 따라서 달라지고 멤버가 많기 때문에 리버싱에 있어서 중요한 멤버들만 살펴본다.
<PEB.BeingDebugged>
Kernel32!IsDebuggerPeresent()라는 API가 있다. 이 API는 현재 프로세스가 디버깅을 당하는지를 판단하여 0 또는 1을 리턴하는 함수이다. 이 함수가 참조하는 PEB의 멤버가 바로 PEB.BeingDebugged이다. (IsDebuggerPresent()는 Windows7에서는 Kernelbase.dll에 구현되어 있고, Windows XP 이하에서는 Kernel32.dll에 구현되어 있음) 이 값은 주로 리버싱 분야에서 안티 디버깅에 쓰인다. 예를 들어 이 값을 조사하여 디버깅중이라고 판단되면 프로세스를 종료시킨다.
Kernel32!IsDebuggerPresent()함수를 Ollydbg로 살펴보면 다음과 같다.
위의 두 줄을 통해 PEB의 주소를 얻고 세번째 줄에서는 +002 만큼 이동하여 PEB.BeingDebugged 멤버를 참조하는 것을 알 수 있다.
실제로 notepad.exe를 실행시켜서 첫 줄을 "MOV EAX,DWORD PTR FS:][30]"으로 바꾼 뒤 한 줄 실행시키면 EAX에 PEB 주소가 들어오며, hex dump에서 PEB를 살펴보면 +002 위치(PEB.BeingDebugged 멤버)에 1이 들어있는 것을 확인할 수 있다.
<PEB.ImageBaseAddress>
* ImageBase : PE파일이 메모리에 로딩되는 시작 주소
* GetModuleHandle() API : 모듈의 이름을 인자로 받아서 해당 모듈의 handle을 리턴하는 함수
Kernel32!GetModuleHandleA() API 내부를 살펴보면 다음과 같다.
PEB를 찾은 뒤 +008에 있는 ImageBase 멤버를 참조하는 것을 알 수 있다.
<PEB.Ldr>
PEB.Ldr 멤버는 _PEB_LDR_DATA 구조체의 포인터이다.
PEB.Ldr을 통해 프로세스에 로딩된 모듈(DLL)의 로딩 베이직 주소를 직접 구할 수 있다.
_PEB_LDR_DATA 구조체는 다음과 같다.
이 구조체에서 InLoadOrderMoudleList, InMemoryOrderModuleList, InInitializationOrderModuleList 세개의 멤버는 _LIST_ENTRY 구조체 타입이다. _LIST_ENTRY 구조체는 프로세스에 로딩된 DLL모듈마다 각각 생성성되며 양방향 연결 리스트로 연결된다. 즉, _PEB_LDR_DATA 구조체에서 알 수 있듯이 모듈들의 연결 방식을 세가지 지원한다.
_LIST_ENTRY 구조체는 다음과 같다.
_LIST_ENTRY 를 통해서 연결 리스트를 제공하고, 저장되는 정보는 _LDR_DATA_TABLE_ENTRY 구조체이다.
<PEB.ProcessHeap & PEB.NtGlobalFlag>
PEB.ProcessHeap 과 PEB.NtGlobalFlag 는 안티 디버깅에서 주로 쓰인다.
PEB.ProcessHeap은 HEAP 구조체를 가리키는 포인터이다.
HEAP 구조체는 다음과 같다.
이 중 Flags와 ForceFlags는 디버깅 중 특정한 값으로 세팅된다.
PEB.NtGlobalFlag는 0x70으로 세팅된다. (윈도우 XP 에서만 효과가 있는 기법이며, 실행중인 프로세스를 attach시킨 경우에도 이 특성이 나타나지 않음)
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 49 (0) | 2016.02.11 |
---|---|
Reverse Engineering Chap 48 (0) | 2016.02.03 |
Reverse Engineering Chap 46 (0) | 2016.02.03 |
Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
Chapter 46 TEB
TEB는 프로세스에서 실행되는 스레드에 대한 정보를 담고 있는 구조체이다. 스레드별로 TEB 구조체가 하나씩 할당된다. 또한 TEB 구조체는 OS 종류별로 그 모양이 조금씩 달라지며 세부적인 내용에 대해서는 문서화 되어있다.
다음은 TEB 구조체의 정의이다.
위는 각각 Windows XP SP3, Windows 7의 TEB 구조체이다. 구성을 보면 Windows 7 이 XP보다 구조체가 더 커진 것을 확인 할 수 있다.
위의 많은 멤버중 유저 모드 디버깅에 중요한 멤버들은 다음과 같다.
먼저 TEB 구조체의 첫번째 멤버는 _NT_TIB 구조체이다. _NT_TIB 구조체의 정의는 다음과 같다. TIB는 Thread Information Block의 줄임말이다.
가장 첫번째 멤버인 ExceptionList 멤버는 _EXCEPTION_REGISTRATION_RECORD 구조체의 연결리스트를 가리키고 있다. 이것은 SEH(Structed Exception Handler)라고 하는 Windows OS의 예외 처리 메커니즘에 사용된다.(48장 참고) Self 멤버는 _NT_TIB 구조체의 셀프포인터이다. _NT_TIB 구조체는 TEB 구조체의 첫번째 멤버이기 때문에 이는 TEB 구조체의 포인터이기도 하다.
이제 TEB 구조체에 유저모드에서 접근하는 방법에 대해서 알아볼 것이다.
Ntdll.NtCurrentTeb() API는 현재 스레드의 TEB 구조체 주소를 리턴하는 함수이다. 이 함수가 어떻게 구현 되어 있는지 OllyDbg를 통해서 확인해보면, 해당 API의 코드는 다음과 같다.
MOV EAX,DWORD PTR FS:[18]
RETN
매우 간단한 코드로, 단순이 FS:[18] 주소의 값을 리턴하는 것이다. 그렇다면 이제 FS 세그먼트 레지스터에 대해서 알아볼것이다.
FS 세그먼트 레지스터는 현재 스레드의 TEB를 지시하는데 사용된다. IA32 시스템에서 프로세스의 가상 메모리 크기는 4GB이므로 32비트 크기의 포인터를 이용해야 전체 메모리 공간이 접근이 가능하다. 그러나 FS 레지스터의 크기는 16바이트이다. FS 세그먼트 레지스터는 TEB를 지시하는데 사용되지만, 직접 TEB 주소를 가리키는 것이 아니라 실제 TEB 주소를 갖고 있는 Segment Descriptor Table의 Index 값을 갖고 있는 것이다.
GDTR 레지스터에 Segment Descriptor Table의 시작 주소가 저장 되어있고 FS 레지스터에 그 인덱스가 저장 되어있다.
따라서 FS:[0x18]은 Segment Descriptor Table에서 0x18 인덱스에 있는 주소 값에 있는 값을 의미한다. 이는 TEB에서 _NT_TIB 구조체 내의 Self 멤버를 의미하며 이는 _NT_TIB 구조체의 주소이자 TEB 구조체의 주소를 의미한다. 허나 0x18 인덱스의 Self 멤버의 값에 접근하지 않고 바로 FS:0 으로 접근하여 TEB의 주소를 구할 수도 있다.
하여 마지막으로 FS 레지스터 인덱스의 중요한 멤버들은 다음과 같다.
FS:[0x30] = TEB.ProcessEnvironmentBlock = PEB 시작 주소
FS:[0x30]은 TEB 구조체 내의 ProcessEnvironmentBlock 멤버의 값을 의미하는데 이는 PEB의 주소를 의미한다. PEB에 대해서는 다음 챕터에서 다룰 예정이다.
FS:[0] = TEB.NtTib.ExceptionList = SEH 시작 주소
위에서 언급하였듯이 FS:[0]은 _NT_TIB 구조체의 첫번째 멤버인 ExceptionList를 의미한다. 이는 SEH의 시작 주소를 의미한다. SEH는 48 챕터에서 다룰 예정이다.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 48 (0) | 2016.02.03 |
---|---|
Reverse Engineering Chap 47 (0) | 2016.02.03 |
Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
Reverse Engineering Chap 45 (0) | 2016.01.30 |
50. 안티 디버깅
디버깅 -> 프로그램의 코드 흐름과 데이터 구조를 쉽게 파악 가능
안티 디버깅을 통해 이를 방지
안티 안티 디버깅 : 리버서들이 안티 디버깅을 상대하는 방법을 일컫는다.
static
디버깅을 시작할 때 한번만 해체해주면 되는 기법
이 기법이 적용된 파일은 RUN조차 되지 않음.
static 기법을 해제해줘야 실행 가능
dynamic
만날 때마다 해결해줘야함.
RUN은 되지만 TRACING은 방해하여 원본 프로그램의 코드와 데이터를 확인할 수 없게 만든다.
run : 디버기 프로세스를 실행시키는 것
trace : 디버기의 내부 Instruction 을 하나씩 실행하면서 레지스터, 메모리(스택) 등을 실시간으로 확인하는 것
51. Static Anti Debugging
51.1. 목적
디버깅중이라고 판단되면 일반 실행과는 다른 코드를 실행
구현 방법 : 디버거 탐지 / 디버깅 환경 탐지 / 디버거 강제 분리
회피 방법 : 탐지 코드에서 얻어오는 정보를 파악하여 그 정보 자체를 변경
51.2. PEB
Process Environment Block : 프로세스 환경 정보가 담겨져 있다.
신용 가능하고, 사용또한 쉽기 대문에 가장 널리 사용되는 안티 디버깅 기법
Windows XP SP3의 PEB 구조체에서 안티 디버깅을 위해 사용되는 멤버는 아래와 같다
+0x002 BeingDebugged : UChar
+0x00c Ldr : Ptr32_PEB_LDR_DATA
+0x018 ProcessHeap : Ptr32_Void
+0x068 NtGlobalFlag : Uint4B
PEB 구하는법 : 올리디버거로 해당 디버기 불러왔을 때, EBX에 저장된 값이 PEB의 시작주소
51.2.1. BeingDebugged (+0x2)
디버깅 중이면 1, 일반 실행이면 0
IsDebuggerPresent()를 통해 이 값을 가져오고, 이로써 디버깅 여부를 판별한다.
회피방법 : 0으로 변경
51.2.2. Ldr (+0xC)
디버깅 프로세스는 힙 메모리 영역에 자신이 디버깅 당하는 프로세스라는 표시를 한다. 그 중 가장 큰 특징은 사용되지 않는 역역은 0xFEEEFEEE 로 채운다는 것이다. 이를 통해 디버깅 여부 판별 가능하다.
Ldr 주소로 가면 0xFEEEFEEE를 찾을 수 있다.
회피방법 : NULL로 덮어씌우면 된다.
XP 이하에서만 동작하고 Vista 이후부터는 동작하지 않는다.
51.2.3. Process Heap (+0x18)
heap 구조체를 가리키는 포인터이다.
heap 구조체 중 Flags(+0xC) 와 Force Flags(+0x10) 멤버는 디버깅 중 특정한 값으로 세팅된다.
일반 실행시 Flags는 0x2 이고, ForceFlags는 0x0 이다.
디버깅 중에는 이 값들이 변경된다.
회피방법 : 정상 값으로 변경.
XP에서만 효과가 있다.
51.2.4. NtGlobaIFlag (+0x68)
디버깅 중일때는 이 값이 0x70으로 세팅된다.
회피방법 : 0x0으로 세팅
51.3. NtQueryInformationProcess()
ntdll!NtQueryInformationProcess() API를 사용하여 프로세스의 정보를 가져온 후, 이 중 디버깅 관련 정보를 통해 안티 디버깅을 수행한다.
3번째 파라미터인 PVOID ProcessInformation에 해당 정보가 세팅되는데 이 정보 중, ProcessDebugPort(0x7), Process DebugObjectHandle(0x1E), ProcessDebugFlags(0x1F) 가 사용된다.
51.3.1. ProcessDebugPort(0x7)
프로세스가 디버깅 중일 때 Debug Port가 할당된다.
디버깅 중이 아니라면 0이 세팅되지만
디버깅중이라면 0xFFFFFFFF 가 세팅된다.
CheckRemoteDebuggerPresent()를 통해 이 값을 가져오고, 이로써 디버깅 여부를 판별한다.
51.3.2. ProcessDebugObjectHandle(0x1E)
프로세스가 디버깅될 때 Debug Object가 생성된다.
디버깅 중이라면 Handle 값이 존재하고, 디버깅중이 아니라면 Handle은 NULL이다.
51.3.3. ProcessDebugFlags(0x1F)
0이면 디버깅 상태이고, 1이면 디버깅이 아니다.
51.3.4. 회피방법
API 후킹을 통해 특정 파라미터일 때 무조건 정상 값을 반환하도록 조작함.
51.4. NtQuerySystemInformation()
디버깅 환경을 체크하는 안티 디버깅 기법
OS가 Debug Mode로 부팅되었는지를 판단하는 안티 디버깅 기법
ntdll!NtQuerySystemInformation() API 를 통해 현재 동작 중인 OS 시스템에 대한 다양한 정보를 구할 수 있다.
첫번째 파라미터인 SYSTEM_INFORMATION_CLASS SystemInformationClass 에 SystemKernelDebuggerlnformation(0x23) 을 입력하면 현재 OS 시스템이 디버그 모드로 부팅되었는지 알 수 있다.
디버그 모드인 경우 SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled는 1에 세팅된다.
[회피]
xp의 경우, boot.ini를 편집하고 ‘/debugport=com1 /bandrate=115200 /Debug’ 값을 제거한다.
7의 경우, cmd 창에서 ‘bcdedit /debug off’ 명령을 내리고 재부팅하면 일반모드로 부팅된다.
51.5. NtQueryObject()
프로세스가 디버깅될 때 Debug Object가 생성되는데 이 존재를 확인하는 방법이다.
ntdll!NtQueryObject()는 시스템의 다양한 종류의 커널 객체 정보를 구해오는 함수이다.
두번째 파라미터에 원하는 값을 입력하고 API를 호출하면 세번째 파라미터에 관련 정보의 구조체 포인터를 리턴한다.
ObjectAllTypesInformaion 항목을 이용하여 시스템의 모든 객체 정보를 구한 다음 그 중에 DebugObject가 있는지 확인한다.
회피방법 : 이 함수가 호출될 때 breakpoint를 걸어 두번째 파라미터에 3(ObjectAllTypesInformaion)이 아닌 0(ObjecBasicTypesInformaion)이 들어가게 한다.
51.6. ZwSetInformationThread()
디버기에서 강제로 디버거를 떼어내는 기법이다.
이 함수는 스레드에게 정보를 세팅하는 System Native API이다.
첫번째 파라미터에 현재 스레드의 핸들을 넘겨주고
두번째 파라미터에 ThreadHideFromDebugger(Ox11) 값을 입력하면 디버거 프로세스가 분리된다.
회피방법 : breakpoint 를 걸어 두번째 파라미터에 0x11이 들어가 있다면 0으로 변경한다.
51.7. TLS
45장 참고
51.8. ETC
- 디버거 창 검색
- 디버거 프로세스 검색
- 컴퓨터 이름 확인 (TEST, ANALYSIS)
- 프로그램 실행 경로 검사 (TEST, SAMPLE)
- 가상 머신 실행 중인지 확인
회피방법 : 반환되는 문자열을 NULL로 세팅
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 47 (0) | 2016.02.03 |
---|---|
Reverse Engineering Chap 46 (0) | 2016.02.03 |
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
Reverse Engineering Chap 45 (0) | 2016.01.30 |
Reverse Engineering Chap 34 (0) | 2016.01.27 |
21. Windows Message Hooking
21.1. Hook
Hook(갈고리)는 원하는 것을 낚아채고 싶을 때 사용하는 도구이다. 컴퓨터 분야에서는 정보를 엿보거나 가로채는 경우에 사용되는 단어로 확장되어 사용된다. Hook을 하는 행위를 ‘후킹한다(Hooking)’라고 표현하는데 이는 중간에서 오고가는 정보를 엿보거나 가로채기 위한 일을 하거나 정보를 조작하는 행위를 의미한다. 실제로 ‘OS-Application-User’ 사이의 정보를 모두 조작 가능하다. 가장 기본적인 방식의 후킹은 Message Hook이다.
21.2. Message Hook
Windows 운영체제는 GUI를 제공하고, 이는 Event Driven1 방식으로 동작한다. 이벤트가 발생할 때 OS는 미리 정의된 메시지를 해당 응용프로그램으로 보낸다.
일반적인 경우의 Windows 메시지 흐름
Hook Chain의 키보드 메시지 훅들은 해당 메시지 열람 및 변조가 가능하다. Hook Chain은 동시에 여러개 설치가 가능하며, 설치 순서대로 호출된다. 이러한 특성때문에 Hook Chain이라고 불린다.
21.3. SetWindowsHookEx()
메시지 훅은 SetWindowsHookEx() 함수를 통해 간단히 구현할 수 있다.
//SetWindowsHookEx() API
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure : 운영체제가 호출해주는 콜백함수
HINSTANCE hMod , // 위 hook procedure가 속해있는 DLL 핸들
DWORD dwThreadld // Hook을 걸고 싶은 thread의 ID. 0을 주고 호출하면 글로벌 훅
);
21.4. 키보드 메시지 후킹 실습
- HookMain.exe는 KeyHook.dll을 최초로 로딩하여 키보드 훅을 설치하는 프로그램
- KeyHook.dll은 훅 프로시저가 존재하는 DLL 파일
- SetWindowssHookEx()를 이용하여 키보드 훅 설치
- 다른 프로세스에서 키 입력 이벤트가 발생하면 OS 에서 해당 프로세스의 메모리 공간에 KeyHook.dll을 강제로 로딩
- KeyboardProc() 함수가 호출됨
21.4.1. 실습 예제
21.4.2. 소스코드 분석
HookMain.cpp
#include "stdio.h"
#include "conio.h"
#include "windows.h"
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
typedef void(*PFN_HOOKSTART)();
typedef void(*PFN_HOOKSTOP)();
void main()
{
HMODULE hDll - NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;
// KeyHook.dll 로딩
hDll = LoadLibraryA(DEF_DLL_NAME);
//export 함수 주소 얻기
HookStart =(PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop =(PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
// 후킹 시작
HookStart();
// 사용자가 'q'를 입력할 때까지 대기
printf("press 'q' to quit\n");
while( _getch() != 'q' );
// 후킹 종료
HookStop();
// KeyHook.dll 언로딩
FreeLibrary(hDll);
}
KeyHook.cpp
#include "stdio.h"
#include "windows.h"
#define DEF_PROCESS_NAME "notepad.exe"
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = {0,};
char *p = NULL;
if(nCode = 0)
{
// bit 31: 0 = key press, 1 = key release
if( !(lParam & 0x80000000) ) // 키보드가 눌렀다 떨어질 때
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
// 현재 프로세스 이름을 비교해서 만약 notepad.exe. 라면,
// 메시지는 응용 프로그램(혹은 다음 훅)으로 전달되지 않음
if( !_stricmp(p+1, DEP_PROCESS_NAME)
return 1;
}
}
// 일반적인 경우에는 CallNextHookEx()를 호출하여
// 응용 프로그램(혹은 다음 훅)으로 메시지를 전달함
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if(g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
21.5. Debugging
21.5.1. ‘HookMain.exe’ 디버깅
- “press ‘q’ to quit!” 문자열을 참조한 코드를 찾음 으로써 main 함수 찾는다.
- main 함수에 breakpoint를 걸어 HookStart()가 실행될 때 까지 디버깅
- HookStart() 해당 주소의 CALL EAX 를 따라가서 KeyHook.dll의 함수임을 확인
- HookStart에서 사용되는 SetWindowsHookExW() 함수 및 파라미터 확인
21.5.2. Notepad.exe 프로세스 내의 KeyHook.dll 디버깅
- OllyDbg의 옵션(‘Break on new module(DLL)’을 Check 상태로 변경함으로써 새로운 DLL이 로딩될 때마다 자동으로 디버깅을 멈출 수 있다.
- 이 상태에서 HookMain.exe를 실행하고 notepad.exe 에서 키보드를 입력하면 OllyDbg가 멈추면서 ‘Executable modules’ 창이 뜬다.
- 여기서 KeyHook.dll이 로딩된 것을 확인할 수 있다.
22. 악의적인 목적으로 사용되는 키로거
관리 차원에서 (사용자 동의하에) 사용되는 키로거는 나쁘다고 할 수 없을 것이다. 하지만 악의적인 목적으로 사용자 몰래 실행되는 키로거는 문제가 된다.
22.1. 악성 키로거의 목표 및 사례
- 목표 : 돈
- 사례
- 온라인 게임
- 인터넷 뱅킹
- 기업 정보 유출
22.2. 키로거의 종류와 향후 발전 방향
키보드 끝에 연결되어 키보드의 입력이 PC로 전달되는 과정에서 가로챈다. 내부에 flash memory를 내장하고 있어 키보드로부터 들어오는 전기 신호를 직접 입력받아 저장하는 장치이다.
22.3. 키로거에 대처하는 우리의 자세
- 공공장소에서는 개인정보 입력 X
- 보안 프로그램 업데이트
- 개인정보 복붙으로 입력
- 개인 방화벽 사용
22.4. 개인정보
비밀번호 어렵게 하자
- 이벤트에 반응하여 동작을 변경하는 방식 ↩
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 46 (0) | 2016.02.03 |
---|---|
Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
Reverse Engineering Chap 45 (0) | 2016.01.30 |
Reverse Engineering Chap 34 (0) | 2016.01.27 |
Reverse Engineering Chap 3-6 (0) | 2016.01.27 |
TLS 콜백은 프로세스에서 스레드가 생성되기 전, 스레드가 종료된 후 자동으로 실행되는 함수 들이다 . 메인 함수가 실행되는 스레드도 예외가 아니기 때문에 메인함수 시작 전에도 TLS 콜백이 실행된다. 메인의 엔트리 포인트 이전에 실행되기 때문에 안티 디버깅을 위한 용도로 쓰이기도 한다.
TLS TLS 콜백을 등록하기 위해서는 아래와 같은 형태의 함수를 사용해야한다.이 형태는 DLL에서 쓰이는 것과 같다.
예제 코드
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK TLS_CALLBACk[] = { tlsCallback, 0 };
#pragma data_seg()
위의 코드가 TLS를 사용하기 위해 필요하다. .CRT$XLX 영역에 PIMAGE_TLS_CALLBACK 변수 또는 배열을 이용하여 TLS 콜백 함수들을 등록한다.
위의 코드를 실행하면 아래와 같은 결과를 볼 수 있다.
메인 함수가 실행되기 전 TLS 콜백이 실행되어 문자열 TLS가 출력된다.
스레드 실행 전 후로 TLS 콜백이 실행되는 것을 확인할 수 있다.
디버깅을 해보면 메인 함수 종료 후에도 TLS 콜백이 실행되나 printf 함수가 제대로 동작하지 않아 실제로 출력은 되지 않는다.
PE 내부 TLS의 위치와 구조
TLS를 사용하면 IMAGE_NT_HEADER.IMAGE_OPTIONAL_HEADER.IMAGE_DATA_DIRECTORY[9]에 TLS 테이블의 크기와 RVA가 기록된다.
PEview로 확인하면 TLS table에 대한 정보가 있는 것을 확인할 수 있다.
RVA 0x176EC에 해당하는 RAW인 0x68EC에는 IMAGE_TLS_DIRECTORY가 있다.
IMAGE_TLS_DIRECTORY 구조체 중 Address of Callbacks에 TLS 콜백 함수들의 주소(VA)가 저장되어있다.
00416720에 해당하는 곳은 아래 그림에서 볼 수 있다.
Address of Callbacks에 해당하는 곳인 0x00416720의 모습이다. 콜백 함수들의 주소가 저장되고 마지막은 NULL로 끝난다. 우리가 정의한 함수의 주소인 0x004110EB는 아래의 그림에 있다.
0x004110EB의 값을 보면 E9로 시작한다. E9 XXXXXXXX는 점프문에 해당하기 때문에 점프 주소를 쉽게 확인하기 위해 올리 디버거로 확인해보았다.
올리 디버거로 확인해보면 VA 0x004110EB에는 JMP 007316E0 명령어가 있고, 이 점프문을 따라가면 우리가 정의했던 TLS 콜백 함수가 나타난다.
문자열 TLS를 스택에 넣고 출력하는 것으로 보아 우리가 정의했던 TLS 콜백 함수임을 알 수 있다.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
---|---|
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
Reverse Engineering Chap 34 (0) | 2016.01.27 |
Reverse Engineering Chap 3-6 (0) | 2016.01.27 |
Reverse Engineering Chap 33 (0) | 2016.01.27 |
Chap 34. 고급 글로벌 API 후킹 - IE 접속 제어
이번 장의 실습 목표는 IE 프로세스의 API를 후킹하여 특정 사이트로 향하는 접속을 다른 사이트로 우회시키는 것이다.
34.1. IE 프로세스 구조
- IE 7부터는 탭 개념을 도입하면서 프로세스 구조가 부모-자식 관계임
34.2. 글로벌 API 후킹 개념 정리
- 일반적인 API 후킹은 Test.exe 프로세스에 인젝션하여 후킹하는 방법 --> 후킹 대상 프로세스가 새로 생성될 때마다 수동으로 API 후킹을 해야 함
- 글로벌 API 후킹은 Windows OS의 기본 Shell인 Explorer.exe 프로세스에 인젝션하여 자식 프로세스 생성에 관련된 API를 후킹하여 자식 프로세스를 생성할 때마다 인젝션 하도록 하는 것임
34.3. ntdll!ZwResumeThread() API
- 글로벌 API 후킹을 하기 위해서는 자식 프로세스를 생성하는 API에 대해 알아야 함
- 프로세스를 생성하는 대표적인 API는 Kernel32!CreateProcess() 임
- CreateProcessW() API의 호출 흐름은 다음과 같음
- 위의 4개의 API 중 어떤 것을 후킹해도 글로벌 API 후킹이 가능하지만 이번 실습에서는 ntdll!ZwResumeThread() API 를 후킹함
34.4. redirect.cpp
- DllMain() 함수에서는 ntdll!ZwResumeThread() 와 wininet!InternetConnectW() API 를 훅/언훅 하는 기능을 함
- NewInternetConnectW() 함수에서는 IE 의 접속 주소를 모니터링 하면서 특정 사이트에 접속할 때 원하는 사이트로 접속을 돌리는 역할을 함
- NewZwResumeThread() 함수에서는 자식 프로세스에 redirect.dll을 인젝션해주는 역할을 함
34.5. 실습 예제 - IE 접속 제어
- InjDll.exe 프로그램은 책 저자가 만든 프로그램(target 프로세스가 32bit인지 64bit인지에 따라 선택해서 사용하면 됨) --> 타깃 프로세스에 원하는 DLL을 인젝션/이젝션하는 기능을 함
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
---|---|
Reverse Engineering Chap 45 (0) | 2016.01.30 |
Reverse Engineering Chap 3-6 (0) | 2016.01.27 |
Reverse Engineering Chap 33 (0) | 2016.01.27 |
Reverse Engineering Chap 32 (0) | 2016.01.24 |
Chapter 3 리틀 엔디언 표기법
3.1 바이트 오더링
- 컴퓨터 메모리에 저장하는 순서입니다.
-빅 엔디언과 리틀 엔디언
아래 코드는 4가지 자료형이 있습니다. 다른 엔디언 방식에 따라서 같은 데이터는 저장되는 방식이 다릅니다.
바이트 타입의 변수를 저장할 때는 두 방식의 차이가 없습니다. 하지만 2바이트 이상의 크기를 가진 자료형을 저장할 때부터 차이가 나타납니다. 빅 엔디언 방식은 정상적인 순서로 데이터를 저장하고 리틀 엔디언 방식은 역순으로 데이터를 저장합니다. 또한 문자열은 저장 방식에 상관없이 동일합니다.
예제 소스
이 코드를 빌드하고 생성한 파일을 디버깅합니다.
Main()함수의 주소는 401000입니다. 변수들의 주소는 40AC40, 40AC44, 40AC48, 40AC4C입니다.
데이터 창을 통해 이 메모리 영역으로 갑니다. 변수 w와 dw의 값이 리틀 엔디언 형식으로 저장된 것을 알 수 있습니다.
Chapter 4 IA_32 Register 기본 설명
4.1 IA-32의 Register
아래의 그림은 Basic program execution registers에 대해 나와 있습니다. 4가지의 그룹으로 나눌 수 있습니다.
- General Purpose Registers (32 비트 - 8개)
- Segment Registers (16 비트 - 6개)
- program Status and Control Register (32 비트 - 1개 )
- Instruction Pointer (32비트 - 1개)
4.1.1 General Purpose Registers(범용 레지스터)
General Purpose Registers는 범용적으로 사용되는 레지스터입니다. 데이터를 전송하고 임시 보존할 수 있습니다. 그리고 산술 연산을 할 수 있고 그 연산한 결과를 저장할 수 있습니다. IA-32에서 각각의 범용 레지스터의 크기는 32비트이고 주로 상수와 주소 등을 저장합니다.
위 4개의 레지스터는 주로 산술연산을 하는 것이고 상수와 변수 값을 저장할 때 사용됩니다. 그리고 ECX와 EAX는 특수한 용도가 있습니다. ECX는 반복문 병령어(loop)에서 반복 카운트(loop count)로 사용됩니다. EAX는 일반적으로 함수 리턴 값에 사용됩니다.
위 4개의 레지스터는 주로 메모리 주소를 저장하는 포인터로 사용됩니다. EPS는 스택 메모리 주소를 가리키고 EBP는 함수가 호출되었을 때의 ESP를 저장하고 함수가 리턴하기 전에 다시 ESP에 값을 되돌려 줍니다. 그리고 ESP와 EDI는 어떤 특정어들(LODS, STOS, REP MOVS 등)과 함께 사용되고 주로 메모리 복사에 사용됩니다.
4.1.2 Segment Register
IA-32 보호 모드에서 세그먼트는 메모리를 조각내서 각 조각마다 시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법입니다.
4.1.3 Program Status and Control Register(프로그램 상태와 컨트롤 레지스터)
이flag 레지스터는 32개의 비트가 있는데 각 비트를 전부 이해하는 것은 어려워서 먼저 애플리케이션 다버깅에 필요한 3가지flag에 대해서 잘 이해하면 됩니다.
- Zero Flag(ZF): 연산 명령 후에 결과 값이 0이 되면 ZF가 true로 세팅됩니디.
- Overflow Flag(OF): 부호 있는 수의 오버플로가 발생했을 때 true로 세팅됩니다. 그리고 MSB(Most significant Bit)가 변경되었을 때 true로 세팅됩니다.
- Carry Flag(CF): 부호 없는 수의 오버플로가 발생했을 때 true로 세팅됩니다.
4.1.4 Instruction Pointer(EIP)
EIP는 CPU가 처리할 명령어의 주소를 나타내는 레지스터입니다. CPU는 EPI에 저장된 메모리 주소의 명영어를 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킵니다.
4.1.4 Instruction Pointer(EIP)
EIP는 CPU가 처리할 명령어의 주소를 나타내는 레지스터입니다. CPU는 EPI에 저장된 메모리 주소의 명영어를 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킵니다.
Chapter 5 스택
5.1. 스택
스택은 다양한 용도가 있는데 주로 함수 내의 로컬 변수를 임시 저장하고 함수 호출 시 파라미터를 전달하고 복귀 주소를 저장할 때 자주 사용합니다. 그리고 스택은First in Last Out 구조가 있습니다. 프로세스에서 스택 포인터는 처음에 제일 아래에 있습니다. Push 명령을 통해서 Stack에 값이 추가되면 스택 포인터는 위에 움직입니다. 그리고 pop명령을 통해서 값이 제거되면 스택 포인트는 아래에 움직입니다.
5.2 스택 동작예제
아래의 그림은 스택 초기 상태이며 스택 포인터(ESP)의 값은 12FF8C입니다.
PUSH명령을 실행한 후 ESP의 값은 12FF88로 바구고 4바이트 줄어들었습니다.
POP명령을 실행한 후 ESP의 값은 다시 12FF88로 바구고 4바이트 증가했습니다.
이 예제를 통해 스택 포인터의 변화를 더 잘 이해할 수 있습니다.
Chapter 6 abex’ crackme #1 분석
먼저 파일을 실행합니다.
[확인]을 눌러서 ‘Error’ 메시지 박스가 나온다
이 파일을 디버깅하고 이런 코드를 볼 있습니다.
401026주소의 JE 명령어를 JMP명령어로 부꾸면 됩니다.
[확인]을 누르면 성공 메시지 박스 나올 수 있습니다.
.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 45 (0) | 2016.01.30 |
---|---|
Reverse Engineering Chap 34 (0) | 2016.01.27 |
Reverse Engineering Chap 33 (0) | 2016.01.27 |
Reverse Engineering Chap 32 (0) | 2016.01.24 |
Reverse Engineering Chap 28 (0) | 2016.01.24 |
Charpter 33 스텔스 프로세스
이번 챕터에서는 API 코드 패치를 통한 API 후킹 방법에 대해서 공부한다. 또한 모든 프로세스를 후킹하는 글로벌 후킹에 대해 공부하고, 이것을 사용하여 특정 프로세스를 감추는 스텔스 기법에 대해 알아볼것이다.
IAT 후킹 방식이 프로세스의 특정 IAT 값을 조작해서 후킹을 하는 방식이라면, 코드 패치 방식은 실제 API 코드 시작 5바이트 값을 JMP XXXXXXXX 명령어로 패치하는 방식이다.
다음은 후킹 되기 전의 정상적인 프로세스 메모리의 모습이다.
procexp.exe 코드에서 ntdll.ZwQuerySystemInformation() API를 호출하면 코드의 흐름은 다음과 같다.
1. procexp.exe의 00422CF7 주소의 명령어는 ZwQuerySystemInformation API를 호출하고 있다.
2. 해당 API 는 실행이 완료되면, 호출코드 다음 명령어 주소로 되돌아 간다.
다음은 API가 후킹된 프로세스 메모리의 모습니다. stealth.dll을 인젝션하여 ntdll.ZwQuerySystemInformation API를 코드 패치하였다.
위 그림의 호출 흐름은 다음과 같다.
1. 422CF7 주소에서 ntdll.ZwQuerySystemInformation() API를 호출(7C93D92E)
2. 7C93D92E 주소에 있는 패치된 코드 JMP 10001120에 의해서 10001120주소로 점프한다. 1000116A 주소의 CALL unhook() 명령어에 의해서 ntdll.ZwQuerySystemInformation API 시작 5바이트는 원래대로 복원된다.
3. 1000119B 주소의 CALL EAX(7C93D92E) 명령어에 의해서 원본 함수(ntdll.ZwQuerySystemInformation)가 호출된다.(언훅 상태이기 때문에 정상적으로 실행됨.
4.ntdll.ZwQuerySystemInformation()의 실행이 완료되면 7C93D93A 주소의 RETN 10 명령에 의해 stealth.dll 코드 영역으로 리턴된다. 그리고 10001212 주소의 CALL hook() 명령어에 의해서 ntdll.ZwQuerySystemInformation API를 다시 후킹한다.(시작 5바이트를 JMP로 패치)
5. stealth.MyZwQuerySystemInformation의 실행이 완료되면, 10001233주소의 RETN 10 명령에 의해서 procexp.exe 프로세스 코드 위치로 리턴된다.
이 챕터에서 설명하는 프로세스 은폐의 동작 원리는 '프로세스를 검색하는 API를 변조시키는 것'이다. 유저모드에서 프로세스를 검색하기 위한 API는 2종류가 있는데, CreateToolhelp32Snapshot과 EnumProcesses API 이다. 이 2가지 API들은 모두 내부적으로 ntdll.ZwQuerySystemInformation API를 호출하기 때문에, 위 2가지 API를 수정 할 것이 아니라 ZwQuerySystemInformation API만 수정 한다면 유저모드에서 프로세스 검색을 우회할 수 있다.
글로벌 API 후킹이란 현재 실행 중인 모든 프로세스와 앞으로 실행될 모든 프로세스에 대해서 API 후킹을 하는 것이다. 새로운 프로세스가 생성되려면 kernel32.CreateProcess API를 사용해야 한다. 프로세스를 실행시키는 다른 API들도 결국 내부적으로는 CreateProcess API를 호출한다. 따라서 현재 실행중인 모든 프로세스에 stealth.dll을 인젝션하고, stealth.dll에서 CreateProcess() API까지 같이 후킹하면 이후 실행되는 프로세스에게도 자동으로 stealth.dll을 인젝션 하도록 만들 수 있다.
모든 프로세스는 부모 프로세스(보통 explorer.exe)에서 CreateProcess를 이용하여 생성해주기 때문에 부모 프로세스의 CreateProcess API를 후킹하여 자식 프로세스에게 stealth.dll을 인젝션하도록 만들면 된다.
CreateProcess API를 후킹할 때는 kernel32.CreateProcessA(), kernel32.CreateProcessW() 두개의 API를 각각 후킹해야 한다.(ASCII 버전과 유니코드 버전)
허나 CreateProcess API를 후킹하는 것은 안정적이지 못하다. API를 후킹할때는 최대한 Low level의 API를 후킹하는 것이 좋다. 하여 Ntdll.ZwResumeThread() API를 후킹할 것이다. 글로벌 후킹을 하기 위해 이 API를 후킹하는 것이 현재 상황에서 가장 적합하다. 허나 ZwResumeThread API는 undocumented API라서 언제 바뀔지 알수가 없으므로, 안정성을 보장할 수 없다. 그렇기에 본 실습의 소스코드에서는 CreateProcessA, CreateProcessW API를 각각 후킹한다.
이제 소스코드 분석을 하게 될것이다.
위 함수는 HideProc.cpp 파일의 InjectAllProcess 함수이다. 소스코드를 보면 실행중인 모든 프로세스를 검색하여 각각 DLL 인젝션/이젝션을 수행한다. PID 100이하의 프로세스는 시스템 프로세스가 다수 있기 때문에 시스템의 안정성을 위해 DLL 인젝션을 하지 않는다.
위 소스코드는 stealth.dll 파일의 dllmain 함수이다. 소스코드를 보면 프로세스에 DLL 인젝션이 되었을때, CreateProcessA, CreateProcessW, ZwQuerySystemInformation API를 각각 후킹을 한다. 그리고 DLL 이젝션이 될때 각 API를 언후킹 해준다.
위 함수는 ZwQuerySystemInformation 함수를 후킹하여 사용하게 될 함수이다. STR_HIDE_PROCESS_NAME 변수는 숨기고자 하는 프로세스의 이름이 들어있는 변수이다. 그래서 소스코드를 읽어보면 모든 프로세스에 대해서 숨길 프로세스의 이름과 같은지 비교하고 이름이 같다면 ZwQuerySystemInformation 함수의 결과에서 숨길 프로세스의 내용을 지운다. 위 함수의 기본적인 순서는
unhook -> ZwQuerySystemInformation 실행 -> 다시 hook
위 순서를 따른다.
CreateProcess API의 후킹 함수의 경우 전체적인 unhook->정상 코드 실행-> 다시 hook의 뼈대는 같고 프로세스를 실행할때 dll을 인젝션 해준다.
7바이트 코드패치
다음은 일반적인 API의 시작 코드부분이다.
API의 최상위 부분을 보면 MOV EDI,EDI가 있는 것을 알 수 잇다. 이는 실행을 해도 아무의미가 없는 명령이다. 7바이트 코드 패치는 MOV EDI, EDI 명령과 이 명령위의 5개의 NOP 명령 총 7바이트를 패치하여 후킹을 시도하는 방식이다.
위 7바이트를 패치할 경우 위의 5바이트 패치와는 다르게 코드를 실행할때 API의 주소+2의 위치를 호출하면 정상적인 API 코드를 실행할 수 있으므로 unhook, hook의 과정을 거칠 필요가 없다.
대부분의 API는 함수 최상위 부분이 5개의 NOP과 MOV EDI,EDI로 구성 되어 있으나 그렇지 않은 API의 경우 7바이트 코드 패치를 적용시킬수 없다. 그렇기에 이러한 경우에는 5바이트 코드패치를 하는 수밖에 없다.
'리버싱핵심원리' 카테고리의 다른 글
Reverse Engineering Chap 34 (0) | 2016.01.27 |
---|---|
Reverse Engineering Chap 3-6 (0) | 2016.01.27 |
Reverse Engineering Chap 32 (0) | 2016.01.24 |
Reverse Engineering Chap 28 (0) | 2016.01.24 |
Reverse Engineering Chap 25~26 (0) | 2016.01.20 |