본문 바로가기

Windows

Windows x64 Universal Shellcode 작성하기

윈도우에서 작동하는 쉘코드를 만들기 위해 아래의 두 사이트를 참고했다.

 

https://rninche01.tistory.com/entry/Universal-Shell-Codex64제작-및-실습

 

Universal Shell Code(x64)제작 및 실습

이번에는 저번에 배웠던 x86환경에서 Universal Shellcode를 작성하는 원리와 실습에서 응용하여 x64환경에서 Universal Shellcode를 작성하여 실행하는데 목표를 두었다. 참고로 저번에는 WinExec함수를 통해

rninche01.tistory.com

https://wogh8732.tistory.com/183

 

window x64 환경에서 Universal shellcode 만들기

목차 소개 환경 분석 3.1 쉘코드 작성 3.2 쉘코드 실행 결론 1. 소개 " 윈도우 시스템 해킹 가이드 버그헌팅과 익스플로잇" 개정판이 얼마전 나왔었다. 해당 챕터 중 Universal shellcode를 작성하는 부분

wogh8732.tistory.com


두 개의 사이트를 참고해 환경을 설정하고 코드를 작성했다.

솔루션 탐색기 캡처

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 - [기타] - 시스템이 지정된 프로그램을 실행할 수 없습니다. 해결 방법

 

시스템이 지정된 프로그램을 실행할 수 없습니다. 해결 방법

여러 테크닉에 대해 코드로 구현하고 실행하다 보면 cmd 창에서 다음과 같은 문구가 뜨며, 프로그램이 실행되지 않을 때가 있습니다. 제가 찾은 해결방법은 두가지 입니다. 1. Microsoft Defender의 실

hobak-gamja.tistory.com