土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

x86-64 ELF(Linux)のアセンブラをGDBでデバッグしながら理解する(GDBコマンド、関連ツールもまとめておく)

前回 は、「入門セキュリティコンテストーーCTFを解きながら学ぶ実戦技術」を読んで、CTF の各ジャンルごとに使われている技術やツールについて、調べたり、実際に使ってみたりしました。

今回は、x86-64 ELF(Linux)のアセンブラを理解していきます。また、よく使う GDBコマンドや、バイナリに対してよく使うコマンド、x86-64 のよく使う命令を書きとめておこうと思います。

それでは、やっていきます。

参考文献

Ghidra の解説が主ですが、冒頭に、x86、x86-64 のアーキテクチャ、アセンブラが解説されています。今回は、この解説をもとに書いています。

GDB の本は少ないのですが、CQ出版の古い書籍は、よくまとまっていると思います。GDB のコマンドについても、ある程度、書いてくれているのですが、短縮版も書いててほしかったです。

はじめに

「セキュリティ」の記事一覧です。良かったら参考にしてください。

セキュリティの記事一覧
・第1回:Ghidraで始めるリバースエンジニアリング(環境構築編)
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方とGPUで実行したときの時間を見積もってみる
・第21回:Scapyの環境構築とネットワークプログラミング
・第22回:CpawCTF2にチャレンジします(この記事はクリア状況を随時更新します)
・第23回:K&Rのmalloc関数とfree関数を理解する
・第24回:C言語、アセンブラでシェルを起動するプログラムを作る(ARM64)
・第25回:機械語でシェルを起動するプログラムを作る(ARM64)
・第26回:入門セキュリhttps://github.com/SECCON/SECCON2017_online_CTF.gitティコンテスト(CTFを解きながら学ぶ実践技術)を読んだ
・第27回:x86-64 ELF(Linux)のアセンブラをGDBでデバッグしながら理解する(GDBコマンド、関連ツールもまとめておく) ← 今回

普通の GDB はデバッグするには不便なので、最初に gdb-peda を導入しておきます。

環境は、VirtualBox+ParrotOS 6.1 です。

それでは、やっていきます。

gdb-pedaの導入方法

gdb-peda の公式の GitHub は以下です。

github.com

Installation に、導入方法が書かれていますので、それに従います(インストール先は変更しています)。

$ git clone https://github.com/longld/peda.git
$ echo "source ~/Downloads/peda/peda.py" >> ~/.gdbinit

では、gdb-peda を導入した状態で、GDB を起動してみます。

$ gdb -q exec_me_revenge

この後、start を実行すると、以下のように、レジスタ、コード、スタックが常に表示されるようになります。とても便利ですね。

gdb-pedaでstartを実行したところ
gdb-pedaでstartを実行したところ

gdb-peda の導入方法は以上です。

簡単なプログラムでx86-64 ELFをGDBでデバッグを開始してみる

簡単な C言語のプログラムを自分で書いてみました。これを使って、GDB で動かしながら、x86-64 ELF の動作を理解していきます。

使用する簡単なC言語プログラム

main関数から、sub関数を呼び出し、sub関数の中で、printf関数を実行、scanf関数を実行して、数値を受け取り、戻り値でmain関数に返します。main関数は、戻り値が 0 超ならシステムに 0 を返し、それ以外なら 1 を返します。

#include <stdio.h>

int sub( void )
{
  int data;
  
  printf( "input data: " );
  
  scanf( "%d", &data );
  
  return data;
}

int main( int argc, void *argv[] )
{
  int ret;
  
  ret = sub();
  
  if( ret > 0 )
    return 0;
  else
    return 1;
}

簡単に実行してみます。

$ gcc -g -o hello_world.out hello_world.c

$ ./hello_world.out 
input data: 0

$ echo $?
1

$ ./hello_world.out 
input data: 1

$ echo $?
0

想定している通りに動作しているようです。

プログラムの概要を調べる

GDB で動作を確認する前に、プログラムの概要を調べます。

ELFヘッダによると、エントリポイントは、0x1060 です。セクションヘッダの textセクションも、0x1060 から始まっています。

$ file hello_world.out 
hello_world.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=89d00684582cd697b573c0fd49c38d4f17146450, for GNU/Linux 3.2.0, with debug_info, not stripped

$ readelf -h hello_world.out 
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:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1060
  Start of program headers:          64 (bytes into file)
  Start of section headers:          15088 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         37
  Section header string table index: 36

$ readelf -l hello_world.out 

Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1060
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000670 0x0000000000000670  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000 0x00000000000001b9 0x00000000000001b9  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000114 0x0000000000000114  R      0x1000
  LOAD           0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0 0x0000000000000250 0x0000000000000258  RW     0x1000
  DYNAMIC        0x0000000000002de0 0x0000000000003de0 0x0000000000003de0 0x00000000000001e0 0x00000000000001e0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014 0x0000000000000034 0x0000000000000034  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0 0x0000000000000230 0x0000000000000230  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got 

