WWW2Exec - atexit(), TLS Storage & Other mangled Pointers

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 ์ง€์›ํ•˜๊ธฐ

__atexit Structures

Caution

์š”์ฆ˜ ์ด๊ฑธ ์•…์šฉํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ด์ƒํ•ฉ๋‹ˆ๋‹ค!

**atexit()**๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๋“ค์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋“ค์€ exit() ๋˜๋Š” main์˜ return์„ ์‹คํ–‰ํ•  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ ์ด ํ•จ์ˆ˜๋“ค ์ค‘ ํ•˜๋‚˜์˜ ์ฃผ์†Œ๋ฅผ ์˜ˆ๋ฅผ ๋“ค์–ด ์‰˜์ฝ”๋“œ๋กœ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ํ”„๋กœ์„ธ์Šค๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ด๋Š” ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค.
ํ˜„์žฌ ์‹คํ–‰๋  ํ•จ์ˆ˜๋“ค์— ๋Œ€ํ•œ ์ฃผ์†Œ๋Š” ์—ฌ๋Ÿฌ ๊ตฌ์กฐ ๋’ค์— ์ˆจ๊ฒจ์ ธ ์žˆ์œผ๋ฉฐ, ๊ฒฐ๊ตญ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฃผ์†Œ๋Š” ํ•จ์ˆ˜์˜ ์ฃผ์†Œ๊ฐ€ ์•„๋‹ˆ๋ผ XOR๋กœ ์•”ํ˜ธํ™”๋˜๊ณ  ๋ฌด์ž‘์œ„ ํ‚ค๋กœ ๋ณ€์œ„๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํ˜„์žฌ ์ด ๊ณต๊ฒฉ ๋ฒกํ„ฐ๋Š” x86 ๋ฐ x64_86์—์„œ๋Š” ๊ทธ๋ฆฌ ์œ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์•”ํ˜ธํ™” ํ•จ์ˆ˜๋Š” **PTR_MANGLE**์ž…๋‹ˆ๋‹ค. m68k, mips32, mips64, aarch64, arm, hppa์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ์•„ํ‚คํ…์ฒ˜๋Š” ์•”ํ˜ธํ™” ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ž…๋ ฅ์œผ๋กœ ๋ฐ›์€ ๊ฒƒ๊ณผ ๊ฐ™์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฌํ•œ ์•„ํ‚คํ…์ฒ˜๋Š” ์ด ๋ฒกํ„ฐ๋กœ ๊ณต๊ฒฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ž‘๋™ ๋ฐฉ์‹์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฒŒ์‹œ๋ฌผ์—์„œ ์„ค๋ช…ํ•œ ๋ฐ”์™€ ๊ฐ™์ด, ํ”„๋กœ๊ทธ๋žจ์ด return ๋˜๋Š” exit()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข…๋ฃŒ๋˜๋ฉด __run_exit_handlers()๊ฐ€ ์‹คํ–‰๋˜์–ด ๋“ฑ๋ก๋œ ์†Œ๋ฉธ์ž๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

Caution

ํ”„๋กœ๊ทธ๋žจ์ด _exit() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ข…๋ฃŒ๋˜๋ฉด, exit syscall์„ ํ˜ธ์ถœํ•˜๊ณ  ์ข…๋ฃŒ ํ•ธ๋“ค๋Ÿฌ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ __run_exit_handlers()๊ฐ€ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด ๊ทธ์— ๋Œ€ํ•œ ์ค‘๋‹จ์ ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ์ฝ”๋“œ๋Š” (source):

ElfW(Dyn) *fini_array = map->l_info[DT_FINI_ARRAY];
if (fini_array != NULL)
{
ElfW(Addr) *array = (ElfW(Addr) *) (map->l_addr + fini_array->d_un.d_ptr);
size_t sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));

while (sz-- > 0)
((fini_t) array[sz]) ();
}
[...]




