前回 は、K&Rのmalloc関数とfree関数を動かして理解しました。
今回は、Exploitコードの部品となりそうな C言語からシェルを起動するプログラムを実装してみたいと思います。
それでは、やっていきます。
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
セキュリティの記事一覧
例えば、バッファオーバーフローの不具合(脆弱性)があるプログラムがあるとすると、よく任意のコードが実行できる、とか聞くと思います。よく聞くんですけど、実際にどういうプログラムを実行するのか、今まで知りませんでした。
今回は、以下のサイトを参考させて頂いて、C言語からシェルを起動するプログラムを実装してみたいと思います。
inaz2.hatenablog.com
上の記事の ARM32bit版の記事もありました。
inaz2.hatenablog.com
また、64bitARM(Aarch64)のアセンブラについては、以下のサイトを参考にさせて頂きました。
www.mztn.org
それでは、やっていきます。
C言語でシェルを起動するプログラムを実装する
初めての内容なので、参考にさせて頂いたサイト(以下、参考サイト)のプログラムを、ほぼそのまま使わせて頂きました。参考サイトは、x86、ARM32bit のコードのようですが、ここでは、ラズパイ4 を使って、ARM64bit のプログラムでやってみたいと思います。
C言語のソースコードとしては、2行だけです。execve関数は、指定したファイル名のプログラムを実行します。
#include <unistd.h>
int main( int argc, char *argv[] )
{
char *args[] = { "/bin/sh", NULL };
execve( args[0], args, NULL );
}
では、ラズパイ4 上で、実際にコンパイルして、動かしてみたいと思います。スタティックリンクで、コンパイルしたので、687KB の実行ファイルになりました。参考サイトが、スタティックリンクにした理由は、libc のアセンブラも見たかったからか、デバッグしたかったからでしょうか。
実際に、動的リンクにして、動かしてみると、main関数から、libc の execve関数を呼ぶのですが、初めて共有ライブラリ関数を実行するときは、動的リンカによる PLT のリンク解決の処理を実行する必要があるので、libc の execve関数にたどり着くまで、かなり長いアセンブラのステップ実行が必要になります。これを回避したかったのかもしれません。
少し脱線しました。コンパイルと実行です。シェルからシェルを起動してるようなものなので、分かりにくいので、コンパイルしたりするシェルの方には、カレントディレクトリを付けました。
execve.out を実行すると、シェルが起動して、ls と exit を実行することが出来ました。
~/svn/experiment/c $ gcc -static -o execve.out execve.c
~/svn/experiment/c $ ./execve.out
$ ls
execve execve.c execve.out k_and_r_org.c
$ exit
~/svn/experiment/c $
C言語でシェルを起動するプログラムをVSCodeでデバッグする
参考サイトの行っている通りに、こちらでもやっていきます。
まず、objdumpコマンドで、アセンブラを確認します。
$ objdump -d execve.out > execve_objdump.s
かなり大きなファイル(3,868KB)が出力されました。main関数を検索で探しました。
x0、x1、x2 の 3つを引数として、400700 で、__execve関数を呼び出しているようです。
00000000004006d4 <main>:
4006d4: a9bd7bfd stp x29, x30, [sp,
4006d8: 910003fd mov x29, sp
4006dc: b9001fe0 str w0, [sp,
4006e0: f9000be1 str x1, [sp,
4006e4: f00002a0 adrp x0, 457000 <_nl_archive_subfreeres+0xe0>
4006e8: 91092000 add x0, x0,
4006ec: f90013e0 str x0, [sp,
4006f0: f90017ff str xzr, [sp,
4006f4: f94013e0 ldr x0, [sp,
4006f8: 910083e1 add x1, sp,
4006fc: d2800002 mov x2,
400700: 94002660 bl 40a080 <__execve>
400704: 52800000 mov w0,
400708: a8c37bfd ldp x29, x30, [sp],
40070c: d65f03c0 ret
同様に、__execve関数の方も見てみます。
40a088 で、システムコールを呼んでいるようです。
000000000040a080 <__execve>:
40a080: d503201f nop
40a084: d2801ba8 mov x8,
40a088: d4000001 svc
40a08c: b13ffc1f cmn x0,
40a090: 54000042 b.cs 40a098 <__execve+0x18>
40a094: d65f03c0 ret
40a098: 14000e02 b 40d8a0 <__syscall_error>
40a09c: d503201f nop
参考サイトは、この後、GDB で追いかけたようですが、こちらでは、VSCode で追いかけてみます。
システムコール実行直前まできました。コメントでは、221番のシステムコールを呼び出そうとしているようです。
システムコールを調べると、221番は、確かに、execve でした。
SVC命令の詳細は、以下で詳しく解説がされています。
www.mztn.org
確かに、x8 レジスタに、221 をセットしています。
x0 から x5 に、システムコールの引数を設定するそうです。今回の execve は引数が 3つなので、x0 から x2 までを使用すると思います。
システムコールを実行直前のレジスタダンプと、スタックの状態です。
レジスタダンプは一部省略しています。左側が16進数で右側が10進数のようです。
-exec i r
x0 0x457248 4551240
x1 0x7fffffedd0 549755809232
x2 0x0 0
x3 0x4005b4 4195764
x4 0x7fffffede0 549755809248
x5 0x235f6cea5f7fb822 2548875667995342882
x6 0x492908 4794632
x7 0x2 2
x8 0xdd 221
sp 0x7fffffedb0 0x7fffffedb0
pc 0x400704 0x400704 <main+48>
次にスタックを16個分ダンプしました。
-exec x/16xg $sp
0x7fffffedb0: 0x0000007fffffede0 0x00000000004007b8
0x7fffffedc0: 0x0000007fffffef98 0x000000010040077c
0x7fffffedd0: 0x0000000000457248 0x0000000000000000
0x7fffffede0: 0x0000007fffffeef0 0x0000000000400b34
0x7fffffedf0: 0x000000000048c710 0x00000000004005b4
0x7fffffee00: 0x0000000100490a60 0x0000007fffffef98
0x7fffffee10: 0x0000000000000001 0x0000007fffffef98
0x7fffffee20: 0x0000000000000002 0x0000007fffffefa8
x0レジスタが指しているメモリの値を見てみます。
ASCIIコードで見ると、"/bin/sh" です。バッチリです。
-exec x/8bx $x0
0x457248: 0x2f 0x62 0x69 0x6e 0x2f 0x73 0x68 0x00
次に、x1レジスタが指しているメモリを見てみます。
上の 0x457248
へのポインタと、NULL が入ってます。こちらもバッチリです。
-exec x/2xg $x1
0x7fffffedd0: 0x0000000000457248 0x0000000000000000
execveシステムコールを呼ぶのに必要な内容は見れたと思います。
参考サイトでは、ここから、アセンブラコードを実装していきます。こちらでも、ARM のアセンブラコードを実装してみたいと思います。
アセンブラでシェルを起動するプログラムを実装する
自力で書こうと思っていたのですが、調べるのに時間がかかりそうなので、まずは、ChatGPT に書いてもらいました(笑)。
.section .data
args:
.string "/bin/sh"
.quad 0
.section .text
.globl _start
_start:
ldr x0, =args
add x1, x0,
mov x2,
mov x8,
svc
mov x8,
mov x0,
svc
それらしい感じで、うまくいきそうです。
では、アセンブルします。と言っても、gcc がアセンブルもリンクもやってくれるようです。スタティックリンクの代わり?でしょうか。-nostdlib
を付けます。ついでに、逆アセンブラも出力しておきます。Web には、このあたりの情報は、かなり少ない印象です。正しいかどうかが少し不安です。
gcc -g -o execve.out -nostdlib execve.s
$ objdump -d execve.out
execve.out: file format elf64-littleaarch64
Disassembly of section .text:
0000000000000290 <_start>:
290: 58000100 ldr x0, 2b0 <_start+0x20>
294: 91002001 add x1, x0,
298: d2800002 mov x2,
29c: d2801ba8 mov x8,
2a0: d4000001 svc
2a4: d2800ba8 mov x8,
2a8: d2800000 mov x0,
2ac: d4000001 svc
2b0: 00020000 .word 0x00020000
2b4: 00000000 .word 0x00000000
このコードが正しいかをデバッガで確認します。
アセンブラファイルを、アセンブルして、VSCode でデバッグする方法を探したのですが、見つかりませんでした。対応していないのかもしれません。
仕方ないので、苦手ですけど、gdb を直接つかいます。
アセンブラでシェルを起動するプログラムをGDBでデバッグする
とにかく、GDB を起動します。シンボル情報は読み込まれたようです。
$ gdb execve.out
GNU gdb (Debian 13.1-3) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from execve.out...
(gdb)
続いて、ブレークポイントを設定します。アセンブラソース execve.s
の10行目、0x290 にブレークポイントを設定できたようです。
(gdb) b _start
Breakpoint 1 at 0x290: file execve.s, line 10.
では、デバッグ開始します。なぜか共有ライブラリをロードしますか?と聞かれますが、No です。dl-start.S が無いと言われてますが、よく分かりません。
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
Starting program: /home/daisuke/svn/experiment/c/execve.out
Breakpoint 1.2, _start () at ../sysdeps/aarch64/dl-start.S:22
22 ../sysdeps/aarch64/dl-start.S: No such file or directory.
(gdb)
逆アセンブラを表示してみます。うーん、少し想定していたものではありません。アセンブラソースに書いた _start
とは違ってます。C言語で言うところのスタートアップルーチン的なコードでしょうか。
(gdb) disassemble
Dump of assembler code for function _start:
=> 0x0000007ff7fd8980 <+0>: nop
0x0000007ff7fd8984 <+4>: mov x29,
0x0000007ff7fd8988 <+8>: mov x30,
0x0000007ff7fd898c <+12>: mov x0, sp
0x0000007ff7fd8990 <+16>: bl 0x7ff7fd44c0 <_dl_start>
0x0000007ff7fd8994 <+20>: mov x21, x0
0x0000007ff7fd8998 <+24>: ldr x1, [sp]
0x0000007ff7fd899c <+28>: add x2, sp,
0x0000007ff7fd89a0 <+32>: add x3, x2, x1, lsl
0x0000007ff7fd89a4 <+36>: add x3, x3,
0x0000007ff7fd89a8 <+40>: adrp x16, 0x7ff7ffe000 <_dl_catch_exception@got.plt>
0x0000007ff7fd89ac <+44>: add x16, x16,
0x0000007ff7fd89b0 <+48>: ldr x0, [x16]
0x0000007ff7fd89b4 <+52>: bl 0x7ff7fc23b0 <_dl_init>
0x0000007ff7fd89b8 <+56>: adrp x0, 0x7ff7fc2000 <_dl_find_object_dlclose+160>
0x0000007ff7fd89bc <+60>: add x0, x0,
0x0000007ff7fd89c0 <+64>: mov x16, x21
0x0000007ff7fd89c4 <+68>: br x16
End of assembler dump.
(gdb) i r
x0 0x0 0
x1 0x0 0
x2 0x0 0
x3 0x0 0
x4 0x0 0
x5 0x0 0
x6 0x0 0
x7 0x0 0
x8 0x0 0
x9 0x0 0
x10 0x0 0
x11 0x0 0
x12 0x0 0
x13 0x0 0
x14 0x0 0
x15 0x0 0
x16 0x0 0
x17 0x0 0
x18 0x0 0
x19 0x0 0
x20 0x0 0
x21 0x0 0
x22 0x0 0
x23 0x0 0
x24 0x0 0
x25 0x0 0
x26 0x0 0
x27 0x0 0
x28 0x0 0
x29 0x0 0
x30 0x0 0
sp 0x7ffffff400 0x7ffffff400
pc 0x7ff7fd8980 0x7ff7fd8980 <_start>
cpsr 0x0 [ EL=0 BTYPE=0 ]
fpsr 0x0 [ ]
fpcr 0x0 [ Len=0 Stride=0 RMode=0 ]
tpidr 0x0 0x0
tpidr2 0x0 0x0
続きを実行してみます。
うーん、これが最初に見えてたら、想定通りでした。ここも _start
なんでしょうか。
x0 に、0x55555502b0 が指しているメモリの内容を格納しています。つまり、x0 には、0x55570000 が入ります。その後、x1 には、x0 を +8 した値が入ります(0x55570008)。
(gdb) c
Continuing.
warning: Temporarily disabling breakpoints for unloaded shared library "/lib/ld-linux-aarch64.so.1"
Breakpoint 1.1, _start () at execve.s:10
10 ldr x0, =args // x0 に "/bin/sh" のアドレスをロード
(gdb) disassemble
Dump of assembler code for function _start:
=> 0x0000005555550290 <+0>: ldr x0, 0x55555502b0 <_start+32>
0x0000005555550294 <+4>: add x1, x0,
0x0000005555550298 <+8>: mov x2,
0x000000555555029c <+12>: mov x8,
0x00000055555502a0 <+16>: svc
0x00000055555502a4 <+20>: mov x8,
0x00000055555502a8 <+24>: mov x0,
0x00000055555502ac <+28>: svc
0x00000055555502b0 <+32>: .inst 0x55570000 ; undefined
0x00000055555502b4 <+36>: udf
End of assembler dump.
メモリの内容を確認してみます。まず、x0 に格納される値を確認すると、0x0000005555570000 です。合ってます。その次は、0x0000005555570000 が指しているアドレスのメモリは、"/bin/sh" っぽい内容です。
(gdb) x/xg 0x55555502b0
0x55555502b0 <_start+32>: 0x0000005555570000
(gdb) x/8xb 0x0000005555570000
0x5555570000: 0x2f 0x62 0x69 0x6e 0x2f 0x73 0x68 0x00
まずは、そのまま実行してみます。何やら、いろいろメッセージが出ますが、やりたいことは出来ているようです。
(gdb) c
Continuing.
process 2998 is executing new program: /usr/bin/dash
Error in re-setting breakpoint 1: Function "_start" not defined.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
$ ls
[Detaching after vfork from child process 3006]
a.out execve.c execve.o execve.s execve_x86.txt main_execve.s
execve execve.map execve.out execve_objdump.s
$ exit
[Inferior 1 (process 2998) exited with code 0177]
(gdb) q
アセンブラでシェルを起動するプログラムを実装する(再)
やりたいことは出来ていますが、少しおかしい気がしてきました。
以下の ChatGPT が作ってくれたアセンブラですが、第2引数の x1 に x0 + 8 をセットしてますが、x0 には "/bin/sh" のアドレスが入っていて、その 8byte 先には NULL(0)が入ってるだけです。execve の第2引数には、args のアドレスをセットしなければならないはずです。
他のサイトで、第2引数に NULL を指定していました。マニュアルを見る限り、NULL だけではダメだというように書かれてる気がします。実際に、上でやった C言語版で、第2引数を NULL で実行してみると、普通に動きました。なるべく、第2引数は設定した方がいい、と理解することにします。
さて、この第2引数だけ修正したかったのですが、mov x1, =args
はアセンブルエラーになりました。mov x1, args
は、なぜか、x1 に 0 が入るアセンブラになりました。
.section .data
args:
.string "/bin/sh"
.quad 0
.section .text
.globl _start
_start:
ldr x0, =args
add x1, x0,
mov x2,
mov x8,
svc
mov x8,
mov x0,
svc
分からないので、もう1度、ChatGPT に別の文章で同じ内容でお願いしてみました。すると、今度は、想定したソースコードを書いてくれたような気がします。
.global _start
_start:
ldr x0, =binsh
adr x1, argv
mov x2, xzr
mov x8,
svc
binsh:
.asciz "/bin/sh"
argv:
.quad binsh
.quad 0
アセンブラでシェルを起動するプログラムをGDBでデバッグする(再)
アセンブルして、objdump もしてみます。
gcc -g -Wl,-Map=execve.map -o execve.out -nostdlib execve.s
objdump の結果です。いい感じです。
execve.out: file format elf64-littleaarch64
Disassembly of section .text:
0000000000000290 <_start>:
290: 58000180 ldr x0, 2c0 <argv+0x14>
294: 100000c1 adr x1, 2ac <argv>
298: aa1f03e2 mov x2, xzr
29c: d2801ba8 mov x8,
2a0: d4000001 svc
00000000000002a4 <binsh>:
2a4: 6e69622f .word 0x6e69622f
2a8: 0068732f .word 0x0068732f
00000000000002ac <argv>:
2ac: 000002a4 .word 0x000002a4
...
2c0: 000002a4 .word 0x000002a4
2c4: 00000000 .word 0x00000000
GDB でデバッグしてみます。まずは、上のアセンブラが実行される直前まで行きます。
$ gdb execve.out
Reading symbols from execve.out...
(gdb) b _start
Breakpoint 1 at 0x290: file execve.s, line 4.
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
Starting program: /home/daisuke/svn/experiment/c/execve.out
Breakpoint 1.2, _start () at ../sysdeps/aarch64/dl-start.S:22
22 ../sysdeps/aarch64/dl-start.S: No such file or directory.
(gdb) disas
Dump of assembler code for function _start:
=> 0x0000007ff7fd8980 <+0>: nop
0x0000007ff7fd8984 <+4>: mov x29,
0x0000007ff7fd8988 <+8>: mov x30,
0x0000007ff7fd898c <+12>: mov x0, sp
0x0000007ff7fd8990 <+16>: bl 0x7ff7fd44c0 <_dl_start>
0x0000007ff7fd8994 <+20>: mov x21, x0
0x0000007ff7fd8998 <+24>: ldr x1, [sp]
0x0000007ff7fd899c <+28>: add x2, sp,
0x0000007ff7fd89a0 <+32>: add x3, x2, x1, lsl
0x0000007ff7fd89a4 <+36>: add x3, x3,
0x0000007ff7fd89a8 <+40>: adrp x16, 0x7ff7ffe000 <_dl_catch_exception@got.plt>
0x0000007ff7fd89ac <+44>: add x16, x16,
0x0000007ff7fd89b0 <+48>: ldr x0, [x16]
0x0000007ff7fd89b4 <+52>: bl 0x7ff7fc23b0 <_dl_init>
0x0000007ff7fd89b8 <+56>: adrp x0, 0x7ff7fc2000 <_dl_find_object_dlclose+160>
0x0000007ff7fd89bc <+60>: add x0, x0,
0x0000007ff7fd89c0 <+64>: mov x16, x21
0x0000007ff7fd89c4 <+68>: br x16
End of assembler dump.
(gdb) c
Continuing.
warning: Temporarily disabling breakpoints for unloaded shared library "/lib/ld-linux-aarch64.so.1"
Breakpoint 1.1, _start () at execve.s:4
4 ldr x0, =binsh // "/bin/sh" を含む文字列のアドレスをレジスタ x0 にセット
(gdb) disas
Dump of assembler code for function _start:
=> 0x0000005555550290 <+0>: ldr x0, 0x55555502c0 <argv+20>
0x0000005555550294 <+4>: adr x1, 0x55555502ac <argv>
0x0000005555550298 <+8>: mov x2, xzr
0x000000555555029c <+12>: mov x8,
0x00000055555502a0 <+16>: svc
End of assembler dump.
直前まで来ました。x0 と x1 にセットされるところまで実行して、レジスタの値を見てみます。その後、それぞれが指しているメモリの内容をダンプしてみます。
(gdb) si
6 adr x1, argv // argv の配列のアドレスをレジスタ x1 にセット
(gdb) si
7 mov x2, xzr // x2 レジスタを NULL に設定(envp のため)
(gdb) i r
x0 0x55555502a4 366503854756
x1 0x55555502ac 366503854764
x2 0x0 0
x3 0x0 0
x4 0x410 1040
x5 0x2 2
x6 0x3f 63
x7 0x7c1 1985
x8 0x7ff7fff380 549621592960
x9 0x7ff7ff7d00 549621562624
x10 0x420000000000 72567767433216
x11 0x7fffffea70 549755808368
x12 0x7ff7ff71b0 549621559728
x13 0x360ed96 56683926
x14 0x7ff7ffe028 549621588008
x15 0x1 1
x16 0x5555550290 366503854736
x17 0x0 0
x18 0x0 0
x19 0x0 0
x20 0x0 0
x21 0x5555550290 366503854736
x22 0x0 0
x23 0x0 0
x24 0x0 0
x25 0x0 0
x26 0x0 0
x27 0x0 0
x28 0x0 0
x29 0x0 0
x30 0x7ff7fd89b8 549621434808
sp 0x7ffffff400 0x7ffffff400
pc 0x5555550298 0x5555550298 <_start+8>
cpsr 0x60200000 [ EL=0 BTYPE=0 SS C Z ]
fpsr 0x0 [ ]
fpcr 0x0 [ Len=0 Stride=0 RMode=0 ]
tpidr 0x7ff7ff7d00 0x7ff7ff7d00
tpidr2 0x0 0x0
(gdb) x/8bx 0x55555502a4
0x55555502a4 <binsh>: 0x2f 0x62 0x69 0x6e 0x2f 0x73 0x68 0x00
(gdb) x/2gx $x1
0x55555502ac <argv>: 0x00000055555502a4 0x0000000000000000
想定通りの内容です!では、このまま続きを実行します。
(gdb) c
Continuing.
process 1985 is executing new program: /usr/bin/dash
Error in re-setting breakpoint 1: Function "_start" not defined.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
$ ls
[Detaching after vfork from child process 1990]
execve.c execve_chatgpt.map execve_chatgpt_fixed.out execve_str.map main_execve.s
execve.map execve_chatgpt.out execve_chatgpt_fixed.s execve_str.out
execve.out execve_chatgpt.s execve_chatgpt_objdump.s execve_x86.txt
execve.s execve_chatgpt_fixed.map execve_str.c k_and_r_org.c
$ exit
[Inferior 1 (process 1985) exited normally]
(gdb) q
動作としても問題ありませんでした。
アセンブラでスタック上でシェル起動を実行するプログラムを実装する
今回やりたかったのは、バッファオーバーフローさせて、そのメモリにシェルを起動するコードを置いて、実行するプログラムです。上のプログラムは、ldr x0, 2c0
というように、アドレスを直接指定してしまっています。これでは、どんなアドレスになるか分からない場所にコードを置くことは出来ません。よって、参考サイトのように、スタックを使って引数を作りこむ必要があります(でも、よく説明を見ると、ldr x0, 2c0
は、PC の相対アドレスに変換される、と書かれているので、実は問題ないのかもしれません)。
ChatGPT の作ってくれるアセンブラを見てきたので、そろそろ自分でも実装できそうです。以下に実装してみました。"ldr x8, binsh" を使ってますが、PC の相対アドレスに変換される、というのを信じてやってみます。
.global _start // エントリーポイントをグローバルとして定義
_start:
ldr x8, binsh // "/bin/sh" を含む文字列を x8 にセット
mov x2, xzr // x2 レジスタを NULL に設定
mov x0, sp // x0 に sp の値をセット (第1引数)
stp x8, x2, [sp],
mov x1, sp // x1 に sp の値をセット (第2引数)
stp x0, x2, [sp, #0] // x0("/bin/sh" のアドレス) と x2(NULL) の値をスタックにプッシュ (sp は動かない)
mov x8,
svc
binsh:
.asciz "/bin/sh" // シェルのパスをNULL終端文字列としてメモリに定義
では、アセンブルして、objdump の結果を取得します。
$ gcc -g -Wl,-Map=execve.map -o execve.out -nostdlib execve.s
$ objdump -d execve.out > execve_objdump.s
objdump の結果です。
execve.out: file format elf64-littleaarch64
Disassembly of section .text:
0000000000000244 <_start>:
244: 58000108 ldr x8, 264 <binsh>
248: aa1f03e2 mov x2, xzr
24c: 910003e0 mov x0, sp
250: a8bf0be8 stp x8, x2, [sp],
254: 910003e1 mov x1, sp
258: a9000be0 stp x0, x2, [sp]
25c: d2801ba8 mov x8,
260: d4000001 svc
0000000000000264 <binsh>:
264: 6e69622f .word 0x6e69622f
268: 0068732f .word 0x0068732f
アセンブラでスタック上でシェル起動を実行するプログラムをGDBでデバッグする
デバッグします。
$ gdb execve.out
Reading symbols from execve.out...
(gdb) b _start
Breakpoint 1 at 0x244: file execve.s, line 4.
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
Starting program: /home/daisuke/svn/experiment/c/execve.out
Breakpoint 1.2, _start () at ../sysdeps/aarch64/dl-start.S:22
22 ../sysdeps/aarch64/dl-start.S: No such file or directory.
(gdb) c
Continuing.
warning: Temporarily disabling breakpoints for unloaded shared library "/lib/ld-linux-aarch64.so.1"
Breakpoint 1.1, _start () at execve.s:4
4 ldr x8, binsh // "/bin/sh" を含む文字列のアドレスを x8 にセット
(gdb) disas
Dump of assembler code for function _start:
=> 0x0000005555550244 <+0>: ldr x8, 0x5555550264 <binsh>
0x0000005555550248 <+4>: mov x2, xzr
0x000000555555024c <+8>: mov x0, sp
0x0000005555550250 <+12>: stp x8, x2, [sp],
0x0000005555550254 <+16>: mov x1, sp
0x0000005555550258 <+20>: stp x0, x2, [sp]
0x000000555555025c <+24>: mov x8,
0x0000005555550260 <+28>: svc
End of assembler dump.
(gdb) x/8bx 0x5555550264
0x5555550264 <binsh>: 0x2f 0x62 0x69 0x6e 0x2f 0x73 0x68 0x00
(gdb) si
6 mov x2, xzr // x2 レジスタを NULL に設定
(gdb)
7 mov x0, sp // x0 に sp の値をセット (第1引数)
(gdb) i r sp
sp 0x7ffffff400 0x7ffffff400
(gdb) si
8 stp x8, x2, [sp],
(gdb) i r x0
x0 0x7ffffff400 549755810816
(gdb) i r x8
x8 0x68732f6e69622f 29400045130965551
(gdb) i r x2
x2 0x0 0
(gdb) si
_start () at execve.s:10
10 mov x1, sp // x1 に sp の値をセット (第2引数)
(gdb) i r sp
sp 0x7ffffff3f0 0x7ffffff3f0
(gdb) x/2gx 0x7ffffff3f0
0x7ffffff3f0: 0x0000000000000000 0x0000000000000000
(gdb) x/2gx 0x7ffffff400
0x7ffffff400: 0x0068732f6e69622f 0x0000000000000000
(gdb) i r x0
x0 0x7ffffff400 549755810816
(gdb) si
11 stp x0, x2, [sp, #0] // x0("/bin/sh" のアドレス) と x2(NULL) の値をスタックにプ ッシュ (sp は動かない)
(gdb) i r x1
x1 0x7ffffff3f0 549755810800
(gdb) si
13 mov x8,
(gdb) x/4gx 0x7ffffff3f0
0x7ffffff3f0: 0x0000007ffffff400 0x0000000000000000
0x7ffffff400: 0x0068732f6e69622f 0x0000000000000000
(gdb) i r x0 x1 x2
x0 0x7ffffff400 549755810816
x1 0x7ffffff3f0 549755810800
x2 0x0 0
(gdb) c
Continuing.
process 2550 is executing new program: /usr/bin/dash
Error in re-setting breakpoint 1: Function "_start" not defined.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
$ exit
[Inferior 1 (process 2550) exited with code 0177]
(gdb) q
うまくいってそうです!
今回はここまでです。
おわりに
今回は、参考サイトを見ながら、execve を使って、C言語とアセンブラで、シェルを起動するプログラムの実装とデバッグを行いました。
次回は、今回作ったアセンブラのプログラムのバイナリの確認と、バッファオーバーフローなどで任意のコードとして埋め込めるように加工していきたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。