이 포스팅은 서적 '윈도우 시스템 해킹 가이드 : 버그헌팅과 익스플로잇'을 기반으로 작성한 포스팅입니다.
이전 포스팅
범용 쉘코드(Universal Shellcode) (1) 보러가기
이전 포스팅에서는 범용 쉘코드의 필요성에 대하여 포스팅하였다.
그렇다면 ASLR기술이 적용된 시스템에서는 어떠한 과정을 통해
실질적으로 우리가 필요로 하는 함수를 구할 수 있을지 학습하여 보겠다.
프로세스에서 함수의 주소값을 구하기 위해서는 함수가 저장되어져있는 dll의 시작 주소값과 dll 시작 주소부터 함수까지의 offset을 알아야한다.
① dll의 시작 주소값 구하기
대체적인 주소값 계산의 흐름은 위와 같다.
FS레지스터에는 TEB의 주소가 저장되어 있다.
TEB란 현재 실행되고 있는 쓰레드에 대한 정보를 담고 있는 구조체이다.
FS레지스터 저장된 TEB+0x30에는
PEB가 저장되어 있는데,
PEB란 실행 중인 프로세스에 대한 정보를 담아두는 구조체이다.
이는 프로세스와 관련된 다양한 정보들이 저장되어 있다.
해당 정보 중에는 프로세스에 로드된 PE Image(EXE, DLL등)들에 대한 정보들도 기록되어 있는데,
우리는 이정보를 이용하여 dll의 시작 주소값을 구할 수 있다.
PEB + 0xC에는
_PEB_LDR_DATA의 주소가 저장되어 있다.
이를 따라간 후
다시한번 더
_PEB_LDR_DATA +0x14를 따라가게 되면
InMemoryOrderModuleList가 있고
여기엔 프로세스의 PE Image들의 데이터가 저장된 LDR_DATA_TABLE_ENTRY 구조체의 더블링크드 리스트의 시작 주소가 저장되어있다.
첫번째 LDR_DATA_TABLE_ENTRY 구조체는 실행파일 그 자체에 대한 정보가 들어 있고,
두번째 LDR_DATA_TABLE_ENTRY 구조체는 첫 번째 로드된 라이브러리 ntdll.dll의 파일 정보가 들어있다.
마지막으로 한번 더 따라가게되면
세번째 LDR_DATA_TABLE_ENTRY 구조체는 두 번째 로드된 라이브러리 kernel32.dll의 파일 정보가 들어있다.
해당 구조체(세번쨰 LDR_DATA_TABLE_ENTRY 구조체)에서 +0x18에 dll이 로드된 dllBase
즉 우리가 찾고자 하는 kernel32.dll의 시작 주소값이 저장되어 있다.
이렇게 말로해서는 여러번의 과정을 거쳐 난해하니 실습을 통해 직접 kernel32.dll의 시작 주소값을 확인해보자.
실습
WinDbg를 이용하여 iexplore.exe를 열어준다.
먼저 TEB의 주소를 구해보자.
>!teb
TEB구조체를 확인하여 PEB 구조체의 주소값을 구해보자
>dt _TEB 0x7ffdf000
PEB의 주소값은 0x7ffd8000으로 확인되었다.
다음으로 PEB구조체를 확인하여 PEB_LDR_DATA구조체의 주소값을 확인해보자.
>dt _PEB 0x7ffd8000
PEB_LDR_DATA의 주소값은 0x774c9880으로 확인되었다.
다음으로 PEB_LDR_DATA구조체를 확인해보자.
>dt PEB_LDR_DATA 0x774c9880
확인 결과 0x221cb0
모둘듈의 _LDR_DATA_TABLE_ENTRY 구조체의 위치가 더블링크드리스트 형태로 저장되어 있으며
첫번쨰 _LDR_DATA_TABLE_ENTRY를 확인해보자.
단
InMemoryOrderLinks는 _LDR_DATA_TABLE_ENTRY가 아닌 InMemoryOrderLinks를 서로 가리키고 있기에
주소값을 확인하기 위해서는 해당 값에서 8을 빼주어야 한다.
>dt _LDR_DATA_TABLE_ENTRY 0x221cb0-8
예상과 동일하게
첫번째 LDR_DATA_TABLE_ENTRY 구조체는 실행파일 그 자체에 대한 정보가 들어 있음을 판단할 수 있다.
하지만 우리가 찾는 kernel32.dll이 아니므로 다음 FLINK가 가리키고 있는 값을 따라가보자.
>dt _LDR_DATA_TABLE_ENTRY 0x221d40-8
예상과 동일하게
두번째 LDR_DATA_TABLE_ENTRY 구조체는 "ntdll.dll"의 정보가 들어 있음을 판단할 수 있다.
하지만 우리가 찾는 kernel32.dll이 아니므로 다음 FLINK가 가리키고 있는 값을 한번 더 따라가보자.
결과적으로
다음과 같은 과정을 통해서
kernel32.dll의 시작주소값은 0x76020000으로 확인할 수 있다.
② 함수까지의 offset 구하기
DLL은 자신이 어떤 함수들을 Export하고 있는지에 대한 정보를 PE헤더에 저장하고 있다.
이러한 정보들은 PE헤더 IMAGE_OPTIONAL_HEADER32의 DataDirectory 배열의 첫 번째 구조체인 Export Directory에 저장되어 있다.
Export Table은 IMAGE_EXPORT_DIRECTORY 구조체로 저장되어 있으며 아래와 같이 구성된다.
가장 중요한 것은
빨간 네모박스안의 멤버인
AddressOfFunctions : 함수 주소 배열(EAT)
AddressOfNames : 함수명 배열
AddressOfNameOrdinals : 함수 서수 배열
이러한 정보를 이용해서 해당 라이브러리에서 함수 주소를 찾게 되는 데, 다음과 같은 과정을 거쳐 실제 함수의 주소값을 찾는다.
1) 함수명 배열로 이동해서 원하는 함수의 이름과 해당 인덱스를 찾는다.
2) Ordinals 배열에서 인덱스에 해당하는 서수 인덱스 값을 찾는다.
3) EAT 배열에서 서수 인덱스에 해당하는 함수 offset을 확인한다.
4) DLL Base 주소와 offset을 더해서 함수의 실제 주소를 구한다.
이 과정 또한 말로만 해서는 난해하니 직접 실습을 거쳐서 확인해보도록 하자.
실습
WinDBG를 이용하여 프로세스에 로드된 kernel32.dll의 ActivateActCtx 함수의 주소값을 구한다.
먼저 다시한번 kernel32.dll의 base 주소를 확인해보겠다.
kernel32.dll의 base주소는 0x76020000로 확인되었다.
다음으로 IMAGE_EXPORT_DIRECTORY를 찾기 위해 IMAGE_OPTIONAL_HEADER를 찾아보도록 하자.
DOS_HEADER, IMAGE_FILE_HEADER의 다음에 위치하므로 각 구조체의 크기 만큼인 0xf4 + 0x14 만큼을 더해 주자.
// Image_Optional_Header = Base + DOS_HEADER + IMAGE+FILE+HEADER
> dt nt!_IMAGE_OPTIONAL_HEADER 0x76020000+0xf4+0x14
다음으로 0x60 오프셋에 존재하는 IMAGE_EXPORT_DIRECTORY의 주소의 값들을 직접 확인해보자.
// IMAGE_EXPORT_DIRECTORY = IMAGE_OPTIONAL_HEADER + 0x60
> dd 0x76020000+0xf4+0x14+0x60
이를 PEViewer를 통해 다시한번 확인해보자.
base 주소에 이 값을 더해주면 실제 IMAGE_EXPORT_DIRECTORY의 주소를 알 수 있다.
// IMAGE_EXPORT_DIRECTORY = BaseAddress + Export Table Offset
> db 0x76020000+0xb583c
IMAGE_EXPORT_DIRECTORY의 0x28부터 시작하여 차례대로 함수주소배열, 함수명 배열, 서수배열의 주소값을 확인할 수 있다.
PE Viewer에서도 각각의 배열에 대한 동일한 주소를 확인할 수 있다.
필요한 정보들은 모두 구하였으니, 실제로 함수 주소를 구해보겠다.
1) 함수명 배열에서 원하는 함수명의 인덱스 값을 찾는다.
// EXPORT Name Table(ENT) = DLL Base + ENT Offset
> dd 0x76020000+0xb6db4
시작 지점부터 각 4바이트 마다 함수명의 Offset이 저장되어 있다.
각 함수명들을 확인 해보도록 하자.
> da 0x76020000+0xb8db9 // 첫번째 함수명 확인(인덱스 0)
> da 0x76020000+0xb8dd1 // 두번째 함수명 확인(인덱스 1)
> da 0x76020000+0xb8de6 // 세번째 함수명 확인(인덱스 2)
> da 0x76020000+0xb8df5 // 네번째 함수명 확인(인덱스 3)
실제로 각 함수명들이 해당 주소에 있음을 확인하였고 다시한번 PEView를 통해 확인해보자.
2) 서수 테이블의 인덱스 2일 때의 값을 확인한다.
// Export Ordinal Table(EOT) = DLL Base + EOT Offset
> db 0x76020000+0xb8304
서수 테이블에서 인덱스 2일 때의 값은 4임을 확인할 수 있다.
3) EAT에서 인덱스 4의 값을 확인한다.
서수값까지 확인했으니 마지막으로 함수 주소 테이블에서 해당 서수에 해당하는 값을 확인하면 된다.
EAT에서 인덱스 4의 값, 즉 5번째 주소값을 확인하면 된다.
// Export Address Table(EAT) = DLL Base + EAT Offset
> db 0x76020000+0xb5864
5번재 주소값(빨간 네모박스)을 확인하면 옵셋이 0x4556d로 확인된다.
그렇다면 실제 주소값은
0x76020000+0x4556d
으로 판단된다.
한번 정확히 확인해보자.
결론을 내리자면
위와 같은 순서로 함수의 주소값을 동적으로 찾을 수 있다.
이를 이용하면 모든 프로세스에서 작동하는 범용적인 쉘코드를 구현할 수 있을 것이다.
'IT > 시스템 해킹(System Hacking)' 카테고리의 다른 글
쉘코드 인코딩(Shellcode Encoding) (0) | 2017.11.11 |
---|---|
메타스플로잇(Metasploit)을 이용한 쉘코드 작성 (0) | 2017.11.10 |
범용 쉘코드(Universal Shellcode) (1) (0) | 2017.11.06 |
쉘코드(Shellcode) 기초(2) (0) | 2017.11.05 |
쉘코드(Shellcode) 기초(1) (3) | 2017.11.05 |