gccが生成するELF付随品について探る
この記事はTSG Advent Calendarの21日目の記事として書かれたものです。
CTFやらでReversingをしていると、いつも見るframe_dummyやinit、本質ではない、的な感じでスルーをしているんですが、それなりに気になるところでもあり、少し深く見てみようかなと思います。
今回は、とりあえず
#include <stdio.h> int main(void) { printf("advent calendar 2016\n"); return 0; }
このソースコードをコンパイルして、中身を調査していきたいと思います。
$ gcc hello.c -o hello $ uname -a Linux vagrant-ubuntu-trusty-64 3.13.0-101-generic #148-Ubuntu SMP Thu Oct 20 22:08:32 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.5 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4 Copyright (C) 2013 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.
これをdisassembleすると、main以外にもいくつか(むしろmainは5,6%程度にすぎない)の部分が含まれていることがわかります。普段は、あまり本質ではないので気にしないのですが(よくない)、今回は、大雑把な、初期化処理をしている〜、みたいな理解よりももう少しだけ、よく理解してみようと思います。
pltセクションの内容は今回の興味ではないので、.textと.initの部分にmain以外にどんな部分があるのかといえば、列挙すると以下になります。
00000000004003e0 <_init>: 0000000000400440 <_start>: 0000000000400470 <deregister_tm_clones>: 00000000004004a0 <register_tm_clones>: 00000000004004e0 <__do_global_dtors_aux>: 0000000000400500 <frame_dummy>: 0000000000400550 <__libc_csu_init>: 00000000004005c0 <__libc_csu_fini>:
初期化処理付近
まずは、_startについて。これは言うまでもなくEntrypointであり、
$ readelf -h hello ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400440 Start of program headers: 64 (bytes into file) Start of section headers: 4472 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 27
0000000000400440 <_start>: 400440: 31 ed xor ebp,ebp 400442: 49 89 d1 mov r9,rdx 400445: 5e pop rsi 400446: 48 89 e2 mov rdx,rsp 400449: 48 83 e4 f0 and rsp,0xfffffffffffffff0 40044d: 50 push rax 40044e: 54 push rsp 40044f: 49 c7 c0 c0 05 40 00 mov r8,0x4005c0 400456: 48 c7 c1 50 05 40 00 mov rcx,0x400550 40045d: 48 c7 c7 2d 05 40 00 mov rdi,0x40052d 400464: e8 b7 ff ff ff call 400420 <__libc_start_main@plt> 400469: f4 hlt 40046a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
確かにこの0x400440
は、_startを指していて、Entrypointです。_start自体に関しては、gdbを使って追ってみます。実行時引数として、「advent calendar」を与えて、実行します。まず、_startについた段階ではスタックの様子は
[------------------------------------stack-------------------------------------] [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdfd0 --> 0x3 0008| 0x7fffffffdfd8 --> 0x7fffffffe24f ("/home/vagrant/host-share/adv/hello") 0016| 0x7fffffffdfe0 --> 0x7fffffffe272 --> 0x6300746e65766461 ('advent') 0024| 0x7fffffffdfe8 --> 0x7fffffffe279 ("calendar") 0032| 0x7fffffffdff0 --> 0x0 0040| 0x7fffffffdff8 --> 0x7fffffffe282 ("XDG_SESSION_ID=3") 0048| 0x7fffffffe000 --> 0x7fffffffe293 ("PYENV_ROOT=/home/vagrant/.pyenv") 0056| 0x7fffffffe008 --> 0x7fffffffe2b3 ("SHELL=/bin/bash") [------------------------------------------------------------------------------][------------------------------------------------------------------------------]
となっています。この_startでは、__libc_start_mainを呼び出すための引数を整えています。具体的には、x64のcalling conventionがrdi, rsi, rdx, rcx, r8, r9の順に積み上がっていき、__libc_start_mainの引数が、
- mainアドレス
- argc
- argv
- initアドレス
- finiアドレス
- stack_end
であり、実際、__libc_start_mainの呼び出しの直前で止めたときにのレジスタは
RAX: 0x1c RBX: 0x0 RCX: 0x400550 (<__libc_csu_init>: push r15) RDX: 0x7fffffffdfd8 --> 0x7fffffffe24f ("/home/vagrant/host-share/adv/hello") RSI: 0x3 RDI: 0x40052d (<main>: push rbp) RBP: 0x0 RSP: 0x7fffffffdfc0 --> 0x7fffffffdfc8 --> 0x1c RIP: 0x400464 (<_start+36>: call 0x400420 <__libc_start_main@plt>) R8 : 0x4005c0 (<__libc_csu_fini>: repz ret) R9 : 0x7ffff7dea530 (<_dl_fini>: push rbp) R10: 0x14 R11: 0x1 R12: 0x400440 (<_start>: xor ebp,ebp) R13: 0x7fffffffdfd0 --> 0x3 R14: 0x0 R15: 0x0 EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
となっていてmain、__libc_csu_initや__libc_csu_finiなどが正しく渡されていることがわかります。
関数のリストにはないですが、このまま__libc_start_mainの挙動を追ってみます。これは、Cのソースコードを見てみることができ、実際に、mainが第一引数argc, 第二引数argv,そしてenvironが第三引数として渡されていることが確認できます。また、mainの返り値をexitに引数として与えているのも見て取れます。
/* Run the program. */
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
さらに、引数として渡した__libc_csu_initは、
if (init)
(*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);
として、呼び出されていて、gdbで確認してみると、
0x7ffff7a36ecc <__libc_start_main+124>: mov edi,DWORD PTR [rsp+0x14] 0x7ffff7a36ed0 <__libc_start_main+128>: mov rdx,QWORD PTR [rax] => 0x7ffff7a36ed3 <__libc_start_main+131>: call rbp 0x7ffff7a36ed5 <__libc_start_main+133>: mov rax,QWORD PTR [rip+0x39bf84] # 0x7ffff7dd2e60
で、
gdb-peda$ p $rbp $1 = (void *) 0x400550 <__libc_csu_init>
となっていて、中身が呼び出されます。__libc_csu_initのソースコードが見れるので、これを、コメントなどを取り除いて、また必要ない部分はなくし、小さくまとめたものが次のコードです(LIBC_NONSHAREDのifdefの部分は、今見ている部分には無さそうという理由でカットしています)。
void __libc_csu_init (int argc, char **argv, char **envp) { _init (); const size_t size = __init_array_end - __init_array_start; for (size_t i = 0; i < size; i++) (*__init_array_start [i]) (argc, argv, envp); }
これと、__libc_csu_initをdisassembleした結果を照らし合わせてみると、まず、_initを呼び出す操作があって、
40057e: e8 5d fe ff ff call 4003e0 <_init>
もう一つの処理として*__init_array_start配列に含まれる関数群を呼び出す操作としては、
0000000000400550 <__libc_csu_init>: 400561: 4c 8d 25 a8 08 20 00 lea r12,[rip+0x2008a8] # 600e10 <__frame_dummy_init_array_entry> 400568: 55 push rbp 400569: 48 8d 2d a8 08 20 00 lea rbp,[rip+0x2008a8] # 600e18 <__init_array_end> 400570: 53 push rbx 400571: 4c 29 e5 sub rbp,r12 400574: 31 db xor ebx,ebx 400576: 48 c1 fd 03 sar rbp,0x3 40057a: 48 83 ec 08 sub rsp,0x8 [省略] 400590: 4c 89 ea mov rdx,r13 400593: 4c 89 f6 mov rsi,r14 400596: 44 89 ff mov edi,r15d 400599: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 40059d: 48 83 c3 01 add rbx,0x1 4005a1: 48 39 eb cmp rbx,rbp 4005a4: 75 ea jne 400590 <__libc_csu_init+0x40>
となっています。ここでは前半部分で、__init_array_endから__frame_dummy_init_array_entryの間にあるポインタの数を計算して、後半のfor文でそれらを呼び出すという処理がされているということがわかります。これらの実行を実際にgdbで確認してみて、何が呼び出されるのかを見てみます。
gdb-peda$ p 0x600e18 - 0x600e10 $1 = 0x8 gdb-peda$ x/x 0x600e10 0x600e10: 0x0000000000400500 gdb-peda$ x/10i 0x0000000000400500 0x400500 <frame_dummy>: cmp QWORD PTR [rip+0x200918],0x0 # 0x600e20 0x400508 <frame_dummy+8>: je 0x400528 <frame_dummy+40> 0x40050a <frame_dummy+10>: mov eax,0x0 0x40050f <frame_dummy+15>: test rax,rax 0x400512 <frame_dummy+18>: je 0x400528 <frame_dummy+40> 0x400514 <frame_dummy+20>: push rbp 0x400515 <frame_dummy+21>: mov edi,0x600e20 0x40051a <frame_dummy+26>: mov rbp,rsp 0x40051d <frame_dummy+29>: call rax 0x40051f <frame_dummy+31>: pop rbp
すると、このテーブルにはframe_dummyが登録されていることがわかります(他にはこの状況では何も登録されていないこともわかります)。 では、ある意味で今回の主役であるframe_dummyはどういうことをしているのか、について調べて行きます。
0000000000400500 <frame_dummy>: 400500: 48 83 3d 18 09 20 00 cmp QWORD PTR [rip+0x200918],0x0 # 600e20 <__JCR_END__> 400507: 00 400508: 74 1e je 400528 <frame_dummy+0x28> 40050a: b8 00 00 00 00 mov eax,0x0 40050f: 48 85 c0 test rax,rax 400512: 74 14 je 400528 <frame_dummy+0x28> 400514: 55 push rbp 400515: bf 20 0e 60 00 mov edi,0x600e20 40051a: 48 89 e5 mov rbp,rsp 40051d: ff d0 call rax 40051f: 5d pop rbp 400520: e9 7b ff ff ff jmp 4004a0 <register_tm_clones> 400525: 0f 1f 00 nop DWORD PTR [rax] 400528: e9 73 ff ff ff jmp 4004a0 <register_tm_clones>
gdbで実行してみると、__JCR_END__が
gdb-peda$ x/x 0x600e20 0x600e20: 0x0000000000000000
となっていますが、__JCR_END__が0であろうとなかろうと結局やっていることは、register_tm_clonesにjumpするということで、frame_dummyは今回ただregister_tm_clonesにjumpするだけのもののようです。結局これだけしかやってないのかあという感じです。
では、次に_initが__libc_csu_initで呼び出されるので、_initがどうなっていくかを追ってみます。 _initのdisassemble結果は短いので、貼ってしまいますが、
00000000004003e0 <_init>: 4003e0: 48 83 ec 08 sub rsp,0x8 4003e4: 48 8b 05 0d 0c 20 00 mov rax,QWORD PTR [rip+0x200c0d] # 600ff8 <_DYNAMIC+0x1d0> 4003eb: 48 85 c0 test rax,rax 4003ee: 74 05 je 4003f5 <_init+0x15> 4003f0: e8 3b 00 00 00 call 400430 <__gmon_start__@plt> 4003f5: 48 83 c4 08 add rsp,0x8 4003f9: c3 ret
となっています。結局_DYNAMIC+0x1d0が0以外のときは__gmon_start__を呼ぶし、0ならば呼ばないということをしているようです。__gmon_start__の実装を見てみると、
void __gmon_start__ (void) { #ifdef HAVE_INITFINI static int called; if (called) return; called = 1; #endif /* Start keeping profiling records. */ __monstartup ((u_long) TEXT_START, (u_long) &etext); atexit (&_mcleanup); }
一回だけしか呼び出されないようになっていて、また結局_monstartupを呼び出すのが本質のようです。これ自体は、コメントにもあるように、Profiling記録を開始する関数のようで、これ以上は、とりあえずおいておくことにして次の関数に行きたいと思います。
とりあえず、__libc_start_mainまで話を戻します。ここでは、__libc_csu_initの他にも__libc_csu_finiを呼び出す関数を呼び出しています。Cのソースコードでいうと、
if (fini) _cxa_atexit ((void (*) (void *)) fini, NULL, NULL);
という部分です。これもlibcの実装を見てみると、
int attribute_hidden __internal_atexit (void (*func) (void *), void *arg, void *d, struct exit_function_list **listp) { struct exit_function *new = __new_exitfn (listp); if (new == NULL) return -1; #ifdef PTR_MANGLE PTR_MANGLE (func); #endif new->func.cxa.fn = (void (*) (void *, int)) func; new->func.cxa.arg = arg; new->func.cxa.dso_handle = d; atomic_write_barrier (); new->flavor = ef_cxa; return 0; } int __cxa_atexit (void (*func) (void *), void *arg, void *d) { return __internal_atexit (func, arg, d, &__exit_funcs); }
これをみると、exit_funcsのリンクリストに新しいエントリとして渡されたfuncをつないでいる(多分)ようです。
Transactional Memory系
具体的に言えば
- register_tm_clones
- deregister_tm_clones
ですが、これに関してはあんまり挙動がよくわからないというのが正直なところで、詳しいことがあんまよくわからないです。マルチスレッドにおいて、共有するメモリへのアクセスが考えられる時に、TMが使われる、ということですが、今回のプログラムとどう関連しているかはよくわからないです。。
さいごに
なんか最後の方疲れてしまった。期限も過ぎているのでとりあえず公開しますが、余力があったらいつか追記します。
EC2のインスタンスをウェブサーバとして S3を使ってNamecheapのDNSを関連付ける
メモ。
簡易的なflaskでのウェブサービスをデプロイしたりしたときにやったこと(後から書いているので漏れがあるかもしれない)。
使ったもの
EC2
サーバー設定
- とりあえず、t2.micro(なんでもいい)を作る
- セキュリティの中で、HTTPやHTTPSのポートを開ける
- sshで鯖内の初期設定をする
- flaskアプリを上げる
- 動くように設定する
- 外部からアクセスできるか試してみる
固定IP
- Elastic IPを設定する(インスタンスが死ぬと課金されるので忘れないようにする)
- だけだったっけ
Namecheap1
- 適当にドメインを買います
Route S3
- Hosted ZoneからCreate Hostet Zoneをする
- ドメインなどを設定する
- Create Record SetからValueにElastic IPを設定して紐付ける
Namechap2
- Manageをして、Domainの中のNameServicesに、Route S3で表示されたドメインを追加する
少し待つとドメインが更新される(初期設定では48時間)
参考にしたもの
Amazon Route53編~サイトを公開してみよう!パート①~ NameCheap domain name and Amazon EC2
HITCON CTF 2016 RegExpert and moRE writeup
RegExpert
RegExpert 59 Teams solved. Description Do you remember "hard to say" last year? I think this one is harder to say... nc 52.69.125.71 2171 Hint None
% cat regexp (?i)s.*e.*l.*e.*c.*t ^(a\g<1>?b)$ ^(?!(xx+)\1+$)xx+$ ^((.)\g<1>\2|.?)$ (?=^(a\g<1>?b)c)a+(b\g<2>?c)$
% cat regexp - | nc 52.69.125.71 2171 Hi! We want to hire some TRUE regular expression hackers to write firewall rules. So here are five interview questions for you, good luck! Note: After CVE-2015-4410, we reject everything contains newline, so you can just safely use ^ and $ here. ================= [SQL] ================= Please match string that contains "select" as a case insensitive subsequence. Running on test #885...Accepted :) =============== [a^nb^n] ================ Yes, we know it is a classical example of context free grammer. Running on test #370...Accepted :) ================= [x^p] ================= A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself. Running on test #304...Accepted :) ============= [Palindrome] ============== Both "QQ" and "TAT" are palindromes, but "PPAP" is not. Running on test #799...Accepted :) ============== [a^nb^nc^n] ============== Is CFG too easy for you? How about some context SENSITIVE grammer? Running on test #504...Accepted :) Congratz! Here is your singing bonus: hitcon{The pumping lemma is a lie, just like the cake}
moRE
We were not able to solve this challenge in time (20 minutes delay). However, we answered all of the “questions.” So, I wrote this writeup.
11 Teams solved. Description Hi, our RegExpert, I know you want moRE. nc 52.69.125.71 2172 Hint None
First and Last of this challenge were solved by @satos___jp
% cat moRE ^(0|[1-9]\d*0000|(4|8|(([1-9]\d*|)([13579][26]|[2468][048])|[1-9]\d*0[48]))(00|))$ (xx\k<1+0>|^x)+$ (?=^-?([0369]|[147][0369]*[258]|(([258]|[147][0369]*[147])([0369]|[258][0369]*[147])*([147]|[258][0369]*[258])))+$)(?=^-?.*[02468]$)(?=^-?(?!$)(?>(|(?<B>4\g<A>|5\g<B>|6\g<C>|[07]\g<D>|[18]\g<E>|[29]\g<F>|3\g<G>))(|(?<C>[18]\g<A>|[29]\g<B>|3\g<C>|4\g<D>|5\g<E>|6\g<F>|[07]\g<G>))(|(?<D>5\g<A>|6\g<B>|[07]\g<C>|[18]\g<D>|[29]\g<E>|3\g<F>|4\g<G>))(|(?<E>[29]\g<A>|3\g<B>|4\g<C>|5\g<D>|6\g<E>|[07]\g<F>|[18]\g<G>))(|(?<F>6\g<A>|[07]\g<B>|[18]\g<C>|[29]\g<D>|3\g<E>|4\g<F>|5\g<G>))(|(?<G>3\g<A>|4\g<B>|5\g<C>|6\g<D>|[07]\g<E>|[18]\g<F>|[29]\g<G>)))(?<A>$|[07]\g<A>|[18]\g<B>|[29]\g<C>|3\g<D>|4\g<E>|5\g<F>|6\g<G>)+$)(^((?!-?0).+|0)$) (?!,)(?=^(.\g<1>.|,)$)(|((0|1))\g<2>),(|(?!\k<3+0>)[01]\g<5>)$
% cat moRE - | nc 52.69.125.71 2172 Hi, our RegExpert, do you want one moRE flag? Solve these four :P ============== [Leap Year] ============== Even my cat can code this 🐱 Running on test #6845...Accepted :) =============== [x^(n^2)] =============== We like squares: ■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪▫ Running on test #517...Accepted :) =========== [Multiple of 42] ============ The answer to life the universe and everything. Running on test #1601...Accepted :) ============= [Complement] ============== Please match x,~x where x is a binary string. Like "0,1" or "1001,0110". Running on test #1870...Accepted :) Congratz! You are now our CRO (Chief Regular Officer). ATTENTION! HUGE GIFT IS COMING! 3...2...1... (...sorry I cut the last part)
The last [Complement] is a little tricky. The essense of the answer is
^(|((0|1))\g<2>),(|(?!\k<3+0>)[01]\g<5>)$ (when extracted, ^(|((0|1))\g<2>),(|(?!\k<2+0>)[01]\g<4>)$ is correct)
First (|((0|1))\g<2>)
matches the sequence of 0,1 and each value has own nest by the recursion. Because there is ‘,’ after this part, as a whole, this part matches x of “x,~x”.
Next (|(?!\k<3+0>)[01]\g<5>)
is a little tricky(and this is the point of this question). In order to make the regexp easier, I want to show another equivalent regex.
^(?<first>|((?<value>0|1))\g<first>),(?<second>|(?!\k<value+0>)[01]\g<second>)$
so, we can see \k\<value+0> of (?!\k<value+0) references at (?\
Last part of this challenge(decoding given “huge gift”) is explained there.
今年の総括
もうすぐ今年も終わりらしいので、一応今年を振り返りたいなと思います。
1月から2月にかけて、絶対JOI春合宿行こうな!っていう気分で、蟻本を読みつつ、競プロなどをしていました。死んでからは、競技プログラミングという分野が見えなくなりました。
でしたが、なんか科オリというものに未練が残ったので、物理チャレンジ予選に出ました。封筒が送られてきたので、通ったのかなと思ったら通っていました。本戦は計10時間小さな箱の中に佇んでいましたが、結果銅賞(19~30位)でした。銅が、JOIとJPhOで定義が違いすぎるなと思いました。
↑去年よりたくさん書けたので、来年はもう少し色々かける一年でありますようにといった感じです。来年も頑張りたいと思います。
JOI本戦参加しました
人生どうでも飯田橋
あけましておめでとうございます
今年は昨年以上に忙しくなる一年だと思うので、頑張りたいと思います。
年賀状情報です。
今年もよろしくおねがいします。目標として、昨年はここで足し算を目標としたので、今年は引き算の習得を目指して行こうと思います。