分解されたバイナリ爆弾フェーズ3のロジックを理解する難しさ


8

バイナリ爆弾ラボから次のアセンブリプログラムがあります。目的は、explode_bomb関数をトリガーせずにバイナリを実行するために必要なキーワードを決定することです。このプログラムのアセンブリの分析についてコメントしましたが、すべてを一緒に結合するのに問題があります。

必要な情報はすべて揃っていると思いますが、それでも実際の根本的なロジックを確認できず、行き詰まっています。どんな助けにも感謝します!

以下は、逆アセンブルされたプログラム自体です。

0x08048c3c <+0>:     push   %edi
   0x08048c3d <+1>:     push   %esi
   0x08048c3e <+2>:     sub    $0x14,%esp
   0x08048c41 <+5>:     movl   $0x804a388,(%esp)
   0x08048c48 <+12>:    call   0x80490ab <string_length>
   0x08048c4d <+17>:    add    $0x1,%eax
   0x08048c50 <+20>:    mov    %eax,(%esp)
   0x08048c53 <+23>:    call   0x8048800 <malloc@plt>
   0x08048c58 <+28>:    mov    $0x804a388,%esi
   0x08048c5d <+33>:    mov    $0x13,%ecx
   0x08048c62 <+38>:    mov    %eax,%edi
   0x08048c64 <+40>:    rep movsl %ds:(%esi),%es:(%edi)
   0x08048c66 <+42>:    movzwl (%esi),%edx
   0x08048c69 <+45>:    mov    %dx,(%edi)
   0x08048c6c <+48>:    movzbl 0x11(%eax),%edx
   0x08048c70 <+52>:    mov    %dl,0x10(%eax)
   0x08048c73 <+55>:    mov    %eax,0x4(%esp)
   0x08048c77 <+59>:    mov    0x20(%esp),%eax
   0x08048c7b <+63>:    mov    %eax,(%esp)
   0x08048c7e <+66>:    call   0x80490ca <strings_not_equal>
   0x08048c83 <+71>:    test   %eax,%eax
   0x08048c85 <+73>:    je     0x8048c8c <phase_3+80>
   0x08048c87 <+75>:    call   0x8049363 <explode_bomb>
   0x08048c8c <+80>:    add    $0x14,%esp
   0x08048c8f <+83>:    pop    %esi
   0x08048c90 <+84>:    pop    %edi
   0x08048c91 <+85>:    ret  

次のブロックには私の分析が含まれています

  5 <phase_3>
  6 0x08048c3c <+0>:     push   %edi // push value in edi to stack
  7 0x08048c3d <+1>:     push   %esi // push value of esi to stack
  8 0x08048c3e <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
  9 
 10 0x08048c41 <+5>:     movl   $0x804a388,(%esp) // put 0x804a388 into loc esp points to
 11 
 12 0x08048c48 <+12>:    call   0x80490ab <string_length> // check string length, store in eax
 13 0x08048c4d <+17>:    add    $0x1,%eax // increment val in eax by 0x1 (str len + 1) 
 14 // at this point, eax = str_len + 1  = 77 + 1 = 78
 15 
 16 0x08048c50 <+20>:    mov    %eax,(%esp) // get val in eax and put in loc on stack
 17 //**** at this point, 0x804a388 should have a value of 78? ****
 18 
 19 0x08048c53 <+23>:    call   0x8048800 <malloc@plt> // malloc --> base ptr in eax
 20 
 21 0x08048c58 <+28>:    mov    $0x804a388,%esi // 0x804a388 in esi 
 22 0x08048c5d <+33>:    mov    $0x13,%ecx // put 0x13 in ecx (counter register)
 23 0x08048c62 <+38>:    mov    %eax,%edi // put val in eax into edi
 24 0x08048c64 <+40>:    rep movsl %ds:(%esi),%es:(%edi) // repeat 0x13 (19) times
 25 // **** populate malloced memory with first 19 (edit: 76) chars of string at 0x804a388 (this string is 77 characters long)? ****
 26 
 27 0x08048c66 <+42>:    movzwl (%esi),%edx // put val in loc esi points to into edx
***** // at this point, edx should contain the string at 0x804a388?
 28 
 29 0x08048c69 <+45>:    mov    %dx,(%edi) // put val in dx to loc edi points to
***** // not sure what effect this has or what is in edi at this point
 30 0x08048c6c <+48>:    movzbl 0x11(%eax),%edx // edx = [eax + 0x11]
 31 0x08048c70 <+52>:    mov    %dl,0x10(%eax) // [eax + 0x10] = dl
 32 0x08048c73 <+55>:    mov    %eax,0x4(%esp) // [esp + 0x4] = eax
 33 0x08048c77 <+59>:    mov    0x20(%esp),%eax // eax = [esp + 0x20]
 34 0x08048c7b <+63>:    mov    %eax,(%esp) // put val in eax into loc esp points to
