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 |
32장은 실행중인 프로세스의 IAT를 변경하여 API를 후킹하는 방식을 설명한다.
DLL의 함수들은 함수의 간접적으로 호출된다. 위 그림에서는 user32.dll의 SetWindowTextW 함수를 호출하는 장면이다. CALL DWORD PTR DS:[01001110]을 실행하고, 주소 01001110에는 user32.dll 내부의 SetWindowTextW 함수의 주소인 7E42960E가 저장되어있다.
프로세스 실행중 동적으로 IAT를 변경하여 간단하게 함수를 후킹할 수 있다. 후킹하려는 함수를 가리키는 곳(위에서는 01001110)에 저장된 함수의 주소를 우리가 새로 만든 DLL의 후킹용 함수로 변경하면 된다.
예제 소스
이 DLL은 계산기의 결과창에 출력되는 숫자를 변경한다. 변경된 숫자는 원본 숫자의 모든 자리의 수에 1을 더한 값이다.(1->2, ... 8->9, 9->0) 숫자를 출력하는 함수가 SetWindowTextW이기 때문에 이 함수를 후킹하는 것이 목적이다.
위의 예제는 3개의 함수로 구성되어있다.
main 함수는 user32.dll의 SetWindowTextW 함수의 주소를 가져오고, IAT를 변경하는 hook_iat 함수를 호출한다.
MySetWindowTextW 함수는 후킹되는 함수인 SetWindowTextW 함수 대신 실행되고, 자신의 코드를 실행한 후 SetWindowTextW 함수를 실행한다. 이 함수는 인자로 전달된 숫자의 모든 자리에 1을 더한다.
hook_iat 함수가 API 호출시 참조하는 곳을 변경한다. 이 함수가 IAT 후킹을 하기 위해 필요한 함수이다.
hook_iat 함수의 작동 방식은 아래와 같다.
프로세스에서 PIMAGE_IMPORT_DESCRIPTOR의 위치를 찾는다.
그중에서 user32.dll에 해당하는 것을 찾는다.
PIMAGE_THUNK_DATA를 찾는다.
PIMAGE_THUNK_DATA에서 SetWindowTextW의 주소가 저장된 곳을 찾는다.
그곳의 값을 새로운 함수인 MySetWindowTextW의 주소로 변경한다.
GetModuleHandle 함수로프로세스의 핸들을 가져온다. 인자로 NULL을 전달하면 함수를 실행한 프로세스의 핸들을 가져온다. 따라서 DLL이 인젝션된 후 실행되면 대상 프로세스의 핸들을 가져오게 된다.
46번 줄에서 pAddr은 프로세스의 ImageBase를 가진다.
48번 줄에서 60(0x3C)바이트 를 더해서 pAddr은 NT 헤더를 가리킨다.
50번 줄에서 dwRVA는 IMPORT TABLE의 위치(RVA)를 가리킨다.
52번 줄에서 최종적으로 pImportDesc가 IMPORT TABLE을 가리킨다.
2. user32.dll을 찾는다.
테이블의 각 원소를 hook_iat의 인자로 받은 user32.dll(szDllName)와 비교하여 user32.dll의 IID를 찾는다.
3. PIMAGE_THUNK_DATA를 찾는다.
59번 줄의 연산으로 인해 pThunk에 user32.dll의 IAT가 저장된다.
4. SetWindowTextW의 주소가 저장된 곳을 찾는다.
pfnOrg는 hook_iat의 인자로서 후킹하려는 함수의 주소가 저장되어있다. 이경우 SetWindowTextW가 저장되어있다.
5. 후킹하려는 함수의 주소를 새로운 함수의 주소로 변경한다.
71번 줄에서 IAT를 변경한다. 함수의 주소를 새로운 함수의 주소로 변경한다. 이를 위해서는 66번 줄처럼 VirtualProtect 함수를 사용하여 수정하려는 곳의 속성을 쓰기 가능하도록 변경한다. 변경 이후에는 73번 줄처럼 원래대로 복구한다.
DLL 인젝션을 실행하면 아래와 같은 결과를 볼 수 있다.
'리버싱핵심원리' 카테고리의 다른 글
| Reverse Engineering Chap 3-6 (0) | 2016.01.27 |
|---|---|
| Reverse Engineering Chap 33 (0) | 2016.01.27 |
| Reverse Engineering Chap 28 (0) | 2016.01.24 |
| Reverse Engineering Chap 25~26 (0) | 2016.01.20 |
| Reverse Engineering Chap 24 (0) | 2016.01.20 |