以下のアプリケーションをコンパイルして、そのシンボルを取り除いたとしましょう。
#include <stdio.h>
int main()
{
printf("Hello\n");
}
ビルド手順:
gcc -o hello hello.c
strip --strip-unneeded hello
アプリケーションが取り除かれていない場合、メイン機能を分解するのは簡単です。ただし、ストリップされたアプリケーションのmain関数を分解する方法はわかりません。
(gdb) disas main
No symbol table is loaded. Use the "file" command.
(gdb) info line main
Function "main" not defined.
どうすればできますか?可能ですか?
Notes:これはGDBでのみ実行する必要があります。 objdumpを忘れます。コードにアクセスできないとします。
ステップバイステップの例をいただければ幸いです。
さて、ここに私の以前の答えの大きな版があります。私は今道を見つけたと思います。
あなた(まだ:)にはこの特定の問題があります:
(gdb) disas main
No symbol table is loaded. Use the "file" command.
ここで、コードをコンパイルすると(最後にreturn 0
を追加しました)、gcc -S
が得られます。
pushq %rbp
movq %rsp, %rbp
movl $.LC0, %edi
call puts
movl $0, %eax
leave
ret
これで、バイナリがいくつかの情報を提供していることがわかります。
ストライプ:
(gdb) info files
Symbols from "/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip".
Local exec file:
`/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip', file type elf64-x86-64.
Entry point: 0x400440
0x0000000000400238 - 0x0000000000400254 is .interp
...
0x00000000004003a8 - 0x00000000004003c0 is .rela.dyn
0x00000000004003c0 - 0x00000000004003f0 is .rela.plt
0x00000000004003f0 - 0x0000000000400408 is .init
0x0000000000400408 - 0x0000000000400438 is .plt
0x0000000000400440 - 0x0000000000400618 is .text
...
0x0000000000601010 - 0x0000000000601020 is .data
0x0000000000601020 - 0x0000000000601030 is .bss
ここで最も重要なエントリは.text
です。これは、アセンブリのコード開始の一般的な名前です。メインの怒鳴る声の説明から、そのサイズから、メインが含まれていることがわかります。逆アセンブルすると、__ libc_start_mainが呼び出されます。最も重要なのは、実際のコードである適切なエントリポイントを逆アセンブルすることです(DATAをCODEに変更するように誤解させることはありません)。
disas 0x0000000000400440,0x0000000000400618
Dump of assembler code from 0x400440 to 0x400618:
0x0000000000400440: xor %ebp,%ebp
0x0000000000400442: mov %rdx,%r9
0x0000000000400445: pop %rsi
0x0000000000400446: mov %rsp,%rdx
0x0000000000400449: and $0xfffffffffffffff0,%rsp
0x000000000040044d: Push %rax
0x000000000040044e: Push %rsp
0x000000000040044f: mov $0x400540,%r8
0x0000000000400456: mov $0x400550,%rcx
0x000000000040045d: mov $0x400524,%rdi
0x0000000000400464: callq 0x400428 <__libc_start_main@plt>
0x0000000000400469: hlt
...
0x000000000040046c: sub $0x8,%rsp
...
0x0000000000400482: retq
0x0000000000400483: nop
...
0x0000000000400490: Push %rbp
..
0x00000000004004f2: leaveq
0x00000000004004f3: retq
0x00000000004004f4: data32 data32 nopw %cs:0x0(%rax,%rax,1)
...
0x000000000040051d: leaveq
0x000000000040051e: jmpq *%rax
...
0x0000000000400520: leaveq
0x0000000000400521: retq
0x0000000000400522: nop
0x0000000000400523: nop
0x0000000000400524: Push %rbp
0x0000000000400525: mov %rsp,%rbp
0x0000000000400528: mov $0x40062c,%edi
0x000000000040052d: callq 0x400418 <puts@plt>
0x0000000000400532: mov $0x0,%eax
0x0000000000400537: leaveq
0x0000000000400538: retq
__ libc_start_main の呼び出しは、最初の引数としてmain()へのポインタを取得します。したがって、呼び出しの直前のスタックの最後の引数はmain()アドレスです。
0x000000000040045d: mov $0x400524,%rdi
0x0000000000400464: callq 0x400428 <__libc_start_main@plt>
ここでは0x400524です(既に知っています)。ここでブレークポイントを設定して、これを試してください:
(gdb) break *0x400524
Breakpoint 1 at 0x400524
(gdb) run
Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2
Breakpoint 1, 0x0000000000400524 in main ()
(gdb) n
Single stepping until exit from function main,
which has no line number information.
hello 1
__libc_start_main (main=<value optimized out>, argc=<value optimized out>, ubp_av=<value optimized out>,
init=<value optimized out>, fini=<value optimized out>, rtld_fini=<value optimized out>,
stack_end=0x7fffffffdc38) at libc-start.c:258
258 libc-start.c: No such file or directory.
in libc-start.c
(gdb) n
Program exited normally.
(gdb)
これで、次のコマンドを使用して分解できます。
(gdb) disas 0x0000000000400524,0x0000000000400600
Dump of assembler code from 0x400524 to 0x400600:
0x0000000000400524: Push %rbp
0x0000000000400525: mov %rsp,%rbp
0x0000000000400528: sub $0x10,%rsp
0x000000000040052c: movl $0x1,-0x4(%rbp)
0x0000000000400533: mov $0x40064c,%eax
0x0000000000400538: mov -0x4(%rbp),%edx
0x000000000040053b: mov %edx,%esi
0x000000000040053d: mov %rax,%rdi
0x0000000000400540: mov $0x0,%eax
0x0000000000400545: callq 0x400418 <printf@plt>
0x000000000040054a: mov $0x0,%eax
0x000000000040054f: leaveq
0x0000000000400550: retq
0x0000000000400551: nop
0x0000000000400552: nop
0x0000000000400553: nop
0x0000000000400554: nop
0x0000000000400555: nop
...
これは主に解決策です。
ところで、これは動作するかどうかを確認するための別のコードです。上記のアセンブリが少し違うのはそのためです。上記のコードは、このcファイルからのものです。
#include <stdio.h>
int main(void)
{
int i=1;
printf("hello %d\n", i);
return 0;
}
だが!
これが機能しない場合でも、いくつかのヒントがあります。
今後は、すべての関数の最初にブレークポイントを設定する必要があります。 ret
またはleave
の直前にあります。最初のエントリポイントは.text
自体です。これは組立開始ですが、メインではありません。
問題は、常にブレークポイントがプログラムを実行できるとは限らないことです。まさに.text
の中でこのように:
(gdb) break *0x0000000000400440
Breakpoint 2 at 0x400440
(gdb) run
Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2
Breakpoint 2, 0x0000000000400440 in _start ()
(gdb) n
Single stepping until exit from function _start,
which has no line number information.
0x0000000000400428 in __libc_start_main@plt ()
(gdb) n
Single stepping until exit from function __libc_start_main@plt,
which has no line number information.
0x0000000000400408 in ?? ()
(gdb) n
Cannot find bounds of current function
したがって、次の場所にブレークポイントを設定して、方法が見つかるまで試行を続ける必要があります。
0x400440
0x40046c
0x400490
0x4004f4
0x40051e
0x400524
他の答えから、この情報を保持する必要があります:
ストライプ化されていないバージョンのファイルでは、次のようになります。
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400524 <+0>: Push %rbp
0x0000000000400525 <+1>: mov %rsp,%rbp
0x0000000000400528 <+4>: mov $0x40062c,%edi
0x000000000040052d <+9>: callq 0x400418 <puts@plt>
0x0000000000400532 <+14>: mov $0x0,%eax
0x0000000000400537 <+19>: leaveq
0x0000000000400538 <+20>: retq
End of assembler dump.
これで、mainが0x0000000000400524,0x0000000000400539
にあることがわかりました。同じオフセットを使用してストライプバイナリを確認すると、同じ結果が得られます。
(gdb) disas 0x0000000000400524,0x0000000000400539
Dump of assembler code from 0x400524 to 0x400539:
0x0000000000400524: Push %rbp
0x0000000000400525: mov %rsp,%rbp
0x0000000000400528: mov $0x40062c,%edi
0x000000000040052d: callq 0x400418 <puts@plt>
0x0000000000400532: mov $0x0,%eax
0x0000000000400537: leaveq
0x0000000000400538: retq
End of assembler dump.
したがって、メインが開始するヒント(シンボルを含む別のコードを使用するなど)が得られない限り、別の方法は、最初のアセンブリ命令に関する情報を入手でき、特定の場所で分解して、一致するかどうかを確認できることです。コードにまったくアクセスできない場合でも、 ELF定義 を読んで、コードに表示されるセクションの数を理解し、計算されたアドレスを試すことができます。それでも、コード内のセクションに関する情報が必要です!
それは大変な仕事です、私の友人!幸運を!
ベコ
info files
(アドレス付きの)セクションリストを取得し、そこから移動しますか?
例:
gdb) info files
Symbols from "/home/bob/tmp/t".
Local exec file:
`/home/bob/tmp/t', file type elf64-x86-64.
Entry point: 0x400490
0x0000000000400270 - 0x000000000040028c is .interp
0x000000000040028c - 0x00000000004002ac is .note.ABI-tag
....
0x0000000000400448 - 0x0000000000400460 is .init
....
逆アセンブル.init
:
(gdb) disas 0x0000000000400448,0x0000000000400460
Dump of assembler code from 0x400448 to 0x400460:
0x0000000000400448: sub $0x8,%rsp
0x000000000040044c: callq 0x4004bc
0x0000000000400451: callq 0x400550
0x0000000000400456: callq 0x400650
0x000000000040045b: add $0x8,%rsp
0x000000000040045f: retq
次に、残りを分解します。
私があなたであり、実行可能ファイルのビルドに使用したのと同じGCCバージョンを持っている場合、ダミーのストリップされていない実行可能ファイルで呼び出された一連の関数を調べます。呼び出しのシーケンスは、ほとんどの通常の場合でおそらく同じであるため、main
までの起動シーケンスを比較するのに役立ちます。最適化はおそらく邪魔になるでしょう。
バイナリが削除されて最適化されている場合、main
はバイナリの「エンティティ」として存在しない可能性があります。おそらく、このタイプの手順よりもはるかに良くなることはできません。
Undtripと呼ばれるparadynプロジェクト(完全な開示:私はこのプロジェクトに取り組んでいます)と呼ばれる素晴らしい無料のツールがあり、プログラムバイナリを書き換えてシンボル情報を追加し、ストリップされたElfバイナリのすべて(またはほぼすべて)の関数を回復しますあなたのために、非常に正確に。それはメイン関数を「メイン」として識別しませんが、それを見つけます。そして、あなたがすでに述べたヒューリスティックを適用して、どの関数がメインであるかを理解することができます。
http://www.paradyn.org/html/tools/unstrip.html
これはgdbのみのソリューションではありません。