***** // not sure what effect these movs have
 35 
 36 // edi --> first arg
 37 // esi --> second arg
 38 // compare value in esi to edi
 39 0x08048c7e <+66>:    call   0x80490ca <strings_not_equal> // store result in eax
 40 0x08048c83 <+71>:    test   %eax,%eax 
 41 0x08048c85 <+73>:    je     0x8048c8c <phase_3+80>
 42 0x08048c87 <+75>:    call   0x8049363 <explode_bomb>
 43 0x08048c8c <+80>:    add    $0x14,%esp
 44 0x08048c8f <+83>:    pop    %esi
 45 0x08048c90 <+84>:    pop    %edi
 46 0x08048c91 <+85>:    ret 

更新:

strings_not_equalが呼び出される前にレジスターを検査すると、次の結果が得られます。

eax            0x804d8aa        134535338
ecx            0x0      0
edx            0x76     118
ebx            0xffffd354       -11436
esp            0xffffd280       0xffffd280
ebp            0xffffd2b8       0xffffd2b8
esi            0x804a3d4        134521812
edi            0x804f744        134543172
eip            0x8048c7b        0x8048c7b <phase_3+63>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99

そして、私はホッパーを使用して次の逆アセンブルされた疑似コードを取得します:

ここに画像の説明を入力してください

eaxにある数字と先に見た文字列の両方をキーワードとして使用してみましたが、どちらも機能しませんでした。


3
よくコメントされた逆アセンブリと、コードが何をしていると思うかについての詳細なメモを投稿していただきありがとうございます。爆弾ラボの質問を投稿するときに、もっと多くの人がこのような努力を注ぐとし
たら

2
rep movsl32ビットのロングワードをアドレスからアドレス%esiにコピーし%edi、それぞれに4ずつ、に等しい回数だけインクリメントしecxます。と考えてくださいmemcpy(edi, esi, ecx*4)。参照してくださいfelixcloutier.com/x86/movs:movsb:movsw:movsd:movsq(それはだmovsdインテル表記で)。
Nate Eldredge

2
つまり、19バイトではなく、19 * 4 = 76バイトです。
Nate Eldredge

@NateEldredgeああ、メモリ位置の文字列0x80490abの長さが77 であるため、それは理にかなっていると思います。そしてリンクに感謝します!
scy8

@fuzありがとうございます。残念ながら、現時点ではすべてをまとめることはできません
scy8

回答:


3

この関数は、静的ストレージからmallocされたバッファに文字列の変更されたコピーを作成します。


これは奇妙に見えます。mallocサイズはに依存しているstrlen1が、memcpyサイズはコンパイル時定数でありますか?あなたの逆コンパイルは明らかにアドレスが文字列リテラルであったことを示しているので、それは問題ないようです。

おそらくstring_length()、別の関数でしか定義されていないカスタム関数が原因で、最適化が行われなかった可能性があります.c(爆弾は、ファイル間インライン化のためのリンク時最適化なしでコンパイルされました)。したがって、これsize_t len = string_length("some string literal");はコンパイル時の定数ではなく、コンパイラは文字列の既知の定数の長さを使用する代わりに、呼び出しを発行しました。

しかし、おそらくそれらstrcpyはソースで使用され、コンパイラはそれをとしてインライン化しましたrep movs。文字列リテラルからコピーしているように見えるため、長さはコンパイル時の定数であり、strcpy通常実行する必要がある作業の一部を最適化することができます。通常、すでに長さmemcpystrcpy計算している場合は、その場で再度計算するのではなく、使用する方が良いですが、この場合、実際に戻り値をstring_lengthに渡した場合よりも、コンパイラがその部分のコードを改善するのに役立ちます。memcpy、再びstring_lengthインライン化して最適化できなかったためです。


   <+0>:     push   %edi // push value in edi to stack
   <+1>:     push   %esi // push value of esi to stack
   <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)

そのようなコメントは冗長です。指示自体はすでにそれを言っています。これは、関数がそれらを内部で使用して後で復元できるように、2つの呼び出し保存レジスタを保存しています。

あなたのコメントsubは良いです。はい、スタックの成長は、ここではより高いレベルの意味論的意味です。この関数は、ローカル(および関数の引数movpushedの代わりに格納される)のためにいくつかの領域を予約します。