// This is the d_un structure
ptype l->l_info[DT_FINI_ARRAY]->d_un
type = union {
Elf64_Xword d_val;	// address of function that will be called, we put our onegadget here
Elf64_Addr d_ptr;	// offset from l->l_addr of our structure
}

map -> l_addr + fini_array -> d_un.d_ptr๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜ธ์ถœํ•  ํ•จ์ˆ˜์˜ ๋ฐฐ์—ด์˜ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐฉ๋ฒ•์— ์ฃผ๋ชฉํ•˜์„ธ์š”.

๋ช‡ ๊ฐ€์ง€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  • map->l_addr์˜ ๊ฐ’์„ ๋ฎ์–ด์จ์„œ **์ž„์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ง€์‹œ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ฐ€์งœ fini_array**๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • l_info[DT_FINI_ARRAY]์™€ l_info[DT_FINI_ARRAYSZ] ํ•ญ๋ชฉ(๋ฉ”๋ชจ๋ฆฌ์—์„œ ๊ฑฐ์˜ ์—ฐ์†์ ์ž„)์„ ๋ฎ์–ด์จ์„œ ์œ„์กฐ๋œ Elf64_Dyn ๊ตฌ์กฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•˜์—ฌ array๊ฐ€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ์˜์—ญ์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด ๊ธ€์€ l_info[DT_FINI_ARRAY]๋ฅผ .bss์— ์žˆ๋Š” ์ œ์–ด๋œ ๋ฉ”๋ชจ๋ฆฌ์˜ ์ฃผ์†Œ๋กœ ๋ฎ์–ด์จ์„œ ๊ฐ€์งœ fini_array๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์งœ ๋ฐฐ์—ด์€ ๋จผ์ € one gadget ์ฃผ์†Œ๋ฅผ ํฌํ•จํ•˜๊ณ , ์ด ์ฃผ์†Œ๊ฐ€ ์‹คํ–‰๋œ ํ›„ ์ด ๊ฐ€์งœ ๋ฐฐ์—ด์˜ ์ฃผ์†Œ์™€ map->l_addr์˜ ๊ฐ’ ์‚ฌ์ด์˜ ์ฐจ์ด๋ฅผ ํฌํ•จํ•˜์—ฌ *array๊ฐ€ ๊ฐ€์งœ ๋ฐฐ์—ด์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด ๊ธฐ์ˆ ์˜ ์ฃผ์š” ๊ฒŒ์‹œ๋ฌผ๊ณผ ์ด ๊ธ€์— ๋”ฐ๋ฅด๋ฉด, ld.so๋Š” ld.so์˜ ์ด์ง„ link_map์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ํฌ์ธํ„ฐ๋ฅผ ์Šคํƒ์— ๋‚จ๊น๋‹ˆ๋‹ค. ์ž„์˜ ์“ฐ๊ธฐ๋ฅผ ํ†ตํ•ด ์ด๋ฅผ ๋ฎ์–ด์“ฐ๊ณ  ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๊ฐ€์งœ fini_array๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์˜ˆ๋ฅผ ๋“ค์–ด one gadget์˜ ์ฃผ์†Œ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ „ ์ฝ”๋“œ ๋‹ค์Œ์— ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ ๋˜ ๋‹ค๋ฅธ ํฅ๋ฏธ๋กœ์šด ์„น์…˜์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

/* Next try the old-style destructor.  */
ElfW(Dyn) *fini = map->l_info[DT_FINI];
if (fini != NULL)
DL_CALL_DT_FINI (map, ((void *) map->l_addr + fini->d_un.d_ptr));
}

์ด ๊ฒฝ์šฐ map->l_info[DT_FINI]์˜ ๊ฐ’์„ ์กฐ์ž‘๋œ ElfW(Dyn) ๊ตฌ์กฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์ •๋ณด๋Š” ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.

TLS-Storage dtor_list ๋ฎ์–ด์“ฐ๊ธฐ in __run_exit_handlers

