サイバーエージェントの長期インターンに行ってきた
8/16から9/21までサイバーエージェントの長期インターンに行っていたので感想を書く。
某インターン以外でも最高の夏🍉は過ごせる!!
面接
インフラかサーバサイドやりたいけど、インフラは自信ないとか言ってたら、「OpenStackって知ってる?」って聞かれて「名前しか知らないです」って言ったのでサーバーサイドの方面で選考が進むことになった。 某コンテンツ系サービスのバックエンド見たかったけど、精鋭が揃っているらしく、goを業務で書いたことがないときびしいと言われたので、某メディア系のサービスの中の人とエンジニア面接して無事にメディアサービス (エンジニア対象)に採用されることになった。goのバイナリ読んだりしてたけど、あんまり書いてなかったし、しゃあない。
エンジニア面接のあとすぐ人事の方から電話がかかってきて「Androidもできたよね!?」って聞かれて、Oss Stars~Android編~ に誘われた。 全然できないんだよなーとおもいつつも「じゃあせっかくなんで行きますー」って言って Oss Stars~Android編~ の方も行くことになった。
人事の方が異常に丁寧で、エンジニア面接に入る前にどうゆうサービスに興味あるか、嫌いなサービスはなにか、使える言語はなにか、使いたくない言語はなにかとか細かに聞いてくれてマッチングをいいかんじにしてくれて、神だった。ふつーは絶対、嫌いなサービスまで聞かないと思う。
ぼくは普通に開発してただけだけど、音波解析とかDCに納入するクソ高いNICの性能評価とかをやってる人もいて、そういうことを希望しても面白かったかなーと思う。
業務
SPAなサービスだったので、95%くらいフロントを触って、5%くらいサーバーサイドを触るみたいなかんじで業務が進んだ。 前にJSをまじめに書いたときはES5にjQueryを使って書いていたので、ES7にReact、Reduxでの開発は大変だった。また、かなり特殊な構成で、慣れるのに非常に時間かかったし、そういう意味でも大変だった。序盤は人権問題に直面していたけど、メンターが異常にやさしくて助かった。感謝。業務では人権が保証される。社内で評判の構成らしく他サービスでのフロント刷新の際、参考にされるらしい。けど、React、Reduxの他に、社員の方の自作ライブラリとか、forkしてカスタムしたライブラリとか使われてて、プライベートで真似して開発するのは難しそうだと思った。
単純に文法とかライブラリのことだけじゃなくて、Atomic DesignとかIsomorphic JavaScriptとかの設計の概念とかサーバー構成の裏事情とか負荷試験のこととか教えてもらえて勉強になった。 n十万行のコードとか触ったことがなかったので、そういう意味でもいい経験になったし、多少は自信がついたので、OSSのコードとか読んでいきたい。
インターンに行ったタイミングが悪く、どでかいリリースの真っ最中で社員のみなさんはものすごく忙しそうだった。 段階的に公開(n%公開)していって、LBの負荷が異常に上がったりした段階で切り戻している姿を見たり、「このリリースが遅れたら一日当たりいくらの損失が出ますか!?」というパワーワードを聞いたりして、「これが現場か!!!!!」となった。毎日ISUCON状態。
上記のどでかいリリースの打ち上げは、タクシー🚕で店まで移動して、どでかいソファとどでかいスクリーンとどでかいスピーカーとカラオケとキッチンが配備されている個室がある店で行われた。「これがWebの力💰💴💸か!!!」となった。
業務の他にもスタジオ見学したり、セキュリティチームの人と会わせてもらえたりして勉強になった。
ネットにはこういう不穏な記事(サイバーエージェント社の内定式がリア充すぎると話題に 【画像あり】 | ニュース2ちゃんねる)もあるけど、普通にいい会社だった。というかエンジニアがあんなにチャラい訳がない。や、総合職の話はしらない。
様子
以下は様子です。
休日は温泉♨を攻めた。東京は小さい銭湯でも黒湯あるし、露天風呂あるしすごい。
はじめて九州料理食べた。写真は冷や汁。
オシャレマクロスデルタにいったはずがCCさくら🌸のlimited storeがあって優勝した。もうOIOIにはオタクのイメージしかない。
BABYMETALの東京ドームライブも行った。
帰る日にふぐ🐡食べた。神だった。とらふぐ亭 渋谷店 (とらふぐてい) - 神泉/ふぐ [食べログ]
おわりに
長期インターンでは短期インターンと違って実際の業務に組み込まれるので、非常に勉強になるのでよかった。某先輩(神)の言う通りだった。
メンターとチームのみなさまと人事のみなさまには感謝しております。この経験を今後につなげていきたいですね。
androidでpwnできる環境を作ってみる
ndk-buildにより作成したプログラムをandroid上で待ち受けさせる。arm環境でpwnするときに使える。
準備
プログラムを用意する
ソース
/* test.c */ # include <stdio.h> int main(void) { printf("Hello, world!\n"); return 0; }
ビルドスクリプト
Application.mkを作成する。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := test LOCAL_SRC_FILES := test.c include $(BUILD_EXECUTABLE)
ここではやっていないが、APP_CFLAGS += -fno-stack-protector を書くことでSSPを無効にできる。
ビルドする
$ ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Application.mk $ adb push libs/arm64-v8a/test /data/local/tmp/ $ adb shell shell@android:/data/local/tmp $ ./test Hello, world!
socatを入れる
androidにはsocatコマンドが入っていないので、socat for android - Post #28からzipをダウンロードしてadbで入れる。
$ adb push ~/downloads/socat-2.0.0-b7-armv7-a /data/local/tmp/
やってみる
adbで端末に接続し、socatで待ち受ける。
$ adb forward tcp:10001 tcp:10001 $ adb shell shell@android:/data/local/tmp $ cd /data/local/tmp/ shell@android:/data/local/tmp $ ./test Hello, world! shell@android:/data/local/tmp $ ip a ... 21: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000 link/ether 30:5a:3a:aa:56:33 brd ff:ff:ff:ff:ff:ff inet 192.168.3.67/24 brd 192.168.3.255 scope global wlan0 valid_lft forever preferred_lft forever inet6 fe80::325a:3aff:feaa:5633/64 scope link valid_lft forever preferred_lft forever shell@android:/data/local/tmp $ ./socat TCP4-LISTEN:10001,fork EXEC:./test
別のshellからnetcatで接続する。
$ nc 192.168.3.67 10001 Hello, world!
無事に接続できた。
社会...
給料日
給料日なのにまだ振り込まれてないのでこれはキテますよ
— 朝はむずかしい (@tkmru) 2016年8月25日
なんか今日は口座確認しただけでつかれた
— 朝はむずかしい (@tkmru) 2016年8月25日
最悪レベルで人権がなくて給料が振り込まれない(2度目)
— 朝はむずかしい (@tkmru) 2016年8月25日
給料ガチャ失敗😣
— 朝はむずかしい (@tkmru) 2016年8月25日
これは増田怪文書作成検討ですよ
— 朝はむずかしい (@tkmru) 2016年8月25日
業務内容は面白いんだけど、人事とか経理が壊滅的
— 朝はむずかしい (@tkmru) 2016年8月25日
次の日
催促したら入金はされた。
入金メールきたよかた
— 朝はむずかしい (@tkmru) 2016年8月26日
入金メールいちばん好きなメールです
— 朝はむずかしい (@tkmru) 2016年8月26日
某社やばすぎる
— 朝はむずかしい (@tkmru) 2016年8月26日
振り込まれたけど給料1日分足りてなかった...
— 朝はむずかしい (@tkmru) 2016年8月26日
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が配置されるアドレスとなるので、最初にこのアドレスを見にいって、違っていたらエントリポイントから処理を追っていくというアプローチがよさそうである。