unix/linux についてもっと知りたいのですが、この質問が頭に浮かびました。たとえば、静的/動的ライブラリ (.a または .so) を作成し、c/c++ ソース コードとヘッダー ファイルを紛失したとします。デフォルトの nm 出力にはシンボルの名前が表示されますが、ヘッダーを作成するには、戻り値の型とパラメーターの数/型を知る必要があります。この追加情報を取得して、特定のライブラリのヘッダーをリバース エンジニアリングすることはできますか?
2 に答える
C と C++ にタグを付けましたが、答えは 2 つの間でわずかに異なります。
C++ の場合、クラスのメソッド名には、シンボル名に埋め込まれた型情報があります。ライブラリをコンパイルしたコンパイラがどのような名前マングリングを行ったかを理解する必要があります。
Cの場合、それを行うための本当にきれいな方法はありません。アセンブリを分解し、関数が受け取るパラメーターの数を把握するために、書き込まれずに読み取られるレジスターとスタック領域を分析できます。これには、ライブラリをコンパイルしたコンパイラが使用する呼び出し規約の知識が必要です。
同様に、各パラメータがアセンブリでどのように使用されているかを確認できます。ロード命令で使用されている場合は、何らかのポインターである可能性が高く、算術で使用されている場合は、何らかの整数である可能性があります。
戻り値の型については、一見意味のあるものが戻り命令の前に戻りレジスタに配置されているかどうかを確認できます。繰り返しますが、これには、プラットフォームの呼び出し規則に関する知識が必要です。
これは、ARM アセンブリでどのように行うかの例です。
ARM のパラメータはレジスタ r0 から r3 に渡され、戻り値はレジスタ r0 に格納されることを知っています。それを念頭に置いて、リバースエンジニアリングを開始できます。2 つの関数のアセンブリを見て、関数のプロトタイプが何であったかを考えてみましょう。
00000000 <func1>:
0: e3510000 cmp r1, #0
4: 0a000007 beq 28 <func1+0x28>
8: e0801001 add r1, r0, r1
c: e1a03000 mov r3, r0
10: e3a00000 mov r0, #0
14: e4d32001 ldrb r2, [r3], #1
18: e1530001 cmp r3, r1
1c: e0800002 add r0, r0, r2
20: 1afffffb bne 14 <func1+0x14>
24: e12fff1e bx lr
28: e1a00001 mov r0, r1
2c: e12fff1e bx lr
ここを見ると、r0 と r1 は両方とも、何かが書き込まれる前に読み取られます。また、r2 と r3 が読み取られる前に書き込まれていることもわかります。func1
したがって、には最大 2 つのパラメーターがあると推測できます。
また、r0 が r3 に移動され、ldrb
メモリからバイトをロードする命令である へのアドレスとして使用されることもわかります。したがって、最初のパラメーターはポインターであると推測します。この命令は 1 バイトしかロードしないため、何らかの 1 バイト データ型へのポインタである可能性があることもわかります。
r1 の 2 番目のパラメーターは、比較命令と加算命令以外では使用されないように見えるため、整数である可能性があります。
それぞれbx lr
(return-to-caller 命令) の前に、何かが r0 に配置されるため、関数が何らかの値を返すと推測されます。
この関数が提示された場合、関数のプロトタイプは次のようになると思います。
int func1(unsigned char *, int);
オリジナル:
unsigned int func1(void *, unsigned int);
ここに別の関数があります
00000030 <func2>:
30: e0822001 add r2, r2, r1
34: e5c02000 strb r2, [r0]
38: e12fff1e bx lr
これはとても簡単です。
r0、r1、r2 はすべて、書き込まれる前に読み取られることがわかります。したがって、関数が 3 つのパラメーターを受け取ることが推測できます。strb
r0 は命令 (ストアバイト)へのアドレスとして使用されるため、おそらくポインターです。繰り返しますが、1バイトしか格納しないため、おそらくバイトサイズのデータ型へのポインターです。
他の 2 つは加算命令でのみ使用されるため、おそらく整数です。
最後に r0 には何も配置されていないように見えるため、関数は最初のパラメーターを返すか、値を返しません。
プロトタイプは次のいずれかになると思います
void func2(unsigned char *, int, int);
unsigned char *func2(unsigned char *, int, int);
オリジナル:
void func2(char *, char, char);
呼び出し元/呼び出し先の規則はプロセッサの命令セットごとに異なり、c ライブラリと c++ ライブラリを一緒に使用しているときに名前マングリングを既に認識していることを念頭に置いて、次の方法を試すことができます。
gdb <executable>
....
disas <function name>
....
Here you can make a wild guess about the type of return value and parameters using the bit size of those values written on stack making use of assembly code.