gccでlibc抜きでコンパイルを通す
Hello from a libc-free world! (Part 1) (Ksplice Blog) がおもしろかったのでやっていく。
libcありのとき
コード
よくあるhello worldのコード
$ cat hello_with_libc.c # include <stdio.h> int main() { printf("Hello World\n"); return 0; }
コンパイル
$ gcc hello_with_libc.c -o hello_with_libc $ ./hello_with_libc Hello World $ wc -c ./hello_with_libc 8561 ./hello_with_libc
もちろん問題なくコンパイルできる
libcなしのとき
コード
libcを使わないコードを書いた。
$ cat hello_no_libc.c int main() { char *str = "Hello World"; return 0; }
コンパイル
$ gcc -nostdlib hello_no_libc.c -o hello_no_libc /usr/bin/ld: 警告: エントリシンボル _start が見つかりません。デフォルトとして 0000000000400144 を使用します $ ./hello_no_libc Segmentation fault (コアダンプ)
-nostdlibをつけてlibcを含まずに、コンパイルするとエントリポイントが見つからないと怒られる。 ELF形式のエントリポイントはcrt1.oで定義されているのでリンクしてみる。
$ gcc -Os -c hello_no_libc.c $ ld /usr/lib/x86_64-linux-gnu/crt1.o -o hello_no_libc hello_no_libc.o ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 0 が無効なシンボル索引 11 を持っています ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 1 が無効なシンボル索引 12 を持っています ... ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 18 が無効なシンボル索引 13 を持っています ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 19 が無効なシンボル索引 21 を持っています ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_line): 再配置 0 が無効なシンボル索引 2 を持っています /usr/lib/x86_64-linux-gnu/crt1.o: 関数 `_start' 内: (.text+0x12): `__libc_csu_fini' に対する定義されていない参照です /usr/lib/x86_64-linux-gnu/crt1.o: 関数 `_start' 内: (.text+0x19): `__libc_csu_init' に対する定義されていない参照です /usr/lib/x86_64-linux-gnu/crt1.o: 関数 `_start' 内: (.text+0x25): `__libc_start_main' に対する定義されていない参照です
libc_csu_fini、libc_csu_init、libc_start_mainがないと怒られた。これらはmain関数が呼ばれる前に呼ばれる関数でlibcのセットアップを行う。crt1.oに頼らず、main関数を呼ぶエントリポイントを書く必要がある。
$ cat stubstart.s .globl _start _start: call main mov $60, %eax # exit xor %rdi, %rdi syscall
stubstart.sはmain関数を呼んだ後、exitのシステムコールを呼び出し終了する_start関数を書いたものである。 これがエントリポイントとなる。
$ gcc -nostdlib stubstart.s hello_no_libc.c -o hello_no_libc $ ./hello_no_libc $ wc -c ./hello_no_libc 1613 ./hello_no_libc
無事にコンパイルが通り実行できるようになった。冒頭でコンパイルしたHello worldの8561byteに比べ、libcがない分、1613byteとサイズが小さくなったことも確認できる。
Unbreakable Enterprise Product Activationをz3pyを使って解いてみる
はじめに
z3pyにkatagaitai勉強会#6 - 関西|easyで入門したので、練習がてらGoogle CTF 2016のUnbreakable Enterprise Product Activationを解いた。 この問題を人力で解くのは難しく、競技期間中は解けなかったが、z3pyを使えば楽に解くことができた。
実行してみる
$ ./unbreakable-enterprise-product-activation unbreakable-enterprise-product-activation: ./unbreakable_enterprise_product_activation product-key $ ./unbreakable-enterprise-product-activation aaaa Product activation failure 255
正しいproduct-keyがflagになりそうと分かる。
静的解析
0x4005aaから0x4005b8までの処理では入力したproduct-keyをコピーしている。0x4005bfから0x40071bまでつづくcallによって、0x4027f0から0x403440にproduct-keyをチェックする処理を呼んでいる。
4005aa mov rsi, qword [ds:rsi+8] ; src address 4005ae mov edx, 0x43 ; size 4005b3 mov edi, 0x6042c0 ; dist address 4005b8 call j_strncpy 4005bd xor eax, eax 4005bf call 0x4027f0 4005c4 xor eax, eax 4005c6 call 0x402830 4005cb xor eax, eax 4005cd call 0x402870 4005d2 xor eax, eax 4005d4 call 0x4028c0
以下のようなproduct-keyをチェックする処理が50個ほどあり、人力で解くのは難しいと分かる。
4027f0 movzx edx, byte [ds:0x6042e6] ; XREF=sub_400590+47 4027f7 movzx eax, byte [ds:0x6042de] 4027fe movzx ecx, byte [ds:0x6042c6] 402805 movzx esi, byte [ds:0x6042c8] 40280c movzx edi, byte [ds:0x6042c0] 402813 xor eax, edx 402815 sub eax, esi 402817 add eax, ecx 402819 cmp dil, al
また、strcpyのコピー先でXREFがついているアドレスは0x6042c0から0x6042f2だったので、flagの長さは51だと分かった。
6042c0 db 0x00 ; '.' ; XREF=sub_400590+35, sub_400870+9, sub_400870+30, sub_400870+2932, sub_400870+4029, sub_400870+4644, sub_400870+4934, sub_400870+6356, sub_400870+7477, sub_400870+8092, sub_400870+9189, … 6042c1 db 0x00 ; '.' ; XREF=sub_400870+47, sub_400870+65, sub_400870+2954, sub_400870+3889, sub_400870+4666, sub_400870+5244, sub_400870+6378, sub_400870+6980, sub_400870+8156, sub_400870+8528, sub_400870+9902, … 6042c2 db 0x00 ; '.' ; XREF=sub_400870+83, sub_400870+103, sub_400870+2977, sub_400870+4007, sub_400870+4651, sub_400870+4689, sub_400870+6401, sub_400870+6527, sub_400870+8227, sub_400870+9088 6042c3 db 0x00 ; '.' ; XREF=sub_400870+122, sub_400870+141, sub_400870+3001, sub_400870+3675, sub_400870+4713, sub_400870+5824, sub_400870+6425, sub_400870+7099, sub_400870+8213, sub_400870+8307, sub_400870+9182, … 6042c4 db 0x00 ; '.' ; XREF=sub_400870+161, sub_400870+179, sub_400870+3026, sub_400870+3034, sub_400870+4738, sub_400870+5100, sub_400870+6450, sub_400870+6598, sub_400870+8366, sub_400870+8414, sub_400870+8421, … 6042c5 db 0x00 ; '.' ; XREF=sub_400870+197, sub_400870+215, sub_400870+3050, sub_400870+4074, sub_400870+4762, sub_400870+5078, sub_400870+6474, sub_400870+6956, sub_400870+8428, sub_400870+8592, sub_400870+8599, … ...
z3pyを使う
コード
#!/usr/bin/env python2.7 # coding: UTF-8 from z3 import * FLAG_LENGTH = 0x6042f2 - 0x6042c0 + 1 s = Solver() text = [] for i in xrange(FLAG_LENGTH): text.append(BitVec(i, 8)) s.add(And(text[i] >= 0x20, text[i] < 0x7f)) # in printable ascii s.add(text[0] == text[6] + (text[38] ^ text[30]) - text[8]) s.add(text[1] == (text[42] ^ (text[38] ^ text[20] ^ text[19]))) s.add(text[2] == text[35] + text[36] - text[19] - text[3] - text[44]) s.add(text[3] == text[19] + (text[17] ^ (text[41] - text[10] - text[10]))) s.add(text[4] == text[33] - text[21]) s.add(text[5] == (text[4] ^ (text[4] ^ text[8] ^ text[39]))) s.add(text[6] == (text[14] ^ (text[10] + text[25] - text[39]))) s.add(text[7] == text[32] + (text[15] ^ text[1])) s.add(text[8] == text[8]) s.add(text[9] == (text[24] ^ text[7])) s.add(text[10] == text[32] + (text[49] ^ text[17]) - text[4]) s.add(text[11] == (text[42] ^ text[38]) - text[17] - text[8]) s.add(text[12] == text[14] + text[8]) s.add(text[13] == text[45] + text[20]) s.add(text[14] == text[9] + (text[20] ^ (text[25] - text[48]))) s.add(text[15] == text[18] - text[31]) s.add(text[16] == (text[24] ^ text[46])) s.add(text[17] == ((text[13] + text[2] + text[47]) ^ (text[14] ^ text[50]))) s.add(text[18] == text[0] + text[36] + text[44] - text[3]) s.add(text[19] == (text[41] ^ text[30]) - text[25] - text[28]) s.add(text[20] == (text[25] ^ text[44])) s.add(text[21] == text[25] + ((text[28] + text[22]) ^ (text[39] ^ text[21]))) s.add(text[22] == (text[31] ^ (text[44] - text[4] - text[12])) - text[30]) s.add(text[23] == (text[39] ^ (text[32] - text[14]))) s.add(text[24] == (text[21] ^ (text[0] ^ text[18] ^ text[21]))) s.add(text[25] == text[18] + text[4] + (text[12] ^ text[17]) - text[11]) s.add(text[26] == (text[32] ^ text[46]) + text[49] + text[20]) s.add(text[27] == text[36] + text[25] + text[39] - text[48]) s.add(text[28] == (text[14] ^ text[15])) s.add(text[29] == text[1] + text[35] - text[42]) s.add(text[30] == text[8] - text[31] - text[30] - text[24]) s.add(text[31] == (text[42] ^ (text[15] + text[18] - text[29]))) s.add(text[32] == text[14] + text[5] + text[15] - text[44]) s.add(text[33] == (text[20] ^ (text[45] - text[15])) - text[32]) s.add(text[34] == (text[3] ^ text[33]) - text[20] - text[10]) s.add(text[35] == (text[44] ^ (text[6] - text[43])) + text[1] - text[44]) s.add(text[36] == (text[49] ^ (text[31] + text[25] - text[28]))) s.add(text[37] == text[11] + (text[34] ^ text[31]) - text[34]) s.add(text[38] == text[42] + (text[27] ^ text[36]) - text[5]) s.add(text[39] == (text[37] ^ text[8])) s.add(text[40] == (text[44] ^ (text[7] + text[28])) - text[10]) s.add(text[41] == (text[20] ^ (text[7] ^ text[17] ^ text[26]))) s.add(text[42] == text[50] + text[1] - text[28]) s.add(text[43] == text[46] + text[33] - text[15]) s.add(text[44] == ((text[24] + text[42] + text[16]) ^ (text[45] ^ text[21]))) s.add(text[45] == text[22] - text[40]) s.add(text[46] == text[12] - text[46] - text[7] - text[35]) s.add(text[47] == (text[39] ^ (text[15] + text[26])) - text[12]) s.add(text[48] == (text[11] ^ (text[15] - text[8]))) s.add(text[49] == (text[27] ^ text[37])) s.add(text[50] == ((text[13] + text[8] + text[17]) ^ (text[24] ^ text[15]))) if s.check() == sat: m = s.model() flag = '' for i in xrange(FLAG_LENGTH): flag += chr(int(str(m[text[i]]))) print('RESULT : {0}'.format(flag))
結果
$ python unbrekable.py RESULT : CTF{0The1Quick2Brown3Fox4Jumped5Over6The7Lazy8Fox9} $ ./unbreakable-enterprise-product-activation CTF{0The1Quick2Brown3Fox4Jumped5Over6The7Lazy8Fox9} Thank you - product activated!
参考記事
IoTセキュリティハッキングコンテスト神戸で大洗女子学園は廃校になりました
IoTセキュリティハッキングコンテスト神戸2016 | Kobe Digital Labo 神戸デジタル・ラボ に大洗女子学園を代表して参加してきました。IoTセキュリティハッキングコンテストというのは、要はIoTに関係がある問題が出るCTFですね。
様子
高校の廃校を回避するため、選択科目であるCTF道を履修している私たちは今日のCTFで入賞することを強要されています
— 友利たけまる (@tkmru) 2016年6月17日
廃校を阻止します pic.twitter.com/rYfQipWo8Y
— 友利たけまる (@tkmru) 2016年6月18日
はやく戦車の応援きてほしい
— 友利たけまる (@tkmru) 2016年6月18日
大洗女子学園は廃校です
— 友利たけまる (@tkmru) 2016年6月18日
4位かよ💢
— 友利たけまる (@tkmru) 2016年6月18日
結果
高校の存続をかけて大洗女子学園代表として参加しましたが4位で惜しくも入賞できず、大洗女子学園は廃校になりました。今後は継続高校で精進していこうと思います。
strippedなgoのバイナリを読み解く
この前はふつーのgoのバイナリを読んだ。今回はstrippedなgoのバイナリを読んでいく。
環境
$ uname -a Linux ubuntu 3.19.0-58-generic #64~14.04.1-Ubuntu SMP Fri Mar 18 19:05:43 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.4 LTS Release: 14.04 Codename: trusty $ go version go version go1.6.2 linux/amd64
準備
バイナリを生成
"hello, world"をprintするだけのバイナリを生成する。
コード
package main import "fmt" func main() { fmt.Println("hello, world") }
シンボルをけずる
$ strip --strip-unneeded ./print_test
これでstripped binaryとなった。
バイナリを読んでいく
goのバイナリをちょっとまじめに読んでみる - 脱力系日記 でgoのバイナリにおいてメインの処理を行うのはmain.mainであり、これは_rt0_amd64_linux、main、runtime.rt0_go、runtime.mainを経て呼び出されると分かったので、これらを追って読んでいく。
シンボルの確認
シンボルはすべてなくなっていた。
gdb-peda$ symbol No symbol file now.
エントリポイント
エントリポイントを確認する。この環境でのgoのエントリポイントは_rt0_amd64_linuxである。
gdb-peda$ i file Symbols from "/mnt/hgfs/code/go_study/print_test". Local exec file: `/mnt/hgfs/code/go_study/print_test', file type elf64-x86-64. Entry point: 0x4562a0 0x0000000000401000 - 0x00000000004aa630 is .text 0x00000000004ab000 - 0x000000000052d7df is .rodata 0x000000000052d7e0 - 0x000000000052f898 is .typelink 0x000000000052f898 - 0x000000000052f898 is .gosymtab 0x000000000052f8a0 - 0x000000000057fb0b is .gopclntab 0x0000000000580000 - 0x0000000000581ce8 is .noptrdata 0x0000000000581d00 - 0x0000000000584250 is .data 0x0000000000584260 - 0x000000000059eab8 is .bss 0x000000000059eac0 - 0x00000000005a38c0 is .noptrbss 0x0000000000400fc8 - 0x0000000000401000 is .note.go.buildid gdb-peda$ x/10i 0x4562a0 => 0x4562a0: lea rsi,[rsp+0x8] 0x4562a5: mov rdi,QWORD PTR [rsp] 0x4562a9: lea rax,[rip+0x10] # 0x4562c0 0x4562b0: jmp rax 0x4562b2: int3 0x4562b3: int3 0x4562b4: int3 0x4562b5: int3 0x4562b6: int3 0x4562b7: int3
エントリポイントでは0x4562c0に飛ばしている。これは main関数 のアドレスである。
main
gdb-peda$ x/5i 0x4562c0 0x4562c0: lea rax,[rip+0xffffffffffffcb19] # 0x452de0 0x4562c7: jmp rax 0x4562c9: int3 0x4562ca: int3 0x4562cb: int3
main関数では0x452de0に飛ばしている。これは runtime.rt0_go のアドレスである。
runtime.rt0_go
この関数はメインの処理を行う runtime.main へ飛ばす役割を持っている。シンボルが削られていることもあり、まともに読むと死ぬので、ret 付近を見る。
gdb-peda$ x/100i 0x452de0 ... 0x452f12: lea rax,[rip+0xd80ff] # 0x52b018 0x452f19: push rax 0x452f1a: push 0x0 0x452f1c: call 0x430b30 0x452f21: pop rax 0x452f22: pop rax 0x452f23: call 0x42c7b0 0x452f28: mov DWORD PTR ds:0xf1,0xf1 0x452f33: ret
rax にアドレスの値を代入したあとpushして関数をcallしている。このraxにいれられた0x52b018が指す値がruntime.mainのアドレスのようだ。
gdb-peda$ x/4x 0x52b018 0x52b018: 0x00 0x98 0x42 0x00
0x429800がruntime.mainのアドレスとなっていた。
runtime.main
長いのでまともに読むのはめんどくさい。処理系のコードを見ると、runtime.mainのmain.mainを呼び出す場所は以下のようになっていた。
if isarchive || islibrary { // A program compiled with -buildmode=c-archive or c-shared // has a main, but it is not executed. return } main_main() if raceenabled { racefini() } // Make racy client program work: if panicking on // another goroutine at the same time as main returns, // let the other goroutine finish printing the panic trace. // Once it does, it will exit. See issue 3934. if panicking != 0 { gopark(nil, nil, "panicwait", traceEvGoStop, 1) } exit(0) for { var x *int32 *x = 0 } }
これを見ながら、ret より逆に命令を読んでいくとそれらしい命令が見つかった。0x429aabでmain.mainをcallしているようだ。0x40100がmain.mainのアドレスであった。
$ x/200i 0x429800 0x429aab: call 0x401000 # call main.main 0x429ab0: mov ebx,DWORD PTR [rip+0x17505e] # 0x59eb14 0x429ab6: cmp ebx,0x0 0x429ab9: je 0x429af4 0x429abb: mov QWORD PTR [rsp],0x0 0x429ac3: mov QWORD PTR [rsp+0x8],0x0 0x429acc: lea rbx,[rip+0xd80cd] # 0x501ba0 0x429ad3: mov QWORD PTR [rsp+0x10],rbx 0x429ad8: mov QWORD PTR [rsp+0x18],0x9 0x429ae1: mov BYTE PTR [rsp+0x20],0x10 0x429ae6: mov QWORD PTR [rsp+0x28],0x1 0x429aef: call 0x429d20 0x429af4: mov DWORD PTR [rsp],0x0 0x429afb: call 0x4562d0 0x429b00: xor eax,eax 0x429b02: mov DWORD PTR [rax],0x0 0x429b08: jmp 0x429b00 0x429b0a: nop 0x429b0b: call 0x427820 0x429b10: add rsp,0x48 0x429b14: ret
main.main
シンボルがないため、気合で読んでいく必要があるが、冒頭では初期化処理が行われているため、retから下から上に処理を見ていく方がよい。今回はretから2つ目の命令で fmt.Println を読んでいた。
gdb-peda$ x/100i 0x401000 0x401000: mov rcx,QWORD PTR fs:0xfffffffffffffff8 0x401009: cmp rsp,QWORD PTR [rcx+0x10] 0x40100d: jbe 0x4010ec 0x401013: sub rsp,0x78 0x401017: lea rbx,[rip+0x100252] # 0x501270 0x40101e: mov QWORD PTR [rsp+0x50],rbx 0x401023: mov QWORD PTR [rsp+0x58],0xc 0x40102c: xor ebx,ebx 0x40102e: mov QWORD PTR [rsp+0x40],rbx 0x401033: mov QWORD PTR [rsp+0x48],rbx 0x401038: lea rbx,[rsp+0x40] 0x40103d: cmp rbx,0x0 0x401041: je 0x4010e5 0x401047: mov QWORD PTR [rsp+0x68],0x1 0x401050: mov QWORD PTR [rsp+0x70],0x1 0x401059: mov QWORD PTR [rsp+0x60],rbx 0x40105e: lea rbx,[rip+0xb7d9b] # 0x4b8e00 0x401065: mov QWORD PTR [rsp],rbx 0x401069: lea rbx,[rsp+0x50] 0x40106e: mov QWORD PTR [rsp+0x8],rbx 0x401073: mov QWORD PTR [rsp+0x10],0x0 0x40107c: call 0x40b9f0 0x401081: mov rcx,QWORD PTR [rsp+0x18] 0x401086: mov rax,QWORD PTR [rsp+0x20] 0x40108b: mov rbx,QWORD PTR [rsp+0x60] 0x401090: mov QWORD PTR [rsp+0x30],rcx 0x401095: mov QWORD PTR [rbx],rcx 0x401098: mov QWORD PTR [rsp+0x38],rax 0x40109d: cmp BYTE PTR [rip+0x19da3c],0x0 # 0x59eae0 0x4010a4: jne 0x4010d1 0x4010a6: mov QWORD PTR [rbx+0x8],rax 0x4010aa: mov rbx,QWORD PTR [rsp+0x60] 0x4010af: mov QWORD PTR [rsp],rbx 0x4010b3: mov rbx,QWORD PTR [rsp+0x68] 0x4010b8: mov QWORD PTR [rsp+0x8],rbx 0x4010bd: mov rbx,QWORD PTR [rsp+0x70] 0x4010c2: mov QWORD PTR [rsp+0x10],rbx 0x4010c7: call 0x45a680 # call fmt.Println 0x4010cc: add rsp,0x78 0x4010d0: ret 0x4010d1: lea r8,[rbx+0x8] 0x4010d5: mov QWORD PTR [rsp],r8 0x4010d9: mov QWORD PTR [rsp+0x8],rax 0x4010de: call 0x40eef0 0x4010e3: jmp 0x4010aa 0x4010e5: mov DWORD PTR [rbx],eax 0x4010e7: jmp 0x401047 0x4010ec: call 0x4531f0 0x4010f1: jmp 0x401000
まとめ
strippedなgoのバイナリの読み解き方を確認した。メインの処理が呼ばれるまでの流れを把握していれば読める。
リンカがカスタムされてないかぎり、0x401000がmain.mainが配置されるアドレスとなるので、最初にこのアドレスを見にいって、違っていたらエントリポイントから処理を追っていくというアプローチがよさそうである。
医療セキュリティハッキングコンテスト神戸2016に行ってきた
医療セキュリティハッキングコンテストとは
実際に運用されている医療系ソフトウェアを合法的に叩き潰せて、賞金も出る最高のイベントです。学校の先輩達とチームで出場してました。
結果
3位入賞しました。
感想
野生の脆弱性を見つけたことがなく、参加前は不安だったのですが、蓋を開けてみると攻略対象は非常に雑なアプリケーションで自明な脆弱性が数多くあり安心?して取り組めました。
また、脆弱性を報告した経験もなかったので、報告文の書き方や脆弱性の分類の仕方など勉強になりました。雑に報告した脆弱性が、3つの脆弱性に小分けされて3回ポイントが得られたときは最高でしたが、今度はちゃんと自分で分類して報告したいと思います。
命に関わることもありそうな病院のシステムはもっと堅牢だと思っていたのですが、内部のネットワーク、人間からしか使用されないと思っているのか、CTFよりも攻略が非常に簡単な脆弱なシステムが運用されていることが分かり、もう病院にはいけないなと思いました。
前の週はdefconでブチギレていたので、いいストレス発散になって良かったですね。
golangのバイナリをちょっとまじめに読んでみる
golangのバイナリをちょっとまじめに読んだのでメモ。runtimeのコードと合わせて、バイナリがどういう構造になっているのか正面から読んだ。
環境
$ uname -a Linux ubuntu 3.19.0-58-generic #64~14.04.1-Ubuntu SMP Fri Mar 18 19:05:43 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.4 LTS Release: 14.04 Codename: trusty $ go version go version go1.6.2 linux/amd64
準備
バイナリを生成
"hello, world"をprintするだけのバイナリを生成する。
コード
package main import "fmt" func main() { fmt.Println("hello, world") }
バイナリを読んでいく
エントリポイント
まずは、gdbでエントリポイントを確認する。
gdb-peda$ i file Symbols from "/mnt/hgfs/code/go_study/print_test". Local exec file: `/mnt/hgfs/code/go_study/print_test', file type elf64-x86-64. Entry point: 0x4562a0 0x0000000000401000 - 0x00000000004aa630 is .text 0x00000000004ab000 - 0x000000000052d7df is .rodata 0x000000000052d7e0 - 0x000000000052f898 is .typelink 0x000000000052f898 - 0x000000000052f898 is .gosymtab 0x000000000052f8a0 - 0x000000000057fb0b is .gopclntab 0x0000000000580000 - 0x0000000000581ce8 is .noptrdata 0x0000000000581d00 - 0x0000000000584250 is .data 0x0000000000584260 - 0x000000000059eab8 is .bss 0x000000000059eac0 - 0x00000000005a38c0 is .noptrbss 0x0000000000400fc8 - 0x0000000000401000 is .note.go.buildid gdb-peda$ i symbol 0x4562a0 _rt0_amd64_linux in section .text
エントリポイントは0x4562a0の_rt0_amd64_linux だと分かる。
つづいて_rt0_amd64_linuxの中身を見る。
gdb-peda$ disas _rt0_amd64_linux Dump of assembler code for function _rt0_amd64_linux: 0x00000000004562a0 <+0>: lea rsi,[rsp+0x8] 0x00000000004562a5 <+5>: mov rdi,QWORD PTR [rsp] 0x00000000004562a9 <+9>: lea rax,[rip+0x10] # 0x4562c0 <main> 0x00000000004562b0 <+16>: jmp rax 0x00000000004562b2 <+18>: int3 0x00000000004562b3 <+19>: int3 0x00000000004562b4 <+20>: int3 0x00000000004562b5 <+21>: int3 0x00000000004562b6 <+22>: int3 0x00000000004562b7 <+23>: int3 0x00000000004562b8 <+24>: int3 0x00000000004562b9 <+25>: int3 0x00000000004562ba <+26>: int3 0x00000000004562bb <+27>: int3 0x00000000004562bc <+28>: int3 0x00000000004562bd <+29>: int3 0x00000000004562be <+30>: int3 0x00000000004562bf <+31>: int3 End of assembler dump.
main関数に飛ばすだけだった。
lib.go でアーキテクチャからエントリポイントに置く関数を決定したあと、この場合だと rt0_linux_amd64.s に書かれた関数の処理内容を、elf.go でELFのエントリポイントに書き込むようだ。
main関数
つづいてmain関数を読んでいく。
gdb-peda$ disas main Dump of assembler code for function main: 0x00000000004562c0 <+0>: lea rax,[rip+0xffffffffffffcb19] # 0x452de0 <runtime.rt0_go> 0x00000000004562c7 <+7>: jmp rax 0x00000000004562c9 <+9>: int3 0x00000000004562ca <+10>: int3 0x00000000004562cb <+11>: int3 0x00000000004562cc <+12>: int3 0x00000000004562cd <+13>: int3 0x00000000004562ce <+14>: int3 0x00000000004562cf <+15>: int3 End of assembler dump.
main関数はruntime.rt0_goに飛ばすだけで、ここで文字列の出力等のメインの処理を行っているわけではないようだ。
これは rt0_linux_amd64.s で定義されていた。
runtime.rt0_go
runtime.rt0_goを読む。長いので4つに分けて読んでいく。
なお、この関数は asm_amd64.s で定義されていた。
gdb-peda$ disas 0x452de0 Dump of assembler code for function runtime.rt0_go: 0x0000000000452de0 <+0>: mov rax,rdi 0x0000000000452de3 <+3>: mov rbx,rsi 0x0000000000452de6 <+6>: sub rsp,0x27 0x0000000000452dea <+10>: and rsp,0xfffffffffffffff0 0x0000000000452dee <+14>: mov QWORD PTR [rsp+0x10],rax 0x0000000000452df3 <+19>: mov QWORD PTR [rsp+0x18],rbx 0x0000000000452df8 <+24>: lea rdi,[rip+0x131d61] # 0x584b60 <runtime.g0> 0x0000000000452dff <+31>: lea rbx,[rsp-0xff98] 0x0000000000452e07 <+39>: mov QWORD PTR [rdi+0x10],rbx 0x0000000000452e0b <+43>: mov QWORD PTR [rdi+0x18],rbx 0x0000000000452e0f <+47>: mov QWORD PTR [rdi],rbx 0x0000000000452e12 <+50>: mov QWORD PTR [rdi+0x8],rsp 0x0000000000452e16 <+54>: xor eax,eax 0x0000000000452e18 <+56>: cpuid 0x0000000000452e1a <+58>: cmp rax,0x0 0x0000000000452e1e <+62>: je 0x452e9a <runtime.rt0_go+186> 0x0000000000452e20 <+64>: cmp ebx,0x756e6547 0x0000000000452e26 <+70>: jne 0x452e3f <runtime.rt0_go+95> 0x0000000000452e28 <+72>: cmp edx,0x49656e69 0x0000000000452e2e <+78>: jne 0x452e3f <runtime.rt0_go+95> 0x0000000000452e30 <+80>: cmp ecx,0x6c65746e 0x0000000000452e36 <+86>: jne 0x452e3f <runtime.rt0_go+95> 0x0000000000452e38 <+88>: mov BYTE PTR [rip+0x14bc95],0x1 # 0x59ead4 <runtime.lfenceBeforeRdtsc> 0x0000000000452e3f <+95>: mov rax,0x1 0x0000000000452e46 <+102>: cpuid 0x0000000000452e48 <+104>: mov DWORD PTR [rip+0x14bc9a],ecx # 0x59eae8 <runtime.cpuid_ecx> 0x0000000000452e4e <+110>: mov DWORD PTR [rip+0x14bc98],edx # 0x59eaec <runtime.cpuid_edx> 0x0000000000452e54 <+116>: and ecx,0x18000000 0x0000000000452e5a <+122>: cmp ecx,0x18000000 0x0000000000452e60 <+128>: jne 0x452f78 <runtime.rt0_go+408> 0x0000000000452e66 <+134>: xor ecx,ecx 0x0000000000452e68 <+136>: xgetbv 0x0000000000452e6b <+139>: and eax,0x6 0x0000000000452e6e <+142>: cmp eax,0x6 0x0000000000452e71 <+145>: jne 0x452f78 <runtime.rt0_go+408> 0x0000000000452e77 <+151>: mov BYTE PTR [rip+0x14bc58],0x1 # 0x59ead6 <runtime.support_avx> 0x0000000000452e7e <+158>: mov eax,0x7 0x0000000000452e83 <+163>: xor ecx,ecx 0x0000000000452e85 <+165>: cpuid 0x0000000000452e87 <+167>: and ebx,0x20 0x0000000000452e8a <+170>: cmp ebx,0x20 0x0000000000452e8d <+173>: jne 0x452f6c <runtime.rt0_go+396> 0x0000000000452e93 <+179>: mov BYTE PTR [rip+0x14bc3d],0x1 # 0x59ead7 <runtime.support_avx2>
0x452e18から0x452e93まででは、Intel CPUを使用しているか確認し、それがAVX、AVX2拡張命令セットをサポートしているCPUであればそれに対応するための処理を実行しているようだ。
0x0000000000452e9a <+186>: mov rax,QWORD PTR [rip+0x1313c7] # 0x584268 <_cgo_init> 0x0000000000452ea1 <+193>: test rax,rax 0x0000000000452ea4 <+196>: je 0x452f34 <runtime.rt0_go+340> 0x0000000000452eaa <+202>: mov rcx,rdi 0x0000000000452ead <+205>: lea rsi,[rip+0x1dbc] # 0x454c70 <setg_gcc> 0x0000000000452eb4 <+212>: call rax 0x0000000000452eb6 <+214>: lea rcx,[rip+0x131ca3] # 0x584b60 <runtime.g0> 0x0000000000452ebd <+221>: mov rax,QWORD PTR [rcx] 0x0000000000452ec0 <+224>: add rax,0x2d0 0x0000000000452ec6 <+230>: mov QWORD PTR [rcx+0x10],rax 0x0000000000452eca <+234>: mov QWORD PTR [rcx+0x18],rax
0x452e9aから0x452ea4までで、_cgo_initとシンボルが付いているアドレスの値がゼロか確認している。goには、cgoというc言語のコードを呼べるようにする機能があり、それが有効であった場合のみ、cgoを有効にするための処理をするようだ。今回、cgoは使っていないので 0x452f34にジャンプする。0x452eb6から0x452ecaまでではStackGuardの更新をしている。
0x0000000000452f34 <+340>: lea rdi,[rip+0x131fc5] # 0x584f00 <runtime.m0+96> 0x0000000000452f3b <+347>: call 0x456790 <runtime.settls> 0x0000000000452f40 <+352>: mov QWORD PTR fs:0xfffffffffffffff8,0x123 0x0000000000452f4d <+365>: mov rax,QWORD PTR [rip+0x131fac] # 0x584f00 <runtime.m0+96> 0x0000000000452f54 <+372>: cmp rax,0x123 0x0000000000452f5a <+378>: je 0x452ece <runtime.rt0_go+238> 0x0000000000452f60 <+384>: mov DWORD PTR ds:0x0,eax 0x0000000000452f67 <+391>: jmp 0x452ece <runtime.rt0_go+238> 0x0000000000452f6c <+396>: mov BYTE PTR [rip+0x14bb64],0x0 # 0x59ead7 <runtime.support_avx2> 0x0000000000452f73 <+403>: jmp 0x452e9a <runtime.rt0_go+186> 0x0000000000452f78 <+408>: mov BYTE PTR [rip+0x14bb57],0x0 # 0x59ead6 <runtime.support_avx> 0x0000000000452f7f <+415>: jmp 0x452f6c <runtime.rt0_go+396> 0x0000000000452f81 <+417>: int3 0x0000000000452f82 <+418>: int3 0x0000000000452f83 <+419>: int3 0x0000000000452f84 <+420>: int3 0x0000000000452f85 <+421>: int3 0x0000000000452f86 <+422>: int3 0x0000000000452f87 <+423>: int3 0x0000000000452f88 <+424>: int3 0x0000000000452f89 <+425>: int3 0x0000000000452f8a <+426>: int3 0x0000000000452f8b <+427>: int3 0x0000000000452f8c <+428>: int3 0x0000000000452f8d <+429>: int3 0x0000000000452f8e <+430>: int3 0x0000000000452f8f <+431>: int3 End of assembler dump.
0x452f3bでruntime.settlsという関数を呼び、TLSのセットアップをしている。TLS (Thread Local Storage)は他のスレッドからは参照されない、スレッド固有のデータを保持できるようにするものである。この関数は sys_linux_amd64.s で定義されていた。
TLSのセットアップが終われば0x452eceにジャンプする。
0x0000000000452ece <+238>: lea rcx,[rip+0x131c8b] # 0x584b60 <runtime.g0> 0x0000000000452ed5 <+245>: mov QWORD PTR fs:0xfffffffffffffff8,rcx 0x0000000000452ede <+254>: lea rax,[rip+0x131fbb] # 0x584ea0 <runtime.m0> 0x0000000000452ee5 <+261>: mov QWORD PTR [rax],rcx 0x0000000000452ee8 <+264>: mov QWORD PTR [rcx+0x30],rax 0x0000000000452eec <+268>: cld 0x0000000000452eed <+269>: call 0x4364c0 <runtime.check> 0x0000000000452ef2 <+274>: mov eax,DWORD PTR [rsp+0x10] 0x0000000000452ef6 <+278>: mov DWORD PTR [rsp],eax 0x0000000000452ef9 <+281>: mov rax,QWORD PTR [rsp+0x18] 0x0000000000452efe <+286>: mov QWORD PTR [rsp+0x8],rax 0x0000000000452f03 <+291>: call 0x435e20 <runtime.args> 0x0000000000452f08 <+296>: call 0x425770 <runtime.osinit> 0x0000000000452f0d <+301>: call 0x42ab00 <runtime.schedinit> 0x0000000000452f12 <+306>: lea rax,[rip+0xd80ff] # 0x52b018 <runtime.mainPC> 0x0000000000452f19 <+313>: push rax 0x0000000000452f1a <+314>: push 0x0 0x0000000000452f1c <+316>: call 0x430b30 <runtime.newproc> 0x0000000000452f21 <+321>: pop rax 0x0000000000452f22 <+322>: pop rax 0x0000000000452f23 <+323>: call 0x42c7b0 <runtime.mstart> 0x0000000000452f28 <+328>: mov DWORD PTR ds:0xf1,0xf1 0x0000000000452f33 <+339>: ret
0x452eedでruntime.checkという関数を呼んでいる。これは型のサイズ等を確認する。この関数は runtime1.go で定義されていた。
0x452f03でruntime.argsという関数を呼んでいる。ここではコマンドライン引数とその個数を保存している。この関数も runtime1.go で定義されていた。
0x452f08から0x452f23ではメインの処理を行うgoroutineをスタートする処理を行っている。
0x452f08でruntime.osinitという関数を呼んでいる。ncpuという変数にCPUのコア数を入れている。
os1_linux.go で定義されていた。
0x452f0dでruntime.schedinitという関数を呼んでいる。proc.go で定義されていた。
これはスケジューリングを行い、race conditionを防ぐ。
0x452f1cでruntime.newprocを呼ぶことで、新しいgoroutineを作り、0x452f23でruntime.mstartを呼ぶことで、goroutineを開始している。どんなgoroutineを開始したのか。
0x452f12でruntime.mainPCというシンボルのついたメモリアドレスが指す値をlea命令によってraxに入れているのが気になる。
runtime.mainPCをgdbで確認するとruntime.mainという関数へのポインタになっていた。
gdb-peda$ x/x 0x52b018 0x52b018 <runtime.mainPC>: 0x0000000000429800 gdb-peda$ i symbol 0x429800 runtime.main in section .text of /mnt/hgfs/code/go_study/print_test
つづく0x452f19でこのポインタをpushしている。runtime.newproc、runtime.mstartで使うためのようだ。runtime.main関数がgoroutineとしてスタートされるようだ。
runtime.newprocは proc.go で、runtime.mstartは proc.go で定義されていた。
runtime.rt0_goがCPUの確認やcgoを使う準備、runtime.mainの起動を行う関数だと分かった。
runtime.main
この関数は長いのでmain.initとmain.mainという関数を呼んでいることに着目し、これらを読んでいく。
gdb-peda$ disas 0x429800 Dump of assembler code for function runtime.main: ... 0x0000000000429a7a <+634>: call 0x401100 <main.init> ... 0x0000000000429aab <+683>: call 0x401000 <main.main>
main.init
gdb-peda$ disas 0x401100 Dump of assembler code for function main.init: 0x0000000000401100 <+0>: mov rcx,QWORD PTR fs:0xfffffffffffffff8 0x0000000000401109 <+9>: cmp rsp,QWORD PTR [rcx+0x10] 0x000000000040110d <+13>: jbe 0x401143 <main.init+67> 0x000000000040110f <+15>: movzx ebx,BYTE PTR [rip+0x19d9ac] # 0x59eac2 <main.initdone.> 0x0000000000401116 <+22>: cmp bl,0x0 0x0000000000401119 <+25>: je 0x40112f <main.init+47> 0x000000000040111b <+27>: movzx ebx,BYTE PTR [rip+0x19d9a0] # 0x59eac2 <main.initdone.> 0x0000000000401122 <+34>: cmp bl,0x2 0x0000000000401125 <+37>: jne 0x401128 <main.init+40> 0x0000000000401127 <+39>: ret 0x0000000000401128 <+40>: call 0x426660 <runtime.throwinit> 0x000000000040112d <+45>: ud2 0x000000000040112f <+47>: mov BYTE PTR [rip+0x19d98c],0x1 # 0x59eac2 <main.initdone.> 0x0000000000401136 <+54>: call 0x466f70 <fmt.init> 0x000000000040113b <+59>: mov BYTE PTR [rip+0x19d980],0x2 # 0x59eac2 <main.initdone.> 0x0000000000401142 <+66>: ret 0x0000000000401143 <+67>: call 0x4531f0 <runtime.morestack_noctxt> 0x0000000000401148 <+72>: jmp 0x401100 <main.init> 0x000000000040114a <+74>: int3 0x000000000040114b <+75>: int3 0x000000000040114c <+76>: int3 0x000000000040114d <+77>: int3 0x000000000040114e <+78>: int3 0x000000000040114f <+79>: int3 End of assembler dump.
0x401136でfmt.init関数を呼び、importしたfmtパッケージの初期化を行っている。main.initはパッケージの初期化を行う関数のようだ。
main.main
$ disas 0x401000 Dump of assembler code for function main.main: 0x0000000000401000 <+0>: mov rcx,QWORD PTR fs:0xfffffffffffffff8 0x0000000000401009 <+9>: cmp rsp,QWORD PTR [rcx+0x10] 0x000000000040100d <+13>: jbe 0x4010ec <main.main+236> 0x0000000000401013 <+19>: sub rsp,0x78 0x0000000000401017 <+23>: lea rbx,[rip+0x100252] # 0x501270 0x000000000040101e <+30>: mov QWORD PTR [rsp+0x50],rbx 0x0000000000401023 <+35>: mov QWORD PTR [rsp+0x58],0xc 0x000000000040102c <+44>: xor ebx,ebx 0x000000000040102e <+46>: mov QWORD PTR [rsp+0x40],rbx 0x0000000000401033 <+51>: mov QWORD PTR [rsp+0x48],rbx 0x0000000000401038 <+56>: lea rbx,[rsp+0x40] 0x000000000040103d <+61>: cmp rbx,0x0 0x0000000000401041 <+65>: je 0x4010e5 <main.main+229> 0x0000000000401047 <+71>: mov QWORD PTR [rsp+0x68],0x1 0x0000000000401050 <+80>: mov QWORD PTR [rsp+0x70],0x1 0x0000000000401059 <+89>: mov QWORD PTR [rsp+0x60],rbx 0x000000000040105e <+94>: lea rbx,[rip+0xb7d9b] # 0x4b8e00 0x0000000000401065 <+101>: mov QWORD PTR [rsp],rbx 0x0000000000401069 <+105>: lea rbx,[rsp+0x50] 0x000000000040106e <+110>: mov QWORD PTR [rsp+0x8],rbx 0x0000000000401073 <+115>: mov QWORD PTR [rsp+0x10],0x0 0x000000000040107c <+124>: call 0x40b9f0 <runtime.convT2E> 0x0000000000401081 <+129>: mov rcx,QWORD PTR [rsp+0x18] 0x0000000000401086 <+134>: mov rax,QWORD PTR [rsp+0x20] 0x000000000040108b <+139>: mov rbx,QWORD PTR [rsp+0x60] 0x0000000000401090 <+144>: mov QWORD PTR [rsp+0x30],rcx 0x0000000000401095 <+149>: mov QWORD PTR [rbx],rcx 0x0000000000401098 <+152>: mov QWORD PTR [rsp+0x38],rax 0x000000000040109d <+157>: cmp BYTE PTR [rip+0x19da3c],0x0 # 0x59eae0 <runtime.writeBarrier> 0x00000000004010a4 <+164>: jne 0x4010d1 <main.main+209> 0x00000000004010a6 <+166>: mov QWORD PTR [rbx+0x8],rax 0x00000000004010aa <+170>: mov rbx,QWORD PTR [rsp+0x60] 0x00000000004010af <+175>: mov QWORD PTR [rsp],rbx 0x00000000004010b3 <+179>: mov rbx,QWORD PTR [rsp+0x68] 0x00000000004010b8 <+184>: mov QWORD PTR [rsp+0x8],rbx 0x00000000004010bd <+189>: mov rbx,QWORD PTR [rsp+0x70] 0x00000000004010c2 <+194>: mov QWORD PTR [rsp+0x10],rbx 0x00000000004010c7 <+199>: call 0x45a680 <fmt.Println> 0x00000000004010cc <+204>: add rsp,0x78 0x00000000004010d0 <+208>: ret 0x00000000004010d1 <+209>: lea r8,[rbx+0x8] 0x00000000004010d5 <+213>: mov QWORD PTR [rsp],r8 0x00000000004010d9 <+217>: mov QWORD PTR [rsp+0x8],rax 0x00000000004010de <+222>: call 0x40eef0 <runtime.writebarrierptr> 0x00000000004010e3 <+227>: jmp 0x4010aa <main.main+170> 0x00000000004010e5 <+229>: mov DWORD PTR [rbx],eax 0x00000000004010e7 <+231>: jmp 0x401047 <main.main+71> 0x00000000004010ec <+236>: call 0x4531f0 <runtime.morestack_noctxt> 0x00000000004010f1 <+241>: jmp 0x401000 <main.main> 0x00000000004010f6 <+246>: int3 0x00000000004010f7 <+247>: int3 0x00000000004010f8 <+248>: int3 0x00000000004010f9 <+249>: int3 0x00000000004010fa <+250>: int3 0x00000000004010fb <+251>: int3 0x00000000004010fc <+252>: int3 0x00000000004010fd <+253>: int3 0x00000000004010fe <+254>: int3 0x00000000004010ff <+255>: int3 End of assembler dump.
0x4010c7でfmt.Println関数を呼び出し、"hello, world"を出力している。main.mainはメインとなる処理を行う、c言語のmain関数のようなものだと分かった。
まとめ
goのバイナリにおいてメインの処理を行うのはmain.mainであり、これは_rt0_amd64_linux、main、runtime.rt0_go、runtime.mainを経て呼び出されると分かった。
感想
処理系のコードと照らし合わせながら、バイナリを読んでいくことで、新たな楽しさを見つけられた。この記事がgoの開発やリバース・エンジニアリングの助けとなればうれしい。
参考資料
http://blog.matttproud.com/2015/02/exploring-gos-runtime-how-process.html
Golang Internals, Part 5: the Runtime Bootstrap Process | Altoros
Golang Internals, Part 6: Bootstrapping and Memory Allocator Initialization | Altoros
A Quick Guide to Go's Assembler - The Go Programming Language
http://www.binwang.me/2014-09-01-notes-on-go-scheduler.html
log2timelineの使い方
この記事は1年半前から下書きのままになっていたものです。
log2timelineとは
タイムライン解析というファイルシステムのタイムスタンプ情報を元に、時系列順に痕跡を解析していく手法がある。
log2timelineはそのためのフレームワークである。PlasoというPythonベースのエンジンが使われている。
使い方
入力フォーマットの一覧
$ log2timeline -f list
出力フォーマットの一覧
$ log2timeline -o list
指定できるタイムゾーンの一覧
$ log2timeline -z list
timelineを見る
log2timelineを実行する前に、The Sleuth Kitのmmlsでパーティションの開始位置を探して、イメージをmountする必要がある。
$ mmls test.dd
mmlsの結果を見て、offsetに1セクタのサイズ*パーティションの開始位置を指定する。
$ mount -o ro,noexec,show_sys_files,loop,offset=<アドレス> test.dd /mnt/hoge
-zでタイムゾーンを -fで入力フォーマットとパスを、-wで出力ファイルを指定する。ここではcsvにした。
$ log2timeline -z <タイムゾーン> -f <入力フォーマット> /mnt/hoge -w timeline.csv
作成されたcsvファイルをEXCELとかnumbersで開くと、サイズが大きくて開くのに時間がかかるので、見たい期間がある程度絞れているなら、wオプションを付けずにlog2timelineを実行してから、l2t_process -b で期間を指定し csv ファイルにするとよい。
$ log2timeline-sift -z <タイムゾーン> -p <パーティション> -i <イメージ> $ cd /cases/timeline-output-folder/ #sans siftの場合 $ l2t_process -b <イメージ>_bodyfile.txt 12-01-2012..12-31-2012 > <イメージ>_bodyfile.csv
参考資料
Sploited: Timelines continued: Log2Timeline for Beginners
SANS Digital Forensics and Incident Response Blog | Digital Forensic SIFTing - Targeted Timeline Creation and Analysis using log2timeline | SANS Institute
log2timeline-sift command missing · Issue #3 · teamdfir/sift · GitHub
v6 とかセキュリティとかおいしいものとか: log2timeline-sift の使い方