윈도우에서 작동하는 쉘코드를 만들기 위해 아래의 두 사이트를 참고했다.
https://rninche01.tistory.com/entry/Universal-Shell-Codex64제작-및-실습
https://wogh8732.tistory.com/183
두 개의 사이트를 참고해 환경을 설정하고 코드를 작성했다.
Visual Studio 2022로 작성했고,
솔루션 탐색기로 본 솔루션의 구성은 위 사진과 같다.
main.cpp과 shellcode.asm이 주요한 파일이고, 나머지는 필요없다.
하지만, 위 두 개의 참고한 사이트에서 있었기 때문에 검색을 통해서 헤더파일과 소스코드를 넣었다.
그런데 뭐 별로 필요 없다.
혹시 궁금한 사람을 위해서 모든 코드를 올리도록 하겠다.
main.cpp
#include "stdafx.h"
#include <Windows.h>
extern "C" void SHELL();
int main() {
SHELL();
return 0;
}
shellcode.asm
.code
SHELL PROC
jmp start
get_func_addr :
loop_ent:
inc rdx
mov eax, dword ptr [rsi]
add rsi, 4
push rax
push rcx
push rdx
push rbx
push rsp
push rbp
push rsi
push rdi
add rbx, rax
mov rsi, rbx
xor rax, rax
xor rdi, rdi
hash :
mov al, byte ptr[rsi]
add rsi, 1
add rdi, rax
test al, al
jnz hash
mov qword ptr [rbp + 10h], rdi
pop rdi
pop rsi
pop rbp
pop rsp
pop rbx
pop rdx
pop rcx
pop rax
cmp [rbp + 10h], rdi
jne loop_ent
movzx rdx, word ptr[rcx + rdx * 2 - 2]
mov rdi, [rbp + 18h]
xor rsi, rsi
mov esi, dword ptr [rdi + 1ch]
mov rdi, rbx
add rsi, rdi
xor rbx, rbx
mov ebx, dword ptr [rsi + rdx * 4]
add rdi, rbx
mov rax, rdi
ret
start:
xor rax, rax
xor rdi, rdi
xor rsi, rsi
xor rcx, rcx
mov rax, gs : [rax+60h] ; peb
mov rax, [rax + 18h] ; peb_ldr_data
mov rax, [rax + 10h] ; .exe inloadordermodulelist
mov rbx, [rax] ; ntdll.dll inloadordermodulelist
mov rbx, [rbx] ; kernel32.dll inloadordermodulelist
mov rbx, [rbx + 30h] ; kernel32.dll base adr
mov edi, dword ptr [rbx + 3ch] ; pe header
add rdi, rbx
xor r8, r8
add r8, rdi
add r8, 40h
mov edi, dword ptr [r8 + 48h] ; Export Table
add rdi, rbx
mov [rbp + 18h], rdi
mov esi, dword ptr [rdi + 20h] ; Export name Table
add rsi, rbx
mov ecx, dword ptr [rdi + 24h] ; Ordinal Table
add rcx, rbx
xor rdx, rdx
push rax
push rcx
push rdx
push rbx
push rsp
push rbp
push rsi
push rdi
xor rdi, rdi
add di, 479h
call get_func_addr ; get exitprocess address
mov [rbp + 40h], rax ; exitprocess address
pop rdi
pop rsi
pop rbp
pop rsp
pop rbx
pop rdx
pop rcx
pop rax
xor rdi, rdi
add di, 496h
call get_func_addr ; get loadlibrary address
mov [rbp + 48h], rax ; loadlibrary address
sub rsp, 8h
xor rax, rax
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 qword ptr[rbp + 48h] ; call loadlibrary
; user32.dll의 EAT, ENT, Ordinal Table 주소 구하기
xor rdi, rdi
xor rsi, rsi
xor rcx, rcx
mov rbx, rax ; rax는 user32.dll base adr
mov edi, dword ptr [rbx + 3ch]
add rdi, rbx
xor r8, r8
add r8, rdi
add r8, 40h
mov edi, dword ptr [r8 + 48h]
add rdi, rbx
mov [rbp + 18h], rdi
mov esi, dword ptr [rdi + 20h]
add rsi, rbx
mov ecx, dword ptr [rdi + 24h]
add rcx, rbx
xor rdx, rdx
xor rdi, rdi
add di, 42fh
call get_func_addr ; MessageBoxA address
mov [rbp + 50h], rax
sub rsp, 20h
xor rax, rax
xor rcx, rcx
xor rdx, rdx
mov qword ptr[rbp+28h], rax
mov byte ptr[rbp+28h], 68h
mov byte ptr[rbp+29h], 65h
mov byte ptr[rbp+2ah], 6ch
mov byte ptr[rbp+2bh], 6ch
mov byte ptr[rbp+2ch], 6fh
xor r8, r8
mov qword ptr[rbp+30h], rcx
mov byte ptr[rbp+30h], 68h
mov byte ptr[rbp+31h], 69h
xor r9, r9
lea rdx, [rbp+28h]
lea r8, [rbp+30h]
call qword ptr[rbp+50h] ; call messageBoxA(0, 'hello', 'hi', 0)
xor rcx, rcx
call qword ptr[rbp + 40h] ; call exitprocess
SHELL ENDP
End
stdafx.cpp
// stdafx.cpp : 표준 포함 파일만 들어 있는 소스 파일입니다.
// shellcode_universal.pch는 미리 컴파일된 헤더가 됩니다.
// stdafx.obj에는 미리 컴파일된 형식 정보가 포함됩니다.
#include "stdafx.h"
// TODO: 필요한 추가 헤더는
// 이 파일이 아닌 STDAFX.H에서 참조합니다.
stdafx.h
// stdafx.h : 자주 사용하지만 자주 변경되지는 않는
// 표준 시스템 포함 파일 및 프로젝트 관련 포함 파일이
// 들어 있는 포함 파일입니다.
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
// TODO: 프로그램에 필요한 추가 헤더는 여기에서 참조합니다.
targetver.h
#pragma once
// SDKDDKVer.h를 포함하면 최고 수준의 가용성을 가진 Windows 플랫폼이 정의됩니다.
// 이전 Windows 플랫폼에 대해 응용 프로그램을 빌드하려는 경우에는 SDKDDKVer.h를 포함하기 전에
// WinSDKVer.h를 포함하고 _WIN32_WINNT 매크로를 지원하려는 플랫폼으로 설정하십시오.
#include <SDKDDKVer.h>
코드는 위 두 사이트를 통해 이해하고, 두 사이트에 있는 것을 비교해서 거의 복붙했던 것 같다.
이렇게 메세지 박스를 띄우는 코드를 작성했으면, 바이트 코드로 따야한다.
나는 이 솔루션을 빌드하고 나온 exe 파일을 HxD로 열어서 Hex값을 얻었다가 뭔가 에러가 나서
Visual Studio에서 바이트 코드로 본 다음에 그걸 노가다로 정리했던 것 같다.
이 방법에 대해선 추후에 시간이 된다면 캡처해서 더 자세하게 기술하겠다.
(위 두 개의 참고 사이트를 보면서 어느정도 할 수 있다.)
그 다음엔 이 쉘코드를 테스트하는 코드를 작성해야 한다.
다음과 같이 작성하면 된다.
Test_Shellcode
main.cpp
#include <stdio.h>
#include <Windows.h>
unsigned char shellcode[] = {
0xEB, 0x5F, 0x48, 0xFF, 0xC2, 0x8B, 0x06, 0x48, 0x83, 0xC6, 0x04, 0x50, 0x51, 0x52, 0x53,
0x54, 0x55, 0x56, 0x57, 0x48, 0x03, 0xD8, 0x48, 0x8B, 0xF3, 0x48, 0x33, 0xC0, 0x48, 0x33,
0xFF, 0x8A, 0x06, 0x48, 0x83, 0xC6, 0x01, 0x48, 0x03, 0xF8, 0x84, 0xC0, 0x75, 0xF3, 0x48,
0x89, 0x7D, 0x10, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x48, 0x39, 0x7D, 0x10,
0x75, 0xC4, 0x48, 0x0F, 0xB7, 0x54, 0x51, 0xFE, 0x48, 0x8B, 0x7D, 0x18, 0x48, 0x33, 0xF6,
0x8B, 0x77, 0x1C, 0x48, 0x8B, 0xFB, 0x48, 0x03, 0xF7, 0x48, 0x33, 0xDB, 0x8B, 0x1C, 0x96,
0x48, 0x03, 0xFB, 0x48, 0x8B, 0xC7, 0xC3, 0x48, 0x33, 0xC0, 0x48, 0x33, 0xFF, 0x48, 0x33,
0xF6, 0x48, 0x33, 0xC9, 0x65, 0x48, 0x8B, 0x40, 0x60, 0x48, 0x8B, 0x40, 0x18, 0x48, 0x8B,
0x40, 0x10, 0x48, 0x8B, 0x18, 0x48, 0x8B, 0x1B, 0x48, 0x8B, 0x5B, 0x30, 0x8B, 0x7B, 0x3C,
0x48, 0x03, 0xFB, 0x4D, 0x33, 0xC0, 0x4C, 0x03, 0xC7, 0x49, 0x83, 0xC0, 0x40, 0x41, 0x8B,
0x78, 0x48, 0x48, 0x03, 0xFB, 0x48, 0x89, 0x7D, 0x18, 0x8B, 0x77, 0x20, 0x48, 0x03, 0xF3,
0x8B, 0x4F, 0x24, 0x48, 0x03, 0xCB, 0x48, 0x33, 0xD2, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
0x56, 0x57, 0x48, 0x33, 0xFF, 0x66, 0x81, 0xC7, 0x79, 0x04, 0xE8, 0x3F, 0xFF, 0xFF, 0xFF,
0x48, 0x89, 0x45, 0x40, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x48, 0x33, 0xFF,
0x66, 0x81, 0xC7, 0x96, 0x04, 0xE8, 0x26, 0xFF, 0xFF, 0xFF, 0x48, 0x89, 0x45, 0x48, 0x48,
0x83, 0xEC, 0x08, 0x48, 0x33, 0xC0, 0x48, 0x89, 0x45, 0x20, 0x48, 0x89, 0x45, 0x28, 0xC6,
0x45, 0x20, 0x75, 0xC6, 0x45, 0x21, 0x73, 0xC6, 0x45, 0x22, 0x65, 0xC6, 0x45, 0x23, 0x72,
0xC6, 0x45, 0x24, 0x33, 0xC6, 0x45, 0x25, 0x32, 0xC6, 0x45, 0x26, 0x2E, 0xC6, 0x45, 0x27,
0x64, 0xC6, 0x45, 0x28, 0x6C, 0xC6, 0x45, 0x29, 0x6C, 0x48, 0x8D, 0x4D, 0x20, 0xFF, 0x55,
0x48, 0x48, 0x33, 0xFF, 0x48, 0x33, 0xF6, 0x48, 0x33, 0xC9, 0x48, 0x8B, 0xD8, 0x8B, 0x7B,
0x3C, 0x48, 0x03, 0xFB, 0x4D, 0x33, 0xC0, 0x4C, 0x03, 0xC7, 0x49, 0x83, 0xC0, 0x40, 0x41,
0x8B, 0x78, 0x48, 0x48, 0x03, 0xFB, 0x48, 0x89, 0x7D, 0x18, 0x8B, 0x77, 0x20, 0x48, 0x03,
0xF3, 0x8B, 0x4F, 0x24, 0x48, 0x03, 0xCB, 0x48, 0x33, 0xD2, 0x48, 0x33, 0xFF, 0x66, 0x81,
0xC7, 0x2F, 0x04, 0xE8, 0xA1, 0xFE, 0xFF, 0xFF, 0x48, 0x89, 0x45, 0x50, 0x48, 0x83, 0xEC,
0x20, 0x48, 0x33, 0xC0, 0x48, 0x33, 0xC9, 0x48, 0x33, 0xD2, 0x48, 0x89, 0x45, 0x28, 0xC6,
0x45, 0x28, 0x68, 0xC6, 0x45, 0x29, 0x65, 0xC6, 0x45, 0x2A, 0x6C, 0xC6, 0x45, 0x2B, 0x6C,
0xC6, 0x45, 0x2C, 0x6F, 0x4D, 0x33, 0xC0, 0x48, 0x89, 0x4D, 0x30, 0xC6, 0x45, 0x30, 0x68,
0xC6, 0x45, 0x31, 0x69, 0x4D, 0x33, 0xC9, 0x48, 0x8D, 0x55, 0x28, 0x4C, 0x8D, 0x45, 0x30,
0xFF, 0x55, 0x50, 0x48, 0x33, 0xC9, 0xFF, 0x55, 0x40
};
int main() {
printf("%d\n", sizeof(shellcode));
void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof(shellcode));
((void(*)())exec)();
return 0;
}
바이트 코드 따기 귀찮다면 그냥 위에 코드를 복붙해서 테스트해도 될 것 같다.
실행 결과는 다음과 같다.
위 사진과 같이 메세지 박스가 잘 뜨는 것을 볼 수 있다.
참고로 cmd창에 출력되는 숫자는 쉘코드의 길이이다.
혹시라도 만든 test 코드가 실행이 되지 않는 사람이 있다면,
본 블로그의 다음 글을 참고해보길 바란다.
2023.04.05 - [기타] - 시스템이 지정된 프로그램을 실행할 수 없습니다. 해결 방법