์—ฌ๊ธฐ์—์„œ ์„ค๋ช…๋œ ๋ฐ”์™€ ๊ฐ™์ด, ํ”„๋กœ๊ทธ๋žจ์ด return ๋˜๋Š” exit()๋ฅผ ํ†ตํ•ด ์ข…๋ฃŒ๋˜๋ฉด **__run_exit_handlers()**๊ฐ€ ์‹คํ–‰๋˜์–ด ๋“ฑ๋ก๋œ ๋ชจ๋“  ์†Œ๋ฉธ์ž ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

_run_exit_handlers()์˜ ์ฝ”๋“œ:

/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS.  */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors.  */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();

**__call_tls_dtors()**์˜ ์ฝ”๋“œ:

typedef void (*dtor_func) (void *);
struct dtor_list //struct added
{
dtor_func func;
void *obj;
struct link_map *map;
struct dtor_list *next;
};

[...]
/* Call the destructors.  This is called either when a thread returns from the
initial function or when the process exits via the exit function.  */
void
__call_tls_dtors (void)
{
while (tls_dtor_list)		// parse the dtor_list chained structures
{
struct dtor_list *cur = tls_dtor_list;		// cur point to tls-storage dtor_list
dtor_func func = cur->func;
PTR_DEMANGLE (func);						// demangle the function ptr

tls_dtor_list = tls_dtor_list->next;		// next dtor_list structure
func (cur->obj);
[...]
}
}

๊ฐ ๋“ฑ๋ก๋œ ํ•จ์ˆ˜์— ๋Œ€ํ•ด **tls_dtor_list**์—์„œ **cur->func**์˜ ํฌ์ธํ„ฐ๋ฅผ ๋””๋ง๊ธ€๋งํ•˜๊ณ  **cur->obj**๋ฅผ ์ธ์ˆ˜๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

์ด GEF์˜ ํฌํฌ์—์„œ tls ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‹ค์ œ๋กœ **dtor_list**๊ฐ€ ์Šคํƒ ์นด๋‚˜๋ฆฌ์™€ PTR_MANGLE ์ฟ ํ‚ค์— ๋งค์šฐ ๊ฐ€๊น๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฅผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐํ•˜๋ฉด ์ฟ ํ‚ค์™€ ์Šคํƒ ์นด๋‚˜๋ฆฌ๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
PTR_MANGLE ์ฟ ํ‚ค๋ฅผ ๋ฎ์–ด์“ฐ๋ฉด PTR_DEMANLE ํ•จ์ˆ˜๋ฅผ 0x00์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” ์‹ค์ œ ์ฃผ์†Œ๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” **xor**๊ฐ€ ์„ค์ •๋œ ์ฃผ์†Œ์™€ ๊ฐ™์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ **dtor_list**์— ์“ฐ๋ฉด ํ•จ์ˆ˜ ์ฃผ์†Œ์™€ ๊ทธ ์ธ์ˆ˜๋กœ ์—ฌ๋Ÿฌ ํ•จ์ˆ˜๋ฅผ ์ฒด์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์ €์žฅ๋œ ํฌ์ธํ„ฐ๋Š” ์ฟ ํ‚ค์™€ xored๋  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ 17๋น„ํŠธ ํšŒ์ „๋„ ์ˆ˜ํ–‰๋œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”:

0x00007fc390444dd4 <+36>:	mov    rax,QWORD PTR [rbx]      --> mangled ptr
0x00007fc390444dd7 <+39>:	ror    rax,0x11		        --> rotate of 17 bits
0x00007fc390444ddb <+43>:	xor    rax,QWORD PTR fs:0x30	--> xor with PTR_MANGLE

๋‹ค์Œ ์ฃผ์†Œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์ „์— ์ด ์ ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์›๋ณธ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์˜ˆ๋ฅผ ์ฐพ์•„๋ณด์„ธ์š”.

