pwn

第五届上海市大学生网络安全大赛的一道pwn题,boring heap

IDA分析找洞

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  char idx[8]; // [rsp+10h] [rbp+0h]

  initbuffer();
  puts("  ____                _                 _    _                     ");
  puts(" |  _ \\              (_)               | |  | |                    ");
  puts(" | |_) |  ___   _ __  _  _ __    __ _  | |__| |  ___   __ _  _ __  ");
  puts(" |  _ <  / _ \\ | '__|| || '_ \\  / _` | |  __  | / _ \\ / _` || '_ \\ ");
  puts(" | |_) || (_) || |   | || | | || (_| | | |  | ||  __/| (_| || |_) |");
  puts(" |____/  \\___/ |_|   |_||_| |_| \\__, | |_|  |_| \\___| \\__,_|| .__/ ");
  puts("                                 __/ |                      | |    ");
  puts("                                |___/                       |_|    ");
  while ( 1 )
  {
    memu();
    getnum();
    switch ( (unsigned int)idx )
    {
      case 1u:
        add();                                  // add
        break;
      case 2u:
        updata();                               // updata
        break;
      case 3u:
        del();                                  // del
        break;
      case 4u:
        view();                                 // view
        break;
      case 5u:
        puts("bye~");
        exit(0);
        return;
      default:
        puts("Invalid Option!");
        exit(1);
        return;
    }
  }
}
unsigned __int64 add()
{
  int siz_idx; // eax
  signed int v1; // ebx
  int size; // [rsp+0h] [rbp-20h]
  unsigned __int64 canary; // [rsp+8h] [rbp-18h]

  canary = __readfsqword(0x28u);
  if ( dword_202024[0] > 30 )
  {
    puts("Too many notes!");
    exit(1);
  }
  sub_B8A();
  siz_idx = getnum();
  if ( siz_idx == 2 )
  {
    size = 48;
  }
  else if ( siz_idx == 3 )
  {
    size = 64;
  }
  else
  {
    if ( siz_idx != 1 )
    {
      puts("Invalid Choice!");
      exit(1);
    }
    size = 32;
  }
  ::size[dword_202024[0]] = size;
  v1 = dword_202024[0];
  notes[v1] = malloc(size);
  puts("Input Content:");
  write((_BYTE *)notes[dword_202024[0]], size);
  ++dword_202024[0];
  return __readfsqword(0x28u) ^ canary;
}

unsigned __int64 updata()
{
  int offset; // ST04_4
  int idx; // [rsp+0h] [rbp-10h]
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  puts("Which one do you want to update?");
  idx = abs(getnum()) % 30;
  if ( notes[idx] && (size[idx] == 32 || size[idx] == 48 || size[idx] == 64) )
  {
    puts("Where you want to update?");
    offset = abs(getnum()) % size[idx];
    puts("Input Content:");
    write((_BYTE *)(notes[idx] + offset), size[idx] - offset);
  }
  else
  {
    puts("Invalid Index!");
  }
  return __readfsqword(0x28u) ^ canary;
}

unsigned __int64 del()
{
  int idx; // [rsp+4h] [rbp-Ch]
  unsigned __int64 canary; // [rsp+8h] [rbp-8h]

  canary = __readfsqword(0x28u);
  puts("Which one do you want to delete?");
  idx = abs(getnum()) % 30;
  if ( !notes[idx] || size[idx] != 32 && size[idx] != 48 && size[idx] != 64 )
  {
    puts("Invalid Index!");
    exit(1);
  }
  free((void *)notes[idx]);
  notes[idx] = 0LL;
  size[idx] = 0;
  return __readfsqword(0x28u) ^ canary;
}

程序本身还是比较简单的,可以申请0x20,0x30,0x40的chunk,(fastbin),可申请的chunk数量也比较多,delete时将指针置NULL了,不存在uaf行为,保护全开,程序内部也有检查,看了半天漏洞点在哪一头雾水,甚至准备上fuzz。

但程序中反复出现的abs()十分可疑,在update中offset也是通过abs()并取模来保证输入为在范围内的正数偏移。

了解下有关abs()函数的话就会了解到abs()函数不是绝对安全的,输入-2147483648 时是返回负数的。

那么我们在update时就可以构造一个负数的offset往chunk前面修改,这里当chunk size 0x30时 -0x80000000%0x30 == -0x20 这就定位到了chunk header的前面,可以修改chunk header实现unsorted bin 与 fastbin 的 overlap,然后 hack main_arena 写 malloc_hook为 onegadget。

具体的利用过程先咕咕咕了,学会overlap再来做一下。

有关abs()函数与返回值

abs()函数原型

int abs(int);

abs()为什么可以返回负数

这是由于int表示的整数域的不对称导致的,int返回值范围 -2147483648~2147483647 , 那么 -2147483648 的绝对值 2147483648 无法被作为一个int型表示,于是abs()返回的还是 -2147483648,负数返回值。

因此,光使用abs()函数是不能保证一定为整数,这将导致程序发生难以预料的行为,存在较大的安全隐患。

程序的上溢或下溢时常导致程序的bug或是安全漏洞。因此,在编程或审计时,函数的返回值的范围是值得关注的。

“经常反问:这个变量或表达式会上溢或下溢吗?”(《编程精粹-Microsoft编写优质无错C程序秘诀》P80,Steve Maguire 著)

ANSI C标准规定了每种整数类型的最小值域(但没有规定它们必须采用哪种编码方案),并要求所有的C语言实现都要在limits.h头文件中通过诸如INT_MIN、INT_MAX这样的宏来指定该实现中整型数据的实际值域,而且这些实际的值域一定不能比标准规定的最小值域还要小(也即要求每种实现在limits.h中定义的宏的绝对值不小于C标准规定的同名宏的绝对值,并且正负号要保持一致)。

标准定义的INT_MIN是-(2^15 - 1),INT_MAX是(2^15 - 1),换句话说,标准保证了int型数据至少能表示-(2^15 - 1)到(2^15 - 1)这样一个对称区间内的所有整数,因此程序员可以放心大胆地用int变量存储以上范围内的任何整数。但与此同时请特别注意,用int变量表达超出上述范围的数将是一件没有法律(标准即是程序员的法律)保障的事情,所以不应该想当然地认为-32768(即-2^15)一定可以用int型来表达,尽管它确实位于用二次补码记法(twos-complement notation)进行编码的16位整数的值域内。熟悉以上背景后再回顾abs函数的问题就会发现,实际上该函数对于标准规定范最小值域内的所有整数都能正常工作,而上面提到的引起错误的输入数据已经不在此范围内了,所以此错误不应由abs而应由函数调用者负责。由此可见,为了安全以及可移植性,将表达式欲表示的值严格限制在标准指定的最小值域内是一个良好的编程习惯。

事实上,USTC 的 hackergame 每年总有多题涉及到整数上溢或是下溢的小游戏。