rep movsdコピー0x13に* 4バイトは、コピーされた領域の終端を越えて点までESIとEDIをインクリメントします。したがって、別のmovsd命令が前のコピーに隣接する別の4バイトをコピーします。

コードは実際には別の2をコピーしますがmovsw、を使用する代わりに、movzwワードロードとmovストアを使用します。 これにより、合計78バイトがコピーされます。

  ...
      # at this point EAX = malloc return value which I'll call buf
<+28>:    mov    $0x804a388,%esi            # copy src = a string literal in .rodata?
<+33>:    mov    $0x13,%ecx
<+38>:    mov    %eax,%edi                  # copy dst = buf
<+40>:    rep movsl %ds:(%esi),%es:(%edi)   # memcpy 76 bytes and advance ESI, EDI

<+42>:    movzwl (%esi),%edx
<+45>:    mov    %dx,(%edi)        # copy another 2 bytes (not moving ESI or EDI)
 # final effect: 78-byte memcpy

一部の(すべてではない)CPUでは、使用するrep movsbrep movsw適切なカウントを使用するのが効率的でしたが、この場合、コンパイラはこれを選択しませんでした。 movzx別名AT&T movzは、部分的なレジスターのペナルティなしで狭いロードを実行するための良い方法です。コンパイラがそれを行うのはそのためです。ストア命令でそのregの下位8ビットまたは16ビットを読み取るだけの場合でも、完全なレジスタを書き込むことができます。

文字列リテラルをbufにコピーした後、で文字をコピーするバイトロード/ストアがありbufます。この時点で、EAXはまだ戻り値bufであるを指していmallocます。 つまり、文字列リテラルの変更されたコピーを作成しています。

<+48>:    movzbl 0x11(%eax),%edx
<+52>:    mov    %dl,0x10(%eax)             # buf[16] = buf[17]

おそらく、ソースが一定の伝播を無効にしておらず、最適化レベルが十分に高い場合、コンパイラは最後の文字列を.rodata見つけられる場所に配置し、この爆弾フェーズを簡単にするかもしれません。:P

次に、ポインタを文字列比較用のスタック引数として格納します。

<+55>:    mov    %eax,0x4(%esp)               # 2nd arg slot = EAX = buf
<+59>:    mov    0x20(%esp),%eax              #  function arg = user input?
<+63>:    mov    %eax,(%esp)                  # first arg slot = our incoming stack arg
<+66>:    call   0x80490ca <strings_not_equal>

「チート」する方法:GDBでランタイム結果を確認する

一部の爆弾ラボでは、爆弾を記録するテストサーバーでのみ、爆弾をオンラインで実行できます。GDBの下では実行できず、静的逆アセンブリ(のようなobjdump -drwC -Mintel)のみを使用します。したがって、テストサーバーは、失敗した試行の回数を記録できます。たとえば、Googleで見つけたcs.virginia.eduのCS 3330のように、完全なクレジットで20回未満の爆発が必要です。

GDBを使用して関数の途中でメモリ/レジスタを調べると、静的分析から作業するよりもはるかに簡単になり、実際には、単一の入力が最後にのみチェックされるこの関数は簡単になります。たとえば、他に渡されている引数を確認しstrings_not_equalます。(特に、GDB jumpまたはset $pc = ...コマンドを使用して爆弾の爆発チェックをスキップする場合。)

の呼び出しの直前にブレークポイントまたはシングルステップを設定しますstrings_not_equalp (char*)$eaxEAXをaとして扱いchar*、そのアドレスで始まる(0で終わる)C文字列を表示するために 使用します。この時点で、ストアからスタックまでを見るとわかるように、EAXはバッファーのアドレスを保持しています。

その文字列の結果をコピーして貼り付ければ完了です。

通常、複数の数値入力を伴う他のフェーズは、デバッガを使用して簡単に実行できず、少なくともいくつかの計算が必要ですが、リストトラバーサルの正しい順序で一連の数値を指定する必要があるリンクリストフェーズも、デバッガーを使用してレジスターを設定し、比較に到達したときに比較を成功させる方法を知っています。


どうもありがとうございます!やっと理解しました。
mlz7

これも私の混乱を解消しました!ありがとうございました!
scy8

2

rep movsl32ビットのロングワードをアドレスからアドレス%esiにコピーし%edi、それぞれに4ずつ、に等しい回数だけインクリメントし%ecxます。と考えてくださいmemcpy(edi, esi, ecx*4)

https://felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq(Intel表記ではmovsd)を参照してください

したがって、これは19*4=76バイトをコピーしています。


おかげで、他にも少し混乱していたことがありました(下部にリストされています)。理解を深めるために役立つと
思い
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.