**__run_exit_handlers**์˜ ๋‹ค๋ฅธ ๋ง๊ฐ€์ง„ ํฌ์ธํ„ฐ

์ด ๊ธฐ์ˆ ์€ ์—ฌ๊ธฐ์—์„œ ์„ค๋ช…๋ฉ๋‹ˆ๋‹ค ๋ฐ ํ”„๋กœ๊ทธ๋žจ์ด **return ๋˜๋Š” exit()**์„ ํ˜ธ์ถœํ•˜์—ฌ ์ข…๋ฃŒ๋  ๋•Œ ๋‹ค์‹œ ์˜์กดํ•˜๋ฏ€๋กœ **__run_exit_handlers()**๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์ด ํ•จ์ˆ˜์˜ ์ฝ”๋“œ๋ฅผ ๋” ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:

while (true)
{
struct exit_function_list *cur;

restart:
cur = *listp;

if (cur == NULL)
{
/* Exit processing complete.  We will not allow any more
atexit/on_exit registrations.  */
__exit_funcs_done = true;
break;
}

while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;

switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
void *arg;

case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
arg = f->func.on.arg;
PTR_DEMANGLE (onfct);

/* Unlock the list while we call a foreign function.  */
__libc_lock_unlock (__exit_funcs_lock);
onfct (status, arg);
__libc_lock_lock (__exit_funcs_lock);
break;
case ef_at:
atfct = f->func.at;
PTR_DEMANGLE (atfct);

/* Unlock the list while we call a foreign function.  */
__libc_lock_unlock (__exit_funcs_lock);
atfct ();
__libc_lock_lock (__exit_funcs_lock);
break;
case ef_cxa:
/* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
we must mark this function as ef_free.  */
f->flavor = ef_free;
cxafct = f->func.cxa.fn;
arg = f->func.cxa.arg;
PTR_DEMANGLE (cxafct);

/* Unlock the list while we call a foreign function.  */
__libc_lock_unlock (__exit_funcs_lock);
cxafct (arg, status);
__libc_lock_lock (__exit_funcs_lock);
break;
}

if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
/* The last exit function, or another thread, has registered
more exit functions.  Start the loop over.  */
goto restart;
}

*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element.  */
free (cur);
}

__libc_lock_unlock (__exit_funcs_lock);

๋ณ€์ˆ˜ f๋Š” initial ๊ตฌ์กฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋ฉฐ, f->flavor์˜ ๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
๊ฐ’์— ๋”ฐ๋ผ ํ˜ธ์ถœํ•  ํ•จ์ˆ˜์˜ ์ฃผ์†Œ๋Š” ๋‹ค๋ฅธ ์œ„์น˜์— ์žˆ์ง€๋งŒ, ํ•ญ์ƒ demangled ์ƒํƒœ์ž…๋‹ˆ๋‹ค.

๋˜ํ•œ, ์˜ต์…˜ ef_on ๋ฐ **ef_cxa**์—์„œ๋Š” ์ธ์ˆ˜๋ฅผ ์ œ์–ดํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋””๋ฒ„๊น… ์„ธ์…˜์—์„œ GEF๋ฅผ ์‹คํ–‰ํ•˜์—ฌ **gef> p initial**๋กœ initial ๊ตฌ์กฐ์ฒด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์•…์šฉํ•˜๋ ค๋ฉด PTR_MANGLE ์ฟ ํ‚ค๋ฅผ leakํ•˜๊ฑฐ๋‚˜ ์ง€์šด ๋‹ค์Œ, system('/bin/sh')๋กœ initial์˜ cxa ํ•ญ๋ชฉ์„ ๋ฎ์–ด์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด์™€ ๊ด€๋ จ๋œ ์˜ˆ์‹œ๋Š” ๊ธฐ์ˆ ์— ๋Œ€ํ•œ ์›๋ž˜ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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 ์ง€์›ํ•˜๊ธฐ