tmxklab
Universal Shell Code(x64)제작 및 실습 본문
이번에는 저번에 배웠던 x86환경에서 Universal Shellcode를 작성하는 원리와 실습에서 응용하여 x64환경에서 Universal Shellcode를 작성하여 실행하는데 목표를 두었다. 참고로 저번에는 WinExec함수를 통해 cmd창을 오픈하는 Universal Shellcode를 작성하였지만 응용하여 MessageBoxA함수를 통해 메시지박스를 오픈하도록 작성할 것이다.
0. 목표
x64환경에서 MessageBoxA함수를 통해 메시지 박스를 오픈하는 Universal Shellcode작성 및 실행
1. 실습 환경
-
OS : Windows 10(x64)
-
Debugging Tool : WinDbg Preview
-
IDE : Visual Studio 2017
-
PE파일 분석 툴 : CFF Explorer
2. 환경 구축
x86환경의 VS 2010에서 인라인 어셈블리가 가능했지만 x64환경에서는 인라인 어셈블리를 사용할 수 없다. 따라서, VS 2017에서 어셈블리어 코드를 작성할 수 있는 환경과 추가로 쉘 코드 작성시 디버깅을 위한 환경을 구성해주도록 한다.
인라인 어셈블리(Inline Assembly)란?
- 어셈블리 명령들을 Inline함수로 작성하는 것을 뜻하며 이전 실습에서 asm키워드를 사용하여 어셈블리 명령어를 코딩한 것을 말한다.
* x64 VS 2017에서 asm키워드를 사용한 경우
참고 링크)
2.1 어셈블리어 코드 작성 환경 구성
masm으로 파일을 만들어서 cpp파일에 링킹시키는 과정으로 작성
환경 설정은 다음 링크를 참조하여 적용하였다.
1) 프로젝트 생성 → [Windows 콘솔 응용 프로그램] 선택
2) 빌드에서 x64설정
3) [프로젝트 우클릭] → [빌드 종속성] → [사용자 지정 빌드] → [masm]체크
4) 이름.asm파일 생성 및 우클릭 → [속성] → [일반] → [항목형식] → [Microsoft Macro Assembler]
5) .asm파일에서 함수명 지정
6) cpp파일에 해당 asm링킹
2.2 쉘 코드 작성시 디버깅을 위한 환경 구성
1) [도구] → [옵션]
2) [디버깅] → [일반] → [주소 수준 디버깅 사용]체크
3) bp설정 및 마우스 우클릭 → [디스어셈블리로 이동]
4) [디버그] → [창/Windows] → [레지스터]
3. 분석
이번에도 지난 번에 배웠던 것(x86 Universal Shellcode)과 동일하게 Universal Shellcode를 작성하기 위해 dll의 시작 주소 값과 dll 시작주소부터 함수까지의 offset을 구하는 작업을 진행하도록 하겠다.(여기서는 MessageBoxA함수를 사용하기 위해 user32.dll의 Base Address와 offset을 구해야 함)
MessageBoxA함수
MessageBoxA함수를 사용하기 위해서 user32.dll파일 필요
1) MessageBoxA()를 사용하여 메시지 박스를 띄우는 코드 작성하여 exe파일 생성
2) CFF Explorer툴을 사용하여 MessageBoxA함수 확인
3.1 WinDbg와 CFF Explorer툴을 사용하여 분석
분석 대상 : UniversalShellcode.exe
1) TEB 시작주소 확인 - WinDbg
2) PEB 시작주소 확인 - WinDbg
3) Ldr멤버 변수 주소 확인 - WinDbg
4) 로드된 모듈의 리스트 시작 주소 확인 - WinDbg
x86에서 InMemoryOrderModuleList를 따라갔지만 x64에서는 InLoadOrderModuleList를 따라감
5) 첫 번째 모듈 확인(자기자신, "UniversalShellcode.exe") - WinDbg
6) 두 번째 모듈 확인("ntdll.dll") - WinDbg
7) 세 번째 모듈 확인("kernel32.dll") - WinDbg
8) 네 번째 모듈 확인("kernelbase.dll") - WinDbg
9) 다섯 번째 모듈 확인("user32.dll") - WinDbg
10) user32.dll의 export table확인 - CFF Explorer
11) Optional Header확인 - CFF Explorer
- PE파일 분석할 때 배웠듯이 Optional Header의 Magic값에 따라 IMAGE_OPTIONAL_HEADER가 32인지 64인지 구분해준다고 했으므로 WinDbg에서 IMAGE_OPTIONAL_HEADER64라고 적어줘야 제대로 나온다.
- 또한, Optional Header까지 Offset이 0x108이므로 아까 user32.dll의 Base주소를 구한 값에 0x108을 더하면 WinDbg에서 나온 값과 cff Explorer에서 나온 값과 동일할 것이다.
12) user32.dll의 IMAGE_OPTIONAL_HEADER64확인 - WinDbg
13) user32.dll의 IMAGE_OPTIONAL_HEADER64확인 - CFF Explorer
그리고 WinDbg에서 마지막 총 16개의 테이블의 시작 주소가 Optinal Header로부터 0x70옵셋을 가지는 것을 확인했으므로 다시 디버깅하여 확인한다.
14) Data Directory확인 - WinDbg
15) Data Directory확인 - CFF Explorer
WinDbg와 CFF Explorer에서 확인한 offset과 동일함을 알 수 있다. 이제 다시 user32.dll Base주소에 Export Table RVA의 value값을 더하여 디버깅해본다.
16) Export Table확인 - WinDbg
17) Export Table확인 - CFF Explorer
WinDbg와 CFF Explorer로 확인한 값들이 동일함을 알 수 있으며 주요 테이블 3개(AddressOfFunction, Names, NameOrdinals)를 확인해본다.
18) Export Table(Function, Names, Ordinals ) - CFF Explorer
19) AddressOfNames - WinDbg
(흠 근데 왜 WinDbg에서 첫 번째 함수가 CFF Explorer에서 확인할 떄는 2번째 함수인지 모르겠음 일단 넘어갖 ㅋ)
20) AddressOfNameOrdinals - WinDbg
21) AddressOfFunctions - WinDbg
22) 함수 주소 확인 - WinDbg
결론
x86환경에서 WinDbg, PE View를 통해서 PE파일을 분석한 결과와 x64환경에서 분석한 결과가 조금씩 차이를 보인다. 위에 분석을 통해 필요한 작업들을 마쳤으므로 이제 Universal Shellcode를 작성해보기로 한다.
4. 쉘 코드 작성
이제부터 이전 작업을 바탕으로 Universal Shellcode를 작성해보기로 한다.
4.1 시행착오
1) PEB Address 구하기
x86에서는 fs:[30]이었지만 x64에서는 gs:[60]으로 바뀜
참고
그리고 gs레지스터를 이용하여 다음과 같이 값을 바로 가져오면 접근 에러가 발생
이유)
현재 rax의 값이 0xcccccccc로 세팅되어 있으므로 xor연산을 통해 rax의 값을 0x0으로 세팅해줘야 함
2) user32.dll Base Address 구하기
이제 PEB를 구했으니 user32.dll Base Address를 구하는 도중 다음과 같은 문제점이 발생하였다.
[이거는 kernelbase.dll의 dllbase를 구한거 - windbg랑 디버깅이랑 같은 값을 가짐]
[이거는 user32.dll의 dllbase를 구한거 - windbg랑 디버깅이랑 다른 값을 가짐]
이유)
kernel32.dll과 ntdll.dll은 부팅시 메모리에 로딩되는데 user32.dll은 필요 있을 때 로딩이 된다.
그래서 왼쪽 windbg는 MessageBoxA함수가 포함된 exe파일이므로 user32.dll이 리스트에 들어가있지만 오른쪽 디버깅에서는 user32.dll이 리스트에 존재하지 않는다.
user32.dll 참고)
해결방법)
모든 프로세스는 kernel32.dll을 공유하고 동일한 주소 값을 가진다.
x86에서도 설명했듯이 kernel32.dll과 ntdll.dll은 무조건 메모리에 올라온 상태이다. 하지만, 핵심 dll중 하나인 user32.dll은 프로그램이 필요로할 때 dll을 호출하는 형식이다. 따라서, user32.dll의 주소 값을 구하기 위해서는 kernel32.dll에 포함된 LoadLibrary함수를 이용하여 user32.dll을 로드해야한다.
loadlibrary 추가 설명)
참고로 kernel32.dll의 Export Table에 LoadLibrary함수가 존재하지 않으므로 이와 비슷하게 동작하는 LoadLibraryA함수를 호출하기로 한다.
[LoadLibraryA 테스트]
rax값에 kernel32.dll의 dllbase주소 값이 나옴(WinDbg랑 동일)
3) MessageBoxA함수 추가 설명
rcx, rdx, r8d, r9d레지스터를 사용하여 MessageBoxA함수의 파라미터로 들어간다.
+) Windows x64 호출 규약
파라미터가 첫 번째부터 rcx, rdx, r8d, r9d순서대로 전달되며 홈 공간을 확보한 후 함수를 호출해야 한다.
4.2 프로세스
이제 쉘 코드 작성하기 위해 진행 절차를 간략하게 정리하면 다음과 같다.
① kernel32.dll의 dllbase주소를 구한다.
② kernel32.dll의 dllbase를 통해 Optional Header에서 3개의 테이블을 통해 LoadLibraryA와 ExitProcess를 구한다.
③ "user32.dll"문자열을 LoadLibrary함수의 파라미터로 넘겨 반환 값(user32.dll의 dllbase)을 받는다.
④ user32.dll의 dllbase를 통해 Optional Header에서 3개의 테이블을 통해 MessageBoxA함수의 주소 값을 구한다.
⑤ MessageBoxA함수를 호출하고 ExitProcess함수를 호출하여 프로세스를 종료한다.
4.3 쉘 코드 작성
0) 각 함수의 hash값 확인
hash_calc.py 파일은 지난번 x86에서 Universal Shellcode원리 및 실습 때 사용한 것과 동일하다.
확인)
LoadLibraryA : 0x496
MessageBoxA : 0x42f
ExitProcess : 0x479
1) 어셈 코드 작성
.code
SHELL PROC
jmp start
get_func_addr:
xor r9, r9 ; NameTable Index
xor r10, r10 ; Cal Hash
xor rcx, rcx
xor rdx, rdx
add rsi, rax
mov ecx, dword ptr[rsi]
add rcx, rax ; rcx : FunctionName Address
jmp hash
loop_ent:
xor r10, r10
inc r9 ; Inc NameTable Index
mov ecx, dword ptr[rsi+r9*4] ; Inc FunctionName Address
add rcx, rax
hash:
mov dl, byte ptr [rcx] ; rdx : 1byte AsciiCode
inc rcx
add r10, rdx
test dl, dl
jnz hash
cmp rdi, r10 ; cmp Find Func, FunctionTable Element Hash
jne loop_ent
mov ecx, dword ptr [rbx+24h] ; rbx : AddressOfNameOrdinals[Export Table]
add rcx, rax
mov dx, word ptr [rcx+2*r9+2]
mov r9, rdx
mov ecx, dword ptr [rbx+1ch] ; rcx : AddressOfFunctions[Export Table]
add rcx, rax
mov edx, dword ptr [rcx+4*r9-4]
add rdx, rax ; rdx : Real Function Address
ret
start:
xor rax, rax ; Setting '0'
xor rbx, rbx
mov rax, gs:[rax+60h] ; PEB Address
mov rax, [rax+18h] ; PEB_LDR_DATA
mov rax, [rax+10h] ; .exe
mov rbx, [rax] ; ntdll.dll
mov rax, [rbx] ; kernel32.dll
mov rax, [rax+30h] ; rax : kernel32.dll Base Address
mov rbx, rax
add rbx, 7ah ; PE Header Offset / 0x100 : Optional Header / 0x70 : Data Directory(First Export Table)
add rbx, 7bh ; 170h = 7ah + 7bh + 7bh
add rbx, 7bh
mov ebx, dword ptr [rbx] ; rbx : Export Table RVA
add rbx, rax ; rbx : Export Table Address
mov esi, dword ptr [rbx+20h] ; rsi : AddressOfNames[Export Table]
xor rdi, rdi ; Setting '0'
mov di, 479h ; 0x479 : ExitProcess()의 hash값
call get_func_addr
mov [rbp+48h], rdx ; [rbp+10h] : ExitProcess()
mov rbx, rax
add rbx, 7ah ; PE Header Offset / 0x100 : Optional Header / 0x70 : Data Directory(First Export Table)
add rbx, 7bh ; 170h = 7ah + 7bh + 7bh
add rbx, 7bh
mov ebx, dword ptr [rbx] ; rbx : Export Table RVA
add rbx, rax ; rbx : Export Table Address
mov esi, dword ptr [rbx+20h] ; rsi : AddressOfNames[Export Table]
xor rdi, rdi ; Setting '0'
mov di, 496h ; 0x496 : LoadLibraryA()의 hash값
call get_func_addr
sub rsp, 8h
xor rax, rax ; Setting '0'
mov qword ptr[rbp+20h], rax
mov qword ptr[rbp+28h], rax
mov byte ptr[rbp+20h], 75h ; u
mov byte ptr[rbp+21h], 73h ; s
mov byte ptr[rbp+22h], 65h ; e
mov byte ptr[rbp+23h], 72h ; r
mov byte ptr[rbp+24h], 33h ; 3
mov byte ptr[rbp+25h], 32h ; 2
mov byte ptr[rbp+26h], 2eh ; .
mov byte ptr[rbp+27h], 64h ; d
mov byte ptr[rbp+28h], 6ch ; l
mov byte ptr[rbp+29h], 6ch ; l
lea rcx, [rbp+20h]
call rdx ; call LoadLibrary() / ret -> rax : user32.dll
add rsp, 10h
mov rbx, rax
add rbx, 7dh ; PE Header Offset / 0x108 : Optional Header / 0x70 : Data Directory(First Export Table)
add rbx, 7dh
add rbx, 7eh
mov ebx, dword ptr [rbx] ; rbx : Export Table RVA
add rbx, rax ; rbx : Export Table Address
mov esi, dword ptr [rbx+20h] ; rsi : AddressOfNames[Export Table]
xor rdi, rdi ; Setting '0'
mov di, 42fh ; 0x42f : MessageBoxA()의 hash값
call get_func_addr
mov rbx, rdx
sub rsp, 20h
xor rdx, rdx
xor rax, rax
mov qword ptr[rbp+28h], rax
mov qword ptr[rbp+30h], rax
mov byte ptr[rbp+28h], 68h ; h
mov byte ptr[rbp+29h], 69h ; i
xor r9d, r9d
lea r8, [rbp+28h]
lea rdx, [rbp+28h]
xor ecx, ecx
call rbx ; call MessageBoxA()
add rsp, 28h
xor ecx, ecx
sub rsp, 8h
call qword ptr [rbp+48h] ; call ExitProcess()
add rsp, 8h
SHELL ENDP
End
2) 어셈 코드 테스트
3) 쉘 코드 변환
쉘 코드 변환 과정은 디버깅모드로 디스 어셈블리 창에서 추출하였다.
솔직히 엄청난 노가다를 하였다....
또한 디버깅을 통해 널 값과 bad char을 직접 제거해주는 방법으로 인코딩하여 쉘 코드를 제작하였다.
#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
unsigned char shellcode[353] = "\xEB\x52\x4D\x33\xC9\x4D\x33\xD2\x48\x33\xC9\x48\x33\xD2\x48\x03\xF0\x8B\x0E\x48\x03\xC8\xEB\x0D"
"\x4D\x33\xD2\x49\xFF\xC1\x42\x8B\x0C\x8E\x48\x03\xC8\x8A\x11\x48\xFF\xC1\x4C\x03\xD2\x84\xD2\x75\xF4"
"\x49\x3B\xFA\x75\xE2\x8B\x4B\x24\x48\x03\xC8\x66\x42\x8B\x54\x49\x02\x4C\x8B\xCA\x8B\x4B\x1C\x48\x03\xC8"
"\x42\x8B\x54\x89\xFC\x48\x03\xD0\xC3\x48\x33\xC0\x48\x33\xDB\x65\x48\x8B\x40\x60\x48\x8B\x40\x18\x48\x8B\x40\x10"
"\x48\x8B\x18\x48\x8B\x03\x48\x8B\x40\x30\x48\x8B\xD8\x48\x83\xC3\x7A\x48\x83\xC3\x7B\x48\x83\xC3\x7B\x8B\x1B"
"\x48\x03\xD8\x8B\x73\x20\x48\x33\xFF\x66\xBF\x79\x04\xE8\x6E\xFF\xFF\xFF\x48\x89\x55\x48\x48\x8B\xD8\x48\x83\xC3\x7A"
"\x48\x83\xC3\x7B\x48\x83\xC3\x7B\x8B\x1B\x48\x03\xD8\x8B\x73\x20\x48\x33\xFF\x66\xBF\x96\x04\xE8\x47\xFF\xFF\xFF"
"\x48\x83\xEC\x08\x48\x33\xC0\x48\x89\x45\x20\x48\x89\x45\x28\xC6\x45\x20\x75\xC6\x45\x21\x73\xC6\x45\x22\x65\xC6\x45\x23\x72"
"\xC6\x45\x24\x33\xC6\x45\x25\x32\xC6\x45\x26\x2E\xC6\x45\x27\x64\xC6\x45\x28\x6C\xC6\x45\x29\x6C\x48\x8D\x4D\x20"
"\xFF\xD2\x48\x83\xC4\x10\x48\x8B\xD8\x48\x83\xC3\x7D\x48\x83\xC3\x7D\x48\x83\xC3\x7E\x8B\x1B\x48\x03\xD8\x8B\x73\x20"
"\x48\x33\xFF\x66\xBF\x2F\x04\xE8\xE3\xFE\xFF\xFF\x48\x8B\xDA\x48\x83\xEC\x20\x48\x33\xD2\x48\x33\xC0\x48\x89\x45\x28"
"\x48\x89\x45\x30\xC6\x45\x28\x68\xC6\x45\x29\x69\x45\x33\xC9\x4C\x8D\x45\x28\x48\x8D\x55\x28\x33\xC9\xFF\xD3\x48\x83\xC4\x28"
"\x33\xC9\x48\x83\xEC\x08\xFF\x55\x48\x48\x83\xC4\x08";
int main()
{
printf("%zd\n", sizeof(shellcode));
void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
((void(*)())exec)();
return 0;
}
하지만, IDA에 LazyIDA플러그인을 설치하면 좀 더 편리하고 쉽게 쉘 코드를 뽑아올 수 있다.
5. 실행 결과
'Security > 01 System Hacking' 카테고리의 다른 글
heap(2) - glibc malloc(1) (feat. Arena) (0) | 2020.07.07 |
---|---|
heap(1) - Dynamic Memory Allocation (4) | 2020.07.07 |
Universal Shell code(x86)원리 및 실습 (0) | 2020.05.17 |
윈도우 실행파일 구조(PE파일) (2) | 2020.05.17 |
RTL(Return-To-Libc) 원리 및 실습 (2) | 2020.02.16 |