序言
SCA又称为侧链路反击,中心思想是透过运转这时候各式各样重要信息对流程展开反击。主要包括单纯耗电预测反击(SimplePower Analysis attacks,SPA)和脉冲响应耗电预测反击(Differential Power Analysis attacks,DPA),与现代公钥预测学较之,此种反击方式反击。
VM身份验证
这儿采用的是他们写的两个CrackMe,不采用除IO的任何人控制系统表达式
#include
#include
const char key[] = {104, 100, 107, 125, 108, 118, 100, 99, 96};
int main() {
char input[10] = {0};
scanf(“%9s”, input);
if (strlen(input) != 9) {
printf(“ERROR The length of key is 9 bytes!\n”);
return -1;
}
for (int i = 0; i < 9; ++i) {
if (input[i] != (key[9 – i – 1] ^ 5)) {
printf(“ERROR The key is incorrect!\n”);
return -1;
}
}
printf(“OK Thanks to your purchase!\n”);
return 0;
}编译后采用VMProtect对main表达式展开VM软件包保护
保护后采用IDA查看代码被替换了只剩下VM入口,代码是不可被直接阅读的
push xxx
call xxx
Block数量预测
这儿采用的是angr对身份验证后的二进制文件模拟执行
def main():
ql = Qiling([rf”{ROOTFS}/bin/main_vmp”], ROOTFS, stdin=pipe.SimpleInStream(sys.stdin.fileno()),stdout=pipe.SimpleOutStream(sys.stdout.fileno()))对流程运转block展开hook,codeNum用来存放此次key所执行的block数
0x401010 – 0x504bB6 是VMP区段,我们仅仅记录该区段的block。
ql.hook_block(trace)
…
def trace(ql, addr, size):
global codeNum
if addr – ql.loader.images[0].base in range(0x401010, 0x504bB6):
codeNum += 1密钥的生成
该流程的密钥由9个字节组成,字节的范围是0~2^16-1
这儿已知该流程的密钥组成是a-z,所以采用a-z展开反击
用a-z对每一位展开反击直到block数量增多
src = string.ascii_letters
codeNum = 0
codeNum1 = 0
indexs = [0] * 9
cur = 0
key = [a] * 9
…
def start(_ql: Qiling):
global data
global key
//前两个block数量
global codeNum
//当前block数量
global codeNum1
global cur
//当前block > 前两个block
if codeNum > codeNum1:
codeNum1 = codeNum
//前两个block > 当前block
elif codeNum1 > codeNum:
//该位正确
indexs[cur] -=2
//回溯
key[cur] = src[indexs[cur]]
//移动到下一位
cur = cur + 1
//新的一轮猜想
codeNum = 0
data = _ql.save()
key[cur] = src[indexs[cur]]
indexs[cur] +=1
key1 = “”.join(key).encode()表达式开始与结尾
为了提升流程运转效率,这儿采用angr对VM头时的Context展开保存,拦截put表达式对结果展开判断
def start(_ql: Qiling):
…
//存放当前的Context
data = _ql.save()
//写入猜想的key
_ql.os.stdin.write(key1)
def end(_ql: Qiling):
global codeNum
//从edi寄存器取结果字符串
read = str(_ql.mem.read(_ql.reg.rdi, 30))
if “ERROR” in read:
//包含ERROR则还原环境展开下一次反击
global data
data = _ql.restore(data)
else:
cur = cur + 1
global key
print(“密钥:”, “”.join(key[0:cur]).encode())运转结果
等待了几十秒密钥便反击出来了
放到crackme中
完整代码
#!/usr/bin/env python3
import sys
from qiling.extensions import pipe
import string
from qiling import QilingROOTFS = r”/home/mrack/qiling/examples/rootfs/x8664_linux”
src = string.ascii_letters
global data
global key
global codeNum
global codeNum1
global isFirst
codeNum = 0
codeNum1 = 0
indexs = [0] * 9
cur = 0
key = [a] * 9
def start(_ql: Qiling):
global data
global key
global codeNum
global codeNum1
global cur
if codeNum > codeNum1:
codeNum1 = codeNum
elif codeNum1 > codeNum:
indexs[cur] -= 2
key[cur] = src[indexs[cur]]
cur = cur + 1codeNum = 0
data = _ql.save()
key[cur] = src[indexs[cur]]
indexs[cur] += 1
key1 = “”.join(key).encode()
_ql.os.stdin.write(key1)
def end(_ql: Qiling):
global cur
global codeNum
read = str(_ql.mem.read(_ql.reg.rdi, 30))
print(codeNum)
if “ERROR” in read:
global data
data = _ql.restore(data)
else:
cur = cur + 1
global key
print(“密钥:”, “”.join(key[0:cur]).encode())
def trace(ql, addr, size):
global codeNum
if addr – ql.loader.images[0].base in range(0x401010, 0x504bB6):
codeNum += 1
def main():
ql = Qiling([rf”{ROOTFS}/bin/main_vmp”], ROOTFS, stdin=pipe.SimpleInStream(sys.stdin.fileno()),
stdout=pipe.SimpleOutStream(sys.stdout.fileno()))
ba = ql.loader.images[0].base
ql.hook_address(start, ba + 0x504168)
ql.hook_address(end, ba + 0x05E0)
ql.hook_block(trace)
ql.run()
if __name__ == “__main__”:
main()