1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コンパイル時の最適化処理とシンボル情報

1
Posted at

今回やること

最近、リバースエンジニアリングについて勉強している。

しかし、最近取り上げる題材は分かりやすいものばかり。
その原因は以下の2つ!
- 最適化処理をしていない
- シンボル情報が残っている

そりゃ、簡単なわけだ。

というわけで、これからは難しいものにも挑戦したいので、今回は「最適化処理で行われること」と「シンボル情報に含まれるもの」について記事を書いていこうと思う。

最適化処理とは?

コンパイラがソースコードをコンパイルする際に、プログラムの動きは変えずに、より効率的で高速な機械語へ作り変える処理のこと。

同じソースコードをコンパイルしても、最適化次第ではアセンブリコードに変化が起きる。

実際に確認してみる

0-ソースコード

使用するコードと動きは以下

動きは次の通り

main()実行

引数に足したい数字を入れてadd()を呼び出す

引数で受け取った数字を足して、戻り値として返す

printfで出力

終了

main.c
#include <stdio.h>

long add(long a, long b, long c, long d) {
    return a + b + c + d;
}

int main() {
    long x = add(1, 2, 3, 4);
    printf("%ld\n", x);
    return 0;
}

1-コンパイル

このソースコードを最適化のレベル別にコンパイルしてみる。
今回は最適化なし(オプション無し)と最適化有り(-O2 オプリョン)でコンパイルする

【最適化無し】
$ gcc main.c -o level0
【最適化有り】
$ gcc main.c -o level2 -O2

2-アセンブリとデコンパイル

それぞれ、gdbでmain関数のアセンブリを見てみると同時にghidraでデコンパイルを見てみる

【最適化無し】

  • gdb
level0
$gdb ./level0
>>> disas main
0x0000555555555168 <+0>:     push   rbp
0x0000555555555169 <+1>:     mov    rbp,rsp
0x000055555555516c <+4>:     sub    rsp,0x10
0x0000555555555170 <+8>:     mov    ecx,0x4
0x0000555555555175 <+13>:    mov    edx,0x3
0x000055555555517a <+18>:    mov    esi,0x2
0x000055555555517f <+23>:    mov    edi,0x1
0x0000555555555184 <+28>:    call   0x555555555139 <add>
0x0000555555555189 <+33>:    mov    QWORD PTR [rbp-0x8],rax
0x000055555555518d <+37>:    mov    rax,QWORD PTR [rbp-0x8]
0x0000555555555191 <+41>:    mov    rsi,rax
0x0000555555555194 <+44>:    lea    rax,[rip+0xe69] 
0x000055555555519b <+51>:    mov    rdi,rax
0x000055555555519e <+54>:    mov    eax,0x0
0x00005555555551a3 <+59>:    call   0x555555555030 <printf@plt>
0x00005555555551a8 <+64>:    mov    eax,0x0
0x00005555555551ad <+69>:    leave
0x00005555555551ae <+70>:    ret
  • ghidra
    image.png

【最適化有り】

  • gdb
level2
$gdb ./level2
>>> disas main
0x0000000000001050 <+0>:     sub    rsp,0x8
0x0000000000001054 <+4>:     mov    esi,0xa
0x0000000000001059 <+9>:     lea    rdi,[rip+0xfa4]        # 0x2004
0x0000000000001060 <+16>:    xor    eax,eax
0x0000000000001062 <+18>:    call   0x1030 <printf@plt>
0x0000000000001067 <+23>:    xor    eax,eax
0x0000000000001069 <+25>:    add    rsp,0x8
0x000000000000106d <+29>:    ret
  • ghidra
    image.png

比べてみると、、、

最適化をしていないlevel0はソースコードに忠実なアセンブリになっている。
最適化を入れたlevel2は、計算も何もせずに最初から合計値をprintf関数で出力するだけになっている。また、add()関数も呼び出されなくなっている。

最適化をしたlevel2ではadd()関数は呼び出されなくなったが、実行ファイルの中には残っている。

  • gdbで見てみる
>>> info func
~略~
0x0000555555555160  add
~略~
>>> disas add
Dump of assembler code for function add:
0x0000555555555160 <+0>:     add    rdi,rsi
0x0000555555555163 <+3>:     add    rdi,rdx
0x0000555555555166 <+6>:     lea    rax,[rdi+rcx*1]
0x000055555555516a <+10>:    ret
End of assembler dump.
  • ghidra
    image.png

不要な関数まで削除するには、-ffunction-sections-Wl,--gc-sectionsなどのリンカ最適化が必要になる。

最適化時の実行速度について

実行速度についてはclaud君を最大限利用して検証して、改めたまとめた記事が以下になります。

シンボル情報とは?

シンボル情報とは、「プログラム中の名前」と「実際のアドレス」を対応付ける情報のこと。

通常のgccでコンパイルした実行ファイルには、関数名やグローバル変数名などのシンボル情報が含まれている。シンボル情報は「名前」と「実際のアドレス」を対応付けるための情報であり、デバッガや解析ツールが関数名を表示できる理由となっている。

確認方法

先程作成したlevel0についてnmコマンドを使用して確認してみる

【シンボル情報あり】

アドレスと関数名が結びついていることがわかる

$nm level0
0000000000003de0 d _DYNAMIC
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
0000000000002000 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000002108 r __FRAME_END__
000000000000200c r __GNU_EH_FRAME_HDR
0000000000004018 D __TMC_END__
000000000000210c r __abi_tag
0000000000004018 B __bss_start
w __cxa_finalize@GLIBC_2.2.5
0000000000004008 D __data_start
00000000000010f0 t __do_global_dtors_aux
0000000000003dd8 d __do_global_dtors_aux_fini_array_entry
0000000000004010 D __dso_handle
0000000000003dd0 d __frame_dummy_init_array_entry
w __gmon_start__
U __libc_start_main@GLIBC_2.34
0000000000004018 D _edata
0000000000004020 B _end
00000000000011b0 T _fini
0000000000001000 T _init
0000000000001050 T _start
0000000000001139 T add
0000000000004018 b completed.0
0000000000004008 W data_start
0000000000001080 t deregister_tm_clones
0000000000001130 t frame_dummy
0000000000001168 T main
U printf@GLIBC_2.2.5
00000000000010b0 t register_tm_clones

【シンボル情報無し】

シンボル情報の削除にはstripコマンドを使用する

$strip level0

削除後に改めてnmコマンドで確認してみる

$nm level0
nm: level0: シンボルがありません

他の確認方法として、readelf -Sでセクション一覧を見る方法もある。
シンボル情報が残っている場合には以下のようなセクションが存在する。

#strip未実行
$readelf -S level0
~略~
[28] .symtab           SYMTAB           0000000000000000  00003038
0000000000000378  0000000000000018          29    18     8
[29] .strtab           STRTAB           0000000000000000  000033b0
00000000000001df  0000000000000000           0     0     1
~略~

最後に

今回まとめたことによって、CTFでプレイしている解析対象とは違い、実際のプログラムの解析は難易度が上がることがわかった。

そのうち、チャレンジしていきたい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?