$ readelf -S hello_world.out 
There are 37 section headers, starting at offset 0x3af0:

Section Headers:
  [Nr] Name              Type             Address           Offset    Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000  0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000318  00000318  000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.pr[...] NOTE             0000000000000338  00000338  0000000000000020  0000000000000000   A       0     0     8
  [ 3] .note.gnu.bu[...] NOTE             0000000000000358  00000358  0000000000000024  0000000000000000   A       0     0     4
  [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c  0000000000000020  0000000000000000   A       0     0     4
  [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0  0000000000000024  0000000000000000   A       6     0     8
  [ 6] .dynsym           DYNSYM           00000000000003c8  000003c8  00000000000000c0  0000000000000018   A       7     1     8
  [ 7] .dynstr           STRTAB           0000000000000488  00000488  00000000000000a8  0000000000000000   A       0     0     1
  [ 8] .gnu.version      VERSYM           0000000000000530  00000530  0000000000000010  0000000000000002   A       6     0     2
  [ 9] .gnu.version_r    VERNEED          0000000000000540  00000540  0000000000000040  0000000000000000   A       7     1     8
  [10] .rela.dyn         RELA             0000000000000580  00000580  00000000000000c0  0000000000000018   A       6     0     8
  [11] .rela.plt         RELA             0000000000000640  00000640  0000000000000030  0000000000000018  AI       6    24     8
  [12] .init             PROGBITS         0000000000001000  00001000  0000000000000017  0000000000000000  AX       0     0     4
  [13] .plt              PROGBITS         0000000000001020  00001020  0000000000000030  0000000000000010  AX       0     0     16
  [14] .plt.got          PROGBITS         0000000000001050  00001050  0000000000000008  0000000000000008  AX       0     0     8
  [15] .text             PROGBITS         0000000000001060  00001060  0000000000000150  0000000000000000  AX       0     0     16
  [16] .fini             PROGBITS         00000000000011b0  000011b0  0000000000000009  0000000000000000  AX       0     0     4
  [17] .rodata           PROGBITS         0000000000002000  00002000  0000000000000014  0000000000000000   A       0     0     4
  [18] .eh_frame_hdr     PROGBITS         0000000000002014  00002014  0000000000000034  0000000000000000   A       0     0     4
  [19] .eh_frame         PROGBITS         0000000000002048  00002048  00000000000000cc  0000000000000000   A       0     0     8
  [20] .init_array       INIT_ARRAY       0000000000003dd0  00002dd0  0000000000000008  0000000000000008  WA       0     0     8
  [21] .fini_array       FINI_ARRAY       0000000000003dd8  00002dd8  0000000000000008  0000000000000008  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000003de0  00002de0  00000000000001e0  0000000000000010  WA       7     0     8
  [23] .got              PROGBITS         0000000000003fc0  00002fc0  0000000000000028  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000003fe8  00002fe8  0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000004010  00003010  0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000004020  00003020  0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00003020  000000000000001f  0000000000000001  MS       0     0     1
  [28] .debug_aranges    PROGBITS         0000000000000000  0000303f  0000000000000030  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  0000306f  000000000000012d  0000000000000000           0     0     1
  [30] .debug_abbrev     PROGBITS         0000000000000000  0000319c  00000000000000eb  0000000000000000           0     0     1
  [31] .debug_line       PROGBITS         0000000000000000  00003287  000000000000006c  0000000000000000           0     0     1
  [32] .debug_str        PROGBITS         0000000000000000  000032f3  00000000000000bc  0000000000000001  MS       0     0     1
  [33] .debug_line_str   PROGBITS         0000000000000000  000033af  000000000000003f  0000000000000001  MS       0     0     1
  [34] .symtab           SYMTAB           0000000000000000  000033f0  0000000000000390  0000000000000018          35    18     8
  [35] .strtab           STRTAB           0000000000000000  00003780  0000000000000200  0000000000000000           0     0     1
  [36] .shstrtab         STRTAB           0000000000000000  00003980  000000000000016a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)

GDBでデバッグを開始してみる

では、早速起動してみます。起動すると、以下のように、入力待ちの状態になります。

$ gdb -q hello_world.out
: no key sequence terminator:
Reading symbols from hello_world.out...
gdb-peda$

ここで、シンボルが残ってる(main関数が分かる)場合は、start を実行すると main関数の先頭で止まってくれます。一方、run を実行すると、main関数では止まらず、ブレークポイントで止まる、もしくは、プログラムの最後まで実行されます。

start を実行してみます。情報量多いですね。

Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

[----------------------------------registers-----------------------------------]
RAX: 0x555555555185 (<main>:    push   rbp)
RBX: 0x7fffffffe358 --> 0x7fffffffe5da ("/home/user/svn/experiment/c/hello_world.out")
RCX: 0x555555557dd8 --> 0x555555555100 (<__do_global_dtors_aux>:        endbr64)
RDX: 0x7fffffffe368 --> 0x7fffffffe606 ("SHELL=/bin/bash")
RSI: 0x7fffffffe358 --> 0x7fffffffe5da ("/home/user/svn/experiment/c/hello_world.out")
RDI: 0x1
RBP: 0x7fffffffe240 --> 0x1
RSP: 0x7fffffffe220 --> 0x7fffffffe358 --> 0x7fffffffe5da ("/home/user/svn/experiment/c/hello_world.out")
RIP: 0x555555555194 (<main+15>: call   0x555555555149 <sub>)
R8 : 0x0
R9 : 0x7ffff7fcf680 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7fcb878 --> 0xc00120000000e
R11: 0x7ffff7fe1930 (<_dl_audit_preinit>:       mov    eax,DWORD PTR [rip+0x1b4e2]        # 0x7ffff7ffce18 <_rtld_global_ro+888>)
R12: 0x0
R13: 0x7fffffffe368 --> 0x7fffffffe606 ("SHELL=/bin/bash")
R14: 0x555555557dd8 --> 0x555555555100 (<__do_global_dtors_aux>:        endbr64)
R15: 0x7ffff7ffd020 --> 0x7ffff7ffe2e0 --> 0x555555554000 --> 0x10102464c457f
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x555555555189 <main+4>:     sub    rsp,0x20
   0x55555555518d <main+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x555555555190 <main+11>:    mov    QWORD PTR [rbp-0x20],rsi
=> 0x555555555194 <main+15>:    call   0x555555555149 <sub>
   0x555555555199 <main+20>:    mov    DWORD PTR [rbp-0x4],eax
   0x55555555519c <main+23>:    cmp    DWORD PTR [rbp-0x4],0x0
   0x5555555551a0 <main+27>:    jle    0x5555555551a9 <main+36>
   0x5555555551a2 <main+29>:    mov    eax,0x0
No argument
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe220 --> 0x7fffffffe358 --> 0x7fffffffe5da ("/home/user/svn/experiment/c/hello_world.out")
0008| 0x7fffffffe228 --> 0x100000000
0016| 0x7fffffffe230 --> 0x0
0024| 0x7fffffffe238 --> 0x0
0032| 0x7fffffffe240 --> 0x1
0040| 0x7fffffffe248 --> 0x7ffff7df124a (<__libc_start_call_main+122>:  mov    edi,eax)
0048| 0x7fffffffe250 --> 0x0
0056| 0x7fffffffe258 --> 0x555555555185 (<main>:        push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Temporary breakpoint 1, main (argc=0x1, argv=0x7fffffffe358) at hello_world.c:18
18        ret = sub();
gdb-peda$

GDB では、ASLR(Address Space Layout Randomization)は、デフォルトで無効になっている(有効にすることもできる)ので、毎回同じアドレス配置になります。

また、最後まで実行した状態で、もう一度、run などを実行すると、再実行することが出来ます。

x86-64の基本的な動作を理解する

主要なレジスタの理解

まず、レジスタについて簡単に理解しておきます。

レジスタ名 概要 用途
RIP プログラムカウンタ 現在のプログラムの位置アドレス
RSP スタックポインタ 現在のスタックポインタのアドレス
RBP ベースポインタ 関数内でスタック領域を扱う基準となるアドレス

x86-64 では、関数呼び出しでは、以下の動作を行います。

  1. call命令では、call命令の次の命令のアドレスをスタックに push して、RIP を関数の先頭アドレスにセットする
  2. 呼び出し元で使用していた RBP をスタックに push する
  3. 現在の RSP を RBP にセットする

この後、関数の内部では、RBP を基準としてスタックを扱います。

また、関数の最後では以下の動作を行います。1. と 2. は leave命令でも同じ動作になります。

  1. RBP を RSP にセットする(RSP は上の関数呼び出しの 3. に戻る)
  2. スタックを RBP に pop する(RBP も関数呼び出し時の状態に戻る)
  3. ret命令では、現在のスタック(上の関数呼び出しの 1. で保存していた call命令の次の命令のアドレス)を pop して RIP にセットする

これにより、関数の呼び出し元では、call命令の実行前と実行後で、RSP、RBP が同じ状態が保持されます。

スタック

大きいアドレスから小さいアドレスに向かって、スタックは使われていきます。

関数の呼び出し規約

x86-64 の関数の呼び出し規約は、x86 と異なります。

x86 は、引数は逆順(3つの引数のとき、第3引数→第2引数→第1引数の順)で、全てスタックに積まれます。戻り値は EAX に格納され、関数の呼び出し元がスタックを開放します。

x86-64 では、引数は第6引数までレジスタで渡され、それ以上はスタックに積まれます。レジスタの順は、以下の通りです。

第1引数 第2引数 第3引数 第4引数 第5引数 第6引数
RDI RSI RDX RCX R8 R9

戻り値は RAX に格納されます。

破壊(揮発性)レジスタと非破壊(不揮発性)レジスタ

  • 破壊(揮発性)レジスタ:RAX、RCX、RDX、R8~R11、XMM0~XMM5
  • 非破壊(不揮発性)レジスタ:RBX、RBP、RDI、RSI、RSP、R12~R15、XMM6~XMM15

簡単なプログラムでx86-64 ELFをGDBでデバッグしてみる

これまでを踏まえて、理解したアセンブラの内容を書いていきます。

main関数のアセンブラの内容

まず、main関数です。

最初の2行は、main関数であっても、お決まりの2行です。その後の sub rsp,0x20 は、main関数で使用するローカル変数のために、スタックを確保しています。

mov DWORD PTR [rbp-0x14],edimov QWORD PTR [rbp-0x20],rsi は、確保したスタックにレジスタの値を退避しているのだと思いますが、理由は分かりません。RDI と RSI は、非破壊レジスタなので、上位でケアする必要はないはずです。また、main関数として退避してるのかと思いましたが、使用していないので必要ないはずです。

call命令で、sub関数を呼び出し、その後、戻り値が EAX に入ってるので、確保したスタックに格納しています。cmp命令で 0 と比較して、jle命令で分岐します。

cmp命令と test命令はステータスレジスタに結果を反映するだけで結果はレジスタに保存しません。jle命令は、最初に Jump の J が付いてるので、ジャンプ命令で、le は(たぶんですが)less than equal なので、小さいか等しい場合にジャンプします。

つまり、sub関数の戻り値が、0 と比べて、小さい、もしくは、等しい場合に main+36(1 を返す方)にジャンプします。そうでなければ、0 を返す方を通り、main+41 にジャンプします。

最後は、こちらも、main関数であっても、お決まりの2行です。

gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000555555555185 <+0>:     push   rbp
   0x0000555555555186 <+1>:     mov    rbp,rsp
   0x0000555555555189 <+4>:     sub    rsp,0x20
   0x000055555555518d <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x0000555555555190 <+11>:    mov    QWORD PTR [rbp-0x20],rsi
=> 0x0000555555555194 <+15>:    call   0x555555555149 <sub>
   0x0000555555555199 <+20>:    mov    DWORD PTR [rbp-0x4],eax
   0x000055555555519c <+23>:    cmp    DWORD PTR [rbp-0x4],0x0
   0x00005555555551a0 <+27>:    jle    0x5555555551a9 <main+36>
   0x00005555555551a2 <+29>:    mov    eax,0x0
   0x00005555555551a7 <+34>:    jmp    0x5555555551ae <main+41>
   0x00005555555551a9 <+36>:    mov    eax,0x1
   0x00005555555551ae <+41>:    leave
   0x00005555555551af <+42>:    ret
End of assembler dump.

sub関数のアセンブラの内容

続いて、sub関数です。上で説明したものは省略します。

lea rax,[rip+0xeac] # 0x555555556004 は、RIP+0xEAC のアドレスを RAX に設定します。RIP は、1つ進んだところ(0x0000555555555158)になります。コメントの通り、結果は、0x555555556004 になります。GDB で、そのアドレスを見てみました。printf関数の引数が入っていました。

gdb-peda$ x/s 0x555555556004
0x555555556004: "input data: "

引数を RDI に格納して、printf関数を呼び出しています。その後、scanf関数を呼び出すために、また lea命令があります。第2引数から準備しています。スタックのアドレス(ローカル変数)を RSI にセットしています。第1引数については、一応内容を確認しておきます。正しく、%d が入っていました。

$ x/s 0x555555556011
0x555555556011: "%d"

あとは、sub関数の戻り値として、scanf関数の結果の第2引数の値を戻り値の EAX にセットして終了です。

gdb-peda$ disas sub
Dump of assembler code for function sub:
   0x0000555555555149 <+0>:     push   rbp
   0x000055555555514a <+1>:     mov    rbp,rsp
   0x000055555555514d <+4>:     sub    rsp,0x10
   0x0000555555555151 <+8>:     lea    rax,[rip+0xeac]        # 0x555555556004
   0x0000555555555158 <+15>:    mov    rdi,rax
   0x000055555555515b <+18>:    mov    eax,0x0
   0x0000555555555160 <+23>:    call   0x555555555030 <printf@plt>
   0x0000555555555165 <+28>:    lea    rax,[rbp-0x4]
   0x0000555555555169 <+32>:    mov    rsi,rax
   0x000055555555516c <+35>:    lea    rax,[rip+0xe9e]        # 0x555555556011
   0x0000555555555173 <+42>:    mov    rdi,rax
   0x0000555555555176 <+45>:    mov    eax,0x0
   0x000055555555517b <+50>:    call   0x555555555040 <__isoc99_scanf@plt>
   0x0000555555555180 <+55>:    mov    eax,DWORD PTR [rbp-0x4]
   0x0000555555555183 <+58>:    leave
   0x0000555555555184 <+59>:    ret
End of assembler dump.

簡単なプログラムをstripしてGDBでデバッグしてみる

これまでは、strip されていない(デバッグ情報が残っている)プログラムを扱ってきましたが、普通は strip されている(デバッグ情報は残っていない)と思います。ここからは、先ほどのプログラムを strip して、GDB でデバッグしてみます。

$ cp hello_world.out hello_world_strip.out
$ strip hello_world_strip.out
$ ll hello_world.out hello_world_strip.out
-rwxr-xr-x 1 user user 18K Sep  7 22:19 hello_world.out*
-rwxr-xr-x 1 user user 15K Sep  8 17:59 hello_world_strip.out*

プログラムの概要を調べる

strip していない、デバッグ情報のあるプログラムでは、readelf の情報を使わなくてもデバッグ出来ましたが、strip されたプログラムの場合は、この情報が重要になります。

エントリポイントは、先ほどと同じで、0x1060 から始まっています。

$ file hello_world_strip.out 
hello_world_strip.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=89d00684582cd697b573c0fd49c38d4f17146450, for GNU/Linux 3.2.0, stripped

$ readelf -h hello_world_strip.out
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:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1060
  Start of program headers:          64 (bytes into file)
  Start of section headers:          12624 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

$ readelf -l hello_world_strip.out

Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1060
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000670 0x0000000000000670  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000 0x00000000000001b9 0x00000000000001b9  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000114 0x0000000000000114  R      0x1000
  LOAD           0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0 0x0000000000000250 0x0000000000000258  RW     0x1000
  DYNAMIC        0x0000000000002de0 0x0000000000003de0 0x0000000000003de0 0x00000000000001e0 0x00000000000001e0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014 0x0000000000000034 0x0000000000000034  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0 0x0000000000000230 0x0000000000000230  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got 

$ readelf -S hello_world_strip.out
There are 29 section headers, starting at offset 0x3150:

Section Headers:
  [Nr] Name              Type             Address           Offset    Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000  0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000318  00000318  000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.pr[...] NOTE             0000000000000338  00000338  0000000000000020  0000000000000000   A       0     0     8
  [ 3] .note.gnu.bu[...] NOTE             0000000000000358  00000358  0000000000000024  0000000000000000   A       0     0     4
  [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c  0000000000000020  0000000000000000   A       0     0     4
  [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0  0000000000000024  0000000000000000   A       6     0     8
  [ 6] .dynsym           DYNSYM           00000000000003c8  000003c8  00000000000000c0  0000000000000018   A       7     1     8
  [ 7] .dynstr           STRTAB           0000000000000488  00000488  00000000000000a8  0000000000000000   A       0     0     1
  [ 8] .gnu.version      VERSYM           0000000000000530  00000530  0000000000000010  0000000000000002   A       6     0     2
  [ 9] .gnu.version_r    VERNEED          0000000000000540  00000540  0000000000000040  0000000000000000   A       7     1     8
  [10] .rela.dyn         RELA             0000000000000580  00000580  00000000000000c0  0000000000000018   A       6     0     8
  [11] .rela.plt         RELA             0000000000000640  00000640  0000000000000030  0000000000000018  AI       6    24     8
  [12] .init             PROGBITS         0000000000001000  00001000  0000000000000017  0000000000000000  AX       0     0     4
  [13] .plt              PROGBITS         0000000000001020  00001020  0000000000000030  0000000000000010  AX       0     0     16
  [14] .plt.got          PROGBITS         0000000000001050  00001050  0000000000000008  0000000000000008  AX       0     0     8
  [15] .text             PROGBITS         0000000000001060  00001060  0000000000000150  0000000000000000  AX       0     0     16
  [16] .fini             PROGBITS         00000000000011b0  000011b0  0000000000000009  0000000000000000  AX       0     0     4
  [17] .rodata           PROGBITS         0000000000002000  00002000  0000000000000014  0000000000000000   A       0     0     4
  [18] .eh_frame_hdr     PROGBITS         0000000000002014  00002014  0000000000000034  0000000000000000   A       0     0     4
  [19] .eh_frame         PROGBITS         0000000000002048  00002048  00000000000000cc  0000000000000000   A       0     0     8
  [20] .init_array       INIT_ARRAY       0000000000003dd0  00002dd0  0000000000000008  0000000000000008  WA       0     0     8
  [21] .fini_array       FINI_ARRAY       0000000000003dd8  00002dd8  0000000000000008  0000000000000008  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000003de0  00002de0  00000000000001e0  0000000000000010  WA       7     0     8
  [23] .got              PROGBITS         0000000000003fc0  00002fc0  0000000000000028  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000003fe8  00002fe8  0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000004010  00003010  0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000004020  00003020  0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00003020  000000000000001f  0000000000000001  MS       0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  0000303f  000000000000010a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)

Ghidraを使ってmain関数のアドレスを特定する

hello_world_strip.out について、Ghidra を使って解析します。

Ghidra の環境構築と使い方については、以下を参照してください。

daisuke20240310.hatenablog.com

daisuke20240310.hatenablog.com

Ghidra を起動して、hello-world-strip という名前でプロジェクトを作り、hello_world_strip.out を解析させます。解析が終わると、entry が表示されました。なお、Window → Memory Map を開き、家のアイコンをクリックして、Base Image Address は、0 に変更しました。

逆コンパイル画面を見ると、__libc_start_main() が見えます。第1引数が main関数なので、FUN_00001185 をダブルクリックします。

Ghidraでentryが表示されたところ
Ghidraでentryが表示されたところ

すると、main関数が表示されました。main関数とは書いてませんが、先ほどと同じアセンブラコードが並んでいます。

Ghidraでmain関数が表示されたところ
Ghidraでmain関数が表示されたところ

main関数の先頭アドレスは、ファイルの先頭から 0x1185 にあることが分かりました。

GDBでデバッグを開始してみる

では、GDB を起動してみます。先ほどと違って、シンボル情報が読み込まれませんでした。

$ gdb -q hello_world_strip.out
: no key sequence terminator:
Reading symbols from hello_world_strip.out...
(No debugging symbols found in hello_world_strip.out)
gdb-peda$

まず、先ほどと同じように、start を実行してみます。_start で止まってくれました。

gdb-peda$ start

[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x0
RSI: 0x0
RDI: 0x0
RBP: 0x0
RSP: 0x7fffffffe340 --> 0x1
RIP: 0x7ffff7fe5a40 (<_start>:  mov    rdi,rsp)
R8 : 0x0
R9 : 0x0
R10: 0x0
R11: 0x0
R12: 0x0
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7fe5a35 <_dl_help+1285>:      call   0x7ffff7fd1120 <_dl_init_paths>
   0x7ffff7fe5a3a <_dl_help+1290>:      jmp    0x7ffff7fe5560 <_dl_help+48>
   0x7ffff7fe5a3f:      nop
=> 0x7ffff7fe5a40 <_start>:     mov    rdi,rsp
   0x7ffff7fe5a43 <_start+3>:   call   0x7ffff7fe6640 <_dl_start>
   0x7ffff7fe5a48 <_dl_start_user>:     mov    r12,rax
   0x7ffff7fe5a4b <_dl_start_user+3>:   mov    rdx,QWORD PTR [rsp]
   0x7ffff7fe5a4f <_dl_start_user+7>:   mov    rsi,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe340 --> 0x1
0008| 0x7fffffffe348 --> 0x7fffffffe5ce ("/home/user/svn/experiment/c/hello_world_strip.out")
0016| 0x7fffffffe350 --> 0x0
0024| 0x7fffffffe358 --> 0x7fffffffe600 ("SHELL=/bin/bash")
0032| 0x7fffffffe360 --> 0x7fffffffe610 ("NMAP_PRIVILEGED=")
0040| 0x7fffffffe368 --> 0x7fffffffe621 ("PWD=/home/user/svn/experiment/c")
0048| 0x7fffffffe370 --> 0x7fffffffe641 ("LOGNAME=user")
0056| 0x7fffffffe378 --> 0x7fffffffe64e ("XDG_SESSION_TYPE=tty")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Temporary breakpoint 1, 0x00007ffff7fe5a40 in _start () from /lib64/ld-linux-x86-64.so.2
gdb-peda$

ここで、プロセスのマップを調べます。hello_world_strip.out は、0x555555554000 にロードされていることが分かります。先ほど、main関数の位置は、先頭から 0x1185 と分かったので、これらを足すと、0x555555551185 に main関数が存在しているはずです。

gdb-peda$ i proc map
process 81015
Mapped address spaces:

          Start Addr           End Addr       Size     Offset  Perms  objfile
      0x555555554000     0x555555555000     0x1000        0x0  r--p   /home/user/svn/experiment/c/hello_world_strip.out
      0x555555555000     0x555555556000     0x1000     0x1000  r-xp   /home/user/svn/experiment/c/hello_world_strip.out
      0x555555556000     0x555555557000     0x1000     0x2000  r--p   /home/user/svn/experiment/c/hello_world_strip.out
      0x555555557000     0x555555559000     0x2000     0x2000  rw-p   /home/user/svn/experiment/c/hello_world_strip.out
      0x7ffff7fc5000     0x7ffff7fc9000     0x4000        0x0  r--p   [vvar]
      0x7ffff7fc9000     0x7ffff7fcb000     0x2000        0x0  r-xp   [vdso]
      0x7ffff7fcb000     0x7ffff7fcc000     0x1000        0x0  r--p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7fcc000     0x7ffff7ff1000    0x25000     0x1000  r-xp   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7ff1000     0x7ffff7ffb000     0xa000    0x26000  r--p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7ffb000     0x7ffff7fff000     0x4000    0x30000  rw-p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0  rw-p   [stack]
gdb-peda$

main関数を表示してみます。無事に、main関数が表示されました。

gdb-peda$ x/10i 0x555555555185
   0x555555555185:      push   rbp
   0x555555555186:      mov    rbp,rsp
   0x555555555189:      sub    rsp,0x20
   0x55555555518d:      mov    DWORD PTR [rbp-0x14],edi
   0x555555555190:      mov    QWORD PTR [rbp-0x20],rsi
   0x555555555194:      call   0x555555555149
   0x555555555199:      mov    DWORD PTR [rbp-0x4],eax
   0x55555555519c:      cmp    DWORD PTR [rbp-0x4],0x0
   0x5555555551a0:      jle    0x5555555551a9
   0x5555555551a2:      mov    eax,0x0

あとは、main関数にブレークポイントを設定して、実行すれば、先ほどと同じようにデバッグが出来ます。

$ b *0x555555555185
Breakpoint 2 at 0x555555555185
gdb-peda$ i b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000555555555185
gdb-peda$ c
Continuing.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

[----------------------------------registers-----------------------------------]
RAX: 0x555555555185 (push   rbp)
RBX: 0x7fffffffe348 --> 0x7fffffffe5ce ("/home/user/svn/experiment/c/hello_world_strip.out")
RCX: 0x555555557dd8 --> 0x555555555100 (endbr64)
RDX: 0x7fffffffe358 --> 0x7fffffffe600 ("SHELL=/bin/bash")
RSI: 0x7fffffffe348 --> 0x7fffffffe5ce ("/home/user/svn/experiment/c/hello_world_strip.out")
RDI: 0x1
RBP: 0x1
RSP: 0x7fffffffe238 --> 0x7ffff7df124a (<__libc_start_call_main+122>:   mov    edi,eax)
RIP: 0x555555555185 (push   rbp)
R8 : 0x0
R9 : 0x7ffff7fcf680 (<_dl_fini>:        push   rbp)
R10: 0x7ffff7fcb878 --> 0xc00120000000e
R11: 0x7ffff7fe1930 (<_dl_audit_preinit>:       mov    eax,DWORD PTR [rip+0x1b4e2]        # 0x7ffff7ffce18 <_rtld_global_ro+888>)
R12: 0x0
R13: 0x7fffffffe358 --> 0x7fffffffe600 ("SHELL=/bin/bash")
R14: 0x555555557dd8 --> 0x555555555100 (endbr64)
R15: 0x7ffff7ffd020 --> 0x7ffff7ffe2e0 --> 0x555555554000 --> 0x10102464c457f
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x555555555180:      mov    eax,DWORD PTR [rbp-0x4]
   0x555555555183:      leave
   0x555555555184:      ret
=> 0x555555555185:      push   rbp
   0x555555555186:      mov    rbp,rsp
   0x555555555189:      sub    rsp,0x20
   0x55555555518d:      mov    DWORD PTR [rbp-0x14],edi
   0x555555555190:      mov    QWORD PTR [rbp-0x20],rsi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe238 --> 0x7ffff7df124a (<__libc_start_call_main+122>:  mov    edi,eax)
0008| 0x7fffffffe240 --> 0x0
0016| 0x7fffffffe248 --> 0x555555555185 (push   rbp)
0024| 0x7fffffffe250 --> 0x100000000
0032| 0x7fffffffe258 --> 0x7fffffffe348 --> 0x7fffffffe5ce ("/home/user/svn/experiment/c/hello_world_strip.out")
0040| 0x7fffffffe260 --> 0x7fffffffe348 --> 0x7fffffffe5ce ("/home/user/svn/experiment/c/hello_world_strip.out")
0048| 0x7fffffffe268 --> 0x3c24da9e3bd1cf39
0056| 0x7fffffffe270 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x0000555555555185 in ?? ()
gdb-peda$

strip されたプログラムを GDB でデバッグする方法は以上です。

現時点で分かってないこと

  • lea命令などで、RAX に設定した後、他のレジスタに mov してるが、最初から他のレジスタを対象に lea命令を実行できないのか?
  • ある関数で、ローカル変数としてスタックを 4byte しか使っていないのに、スタックは 16byte 確保されていたが、なぜか?(8byteでいいのでは?)
  • 関数呼び出し時に EAX をゼロクリアしてから関数を呼び出していたが、なぜか?
  • RDI と RSI は、非破壊レジスタなのに、上位で退避しているのはなぜか?

デバッグコンソールでよく使うGDBコマンド

GDB では、現在のレジスタの値を見たり、逆アセンブラコードを見たり、ステップ実行したりするときに、GDBのコマンドを一覧にしておきます。VSCode で、GDBコマンドを実行するには、デバッグコンソールを開いて、「-exec GDBコマンド」と入力します。

例えば、info registers のコマンドが実行したい場合は、「-exec info registers」と入力してリターンキーを押すと実行できます。もちろん、短縮形の「-exec i r」でも同じことが出来ます。

以下の表では、-exec は省略しています。また、なるべく短縮形の方を書いていきます。

コマンド 内容
start 実行開始する(シンボル情報があればmain関数で止まる)
run 実行開始する(main関数で止まらない)
c 実行を再開する
b 関数名 指定した関数にブレークポイントを設定する
b *アドレス 指定したアドレスにブレークポイントを設定する
tb *アドレス 指定したアドレスに1度だけ有効なブレークポイントを設定する
i b ブレークポイントの一覧を表示する
d 削除するブレークポイント番号 上のブレークポイントの一覧で削除したい番号を指定するとブレークポイントを削除できる
i r 整数のレジスタを全て表示する
i r $sp スタックポインタのレジスタを表示する
i r $x0 $x1 x0 と x1 のレジスタを表示する
s C言語のステップ実行をする
si アセンブラのステップ実行をする
n C言語のステップオーバー(関数に入らない)実行をする
ni アセンブラのステップオーバー(関数に入らない)実行をする
fin 関数を抜けるまで処理を実行する
x/b $sp SPが指しているメモリを1バイト表示する
x/xb $sp SPが指しているメモリの1バイトを16進数で表示する
x/xw $sp SPが指しているメモリの1ワード(4byte)を16進数で表示する
x/4xw $sp SPが指しているメモリの4ワード(4byte×4)を16進数で表示する
x/xg $sp SPが指しているメモリの8バイトを16進数で表示する
x/s $sp SPが指しているメモリの文字列を表示する
x/10i アドレス 指定したアドレスのコードを表示する

バイナリを扱うコマンドのまとめ

よく使うバイナリを扱うコマンドを列挙します。

objdump による逆アセンブラの出力は、何も指定しない場合は、AT&T記法と呼ばれるフォーマットとなります。これは、GDB、Ghidra で見かける Intel記法とは、ソースとデスティネーションが入れ替わるため、全く異なります。常に、-M intel を指定するのがおすすめです。

コマンド 内容
file a.out a.outのファイルの概要を表示する
strings a.out a.outに含まれる文字列のファイルの概要を表示する(デフォルト:可読部分が4文字以上連続)
objdump -M intel -d a.out > a.s 逆アセンブラを出力する
readelf -h a.out ELFヘッダを出力する
readelf -l a.out プログラムヘッダを出力する
readelf -S a.out セクションヘッダを出力する

x86-64の命令まとめ

よく使う x86-64 の命令をまとめておきます。

命令 内容
mov dest, src src を dest にコピーする
push value value をスタックに保存、RSPは-8
pop dest スタックの値を dest に取得、RSPは+8
add dest, src dest と src を加算して dest に保存
sub dest, src dest から src を減算して dest に保存
xor dest, src dest と src を排他的論理和して dest に保存
call function 関数呼び出し
ret 関数から呼び出し元に戻る
shl dest, src dest を src だけ左論理シフトして dest に保存
cmp src1, src2 src1 と src2 を比較して結果を EFLAGSレジスタにセット
jmp address address に無条件ジャンプ
jz address ゼロの場合(ZF=1)は address にジャンプ
jnz address ゼロでない場合(ZF=0)は address にジャンプ
jl address 小さい場合(SF=0)は address にジャンプ
jle address 小さい、または、等しい場合は address にジャンプ
jg address 大きい場合は address にジャンプ
jge 大きい、または、等しい場合は address にジャンプ

おわりに

今回は、PC Linux のアセンブラを理解してみました。x86-64 のアセンブラは今回初めてでしたが、ARM とそこまで違うというわけではなかったので、何とか簡単なところは理解できたと思います。

文字数は 4万文字を超えました。だいぶ重いです(笑)。

今回は以上です。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。