signature=edc3f9d42b8728d7aacfa103506e19c4,【wp】HWS计划2021硬件安全冬令营线上选拔赛

逆向手在夹缝中艰难求生系列。

这篇真的存粹是做题笔记了,对内核驱动啥的不太懂,pwn也不会,能做出来的题都是硬逆出来的(

childre最后死活没整出来,后来看大佬的wp才知道对子进程有修改(。)呜呜呜多进程真的不太会啊。

REVERSE

obfu

一堆密码堆起来的题(密码菜鸡卑微落泪

按逻辑来说拿到serial number就能拿到flag了。

通过findcrypt找到三个加密算法的常量,通过交叉引用确定函数用途。

a345091dd223730ddf0acf7604bc543e.png

ae8ea6340b368254502604e90d5c46af.png

v10和v8相比之下v8更容易出,v8跟输入没有关系,相当于已知字符串(=md5("admin")。

然后就是enctrpy函数。

365185c968efa600986b6af91cb177f0.png

按照顺序是AES_enc->RC4_dec->循环左移三位复原。

exp:

from binascii import *

from Crypto.Cipher import AES,ARC4

from hashlib import sha256,md5

v8=unhexlify(md5("admin").hexdigest())

admin=[ord(x) for x in unhexlify(sha256("admin").hexdigest())]

tmp=""+chr(admin[0])

for i in range(1,32):

tmp+=chr(admin[i]^admin[i-1])

key=tmp[:16]

iv=tmp[16:]

aes=AES.new(key,AES.MODE_CBC,iv)

cipher=aes.encrypt(v8)

rc4=ARC4.new(key)

input_enc=rc4.decrypt(cipher)

tmps=bin(int(hexlify(input_enc),16))[2:].rjust(16*8,'0')

tmps=tmps[3:]+tmps[:3]

serial=""

for i in range(16):

serial+=hex(int(tmps[i*8:i*8+8],2))[2:].rjust(2,'0')

print(serial)

拿到serial:653b987431e5a2fc7c3d748fba008869

7456bbe86341ff69aeefb91243553bfc.png

最后拿到flag:0725f66471f85ba9d742eb583c75959c

decryption

送!分!题!泪目了呜呜呜呜呜

加密函数在这里:

c1ac14bb97d64bd038928e4792537407.png

懒得逆逻辑了,直接copy算法爆破(毕竟是按字节加密hhh。

arr=[0x12, 0x45, 0x10, 0x47, 0x19, 0x49, 0x49, 0x49, 0x1A, 0x4F, 0x1C, 0x1E, 0x52, 0x66, 0x1D, 0x52, 0x66, 0x67, 0x68, 0x67, 0x65, 0x6F, 0x5F, 0x59, 0x58, 0x5E, 0x6D, 0x70, 0xA1, 0x6E, 0x70, 0xA3]

flag=""

for i in range(32):

for j in range(32,127):

v5=j

v4=i

while True:

v3=2*(v4&v5)

v5^=v4

v4=v3

if v3==0:

break

if v5^0x23==arr[i]:

flag+=chr(j)

break

print(flag)

flag:1e1a6edc1c52e80b539127fccd48f05a

babyre

723cafc8d2704187109e1ee460b12876.png

乍一看主函数逻辑很简单,实际上如果不patch的话是不可能完成的(byte_41A808这里有不可见字符。

看到这篇wp(HWS计划2021硬件安全冬令营线上选拔赛 re wp_20000s的博客-CSDN博客)里说是“内部加载dll,hook了驱动”,好像是这样,不过为啥动态调试的时候没有hook到呢(tcl不懂,选择手动hook

然后注意到了2047这个奇怪的数字,正常来说ZwLoadDriver应该是不会返回这种东西的(吧,所以从左边函数表一个一个翻下来找到了关键函数sub_412A20()

5896d1d8c56eaaa8fc15d5c108f143af.png

结合前面v9的赋值可以猜测,应该调用这个函数,并且a2是v9,才能对输入的Str调用v4进行加密得到byte_41A808。

所以先直接暴力patch

0ed44c52e83f4dfb0133770cd98b59cd.png

4fa1476517a426e0bc3db43b0cbf2edc.png

然后下断点,动态调试,可以看到v4实际上是:

f57c8e158447ee198ee117c1fe7462ef.png

噢,感谢密钥的提示,不用看了hhh,直接sm4解密(从SM4 python_dumpling-cat的博客-CSDN博客抓了脚本改了改)。

# S盒

SboxTable = \

[

0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,

0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,

0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,

0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,

0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,

0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,

0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,

0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,

0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,

0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,

0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,

0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,

0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,

0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,

0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,

0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,

]

# 常数FK

FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc] ; ENCRYPT = 0 ;DECRYPT = 1

# 固定参数CK

CK = \

[

0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,

0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,

0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,

0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,

0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,

0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,

0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,

0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279

]

def padding(data): #填充

print ("plaintext:\t", bytes (data))

file_data_list = list(data)

lenth = len (file_data_list)

#print ("data lenth:", lenth)

remainder = lenth % 16

if remainder != 0:

i=16-remainder #i为需要填充的位数

#print ("padding numbers = ", i)

for j in range(i):

file_data_list.append(i) #填充 char 0-(i-1)

if remainder == 0:

for k in range(16):

file_data_list.append (0x08) #刚好的话 填充0x08

print("after PKCS5 padding:",file_data_list)

return file_data_list

def list_4_8_to_int32(key_data): # 列表4个8位,组成32位

return int ((key_data[0] << 24) | (key_data[1] << 16) | (key_data[2] << 8) | (key_data[3]))

def n32_to_list4_8(n): #把n分别取32位的每8位放入列表

return [int ((n >> 24) & 0xff), int ((n >> 16) & 0xff), int ((n >> 8) & 0xff), int ((n) & 0xff)]

#循环左移

def shift_left_n(x, n):

return int (int (x << n) & 0xffffffff)

def shift_logical_left(x, n):

return shift_left_n (x, n) | int ((x >> (32 - n)) & 0xffffffff) #两步合在一起实现了循环左移n位

def XOR(a, b):

return list (map (lambda x, y: x ^ y, a, b))

#s盒查找

def sbox(idx):

return SboxTable[idx]

def extended_key_LB(ka): #拓展密钥算法LB

a = n32_to_list4_8 (ka) #a是ka的每8位组成的列表

b = [sbox (i) for i in a] #在s盒中每8位查找后,放入列表b,再组合成int bb

bb = list_4_8_to_int32 (b)

rk = bb ^ (shift_logical_left (bb, 13)) ^ (shift_logical_left (bb, 23))

return rk

def linear_transform_L(ka): #线性变换L

a = n32_to_list4_8 (ka)

b = [sbox (i) for i in a]

bb = list_4_8_to_int32 (b) #bb是经过s盒变换的32位数

return bb ^ (shift_logical_left (bb, 2)) ^ (shift_logical_left (bb, 10)) ^ (shift_logical_left (bb, 18)) ^ (shift_logical_left (bb, 24)) #书上公式

def sm4_round_function(x0, x1, x2, x3, rk): #轮函数

return (x0 ^ linear_transform_L (x1 ^ x2 ^ x3 ^ rk))

class Sm4 (object):

def __init__(self):

self.sk = [0] * 32

self.mode = ENCRYPT

def sm4_set_key(self, key_data, mode): #先算出拓展密钥

self.extended_key_last (key_data, mode)

def extended_key_last(self, key, mode): #密钥扩展算法

MK = [0, 0, 0, 0]

k = [0] * 36

MK[0] = list_4_8_to_int32 (key[0:4])

MK[1] = list_4_8_to_int32 (key[4:8])

MK[2] = list_4_8_to_int32 (key[8:12])

MK[3] = list_4_8_to_int32 (key[12:16])

k[0:4] = XOR (MK, FK)

for i in range (32):

k[i + 4] = k[i] ^ (extended_key_LB (k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ CK[i]))

self.sk = k[4:] #生成的32轮子密钥放到sk中

self.mode = mode

if mode == DECRYPT: #解密时rki逆序

self.sk.reverse ()

def sm4_one_round(self, sk, in_put): #一轮算法 ,4个32位的字=128bit=16个字节(8*16)

item = [list_4_8_to_int32 (in_put[0:4]), list_4_8_to_int32 (in_put[4:8]), list_4_8_to_int32 (in_put[8:12]),

list_4_8_to_int32 (in_put[12:16])] #4字节一个字,把每4个字节变成32位的int

x=item

for i in range (32):

temp=x[3]

x[3] = sm4_round_function (x[0], x[1], x[2], x[3], sk[i]) #x[3]成为x[4]

x[0]=x[1]

x[1]=x[2]

x[2]=temp

print("%dround----->"%(i+1),"key:%-12d\n"%sk[i],"result:",x)

res=x

# res = reduce (lambda x, y: [x[1], x[2], x[3], sm4_round_function (x[0], x[1], x[2], x[3], y)],sk, item) #32轮循环加密

res.reverse ()

rev = map (n32_to_list4_8, res)

out_put = []

[out_put.extend (_) for _ in rev]

return out_put

def encrypt(self, input_data):

# 块加密

output_data = []

tmp = [input_data[i:i + 16] for i in range (0, len (input_data), 16)] #输入数据分块

[output_data.extend (each) for each in map (lambda x: self.sm4_one_round (self.sk, x), tmp)]

return output_data

def encrypt(mode, key, data):

sm4_d = Sm4 ()

sm4_d.sm4_set_key (key, mode)

en_data = sm4_d.encrypt (data)

return en_data

def sm4_crypt_cbc(mode, key, iv, data):

sm4_d = Sm4 ()

sm4_d.sm4_set_key (key, mode)

en_data = sm4_d.sm4_crypt_cbc (iv, data)

return en_data

if __name__ == "__main__":

key_data=[ord(c) for c in "Ez_5M4_C1pH@r!!!"]

en_data=b"\xEA\x63\x58\xB7\x8C\xE2\xA1\xE9\xC5\x29\x8F\x53\xE8\x08\x32\x59\xAF\x1B\x67\xAE\xD9\xDA\xCF\xC4\x72\xFF\xB1\xEC\x76\x73\xF3\x06"

sm4_d = Sm4 ()

sm4_d.sm4_set_key (key_data, DECRYPT)

de_data = sm4_d.encrypt (en_data)

flag=''.join(list(map(chr,de_data)))

print(flag)

flag:42b061b4cb41cfa89ca78047bde1856e

Enigma

7bc78b5dd35dc7a2db69cf0b64ca9d02.png

逻辑很清晰,直接分析loc_4018F0。

7530dc326fae329f0056eceecc43ae8b.png

这边有一个SetUnhandledExceptionFilter捕获异常。

一开始以为下面的都是花指令,但每次都是0xC7转不了就很奇怪,异常+0xC7想到了平时用的CC断点,再加上sub_401630看起来像是个虚拟机,感觉0xC7是一个中断号(计组刚学,捕获中断然后用sub_401630处理。

ade66279c9a36c56e4e03b47c2a7d430.png

每一个case里sub_4011B0是取值,另一个函数则是处理,v18是指令长度。

通过对ExceptionInfo的查阅发现,v19=*(_DWORD *)(*(_DWORD *)(a1 + 4) + 0xB8)开始是异常发生的地址即0xC7的地址,最后则应该是异常处理完毕后要返回的用户空间代码地址。(这么一推异常应该是0xC7FF才对

opcode是0xC7FF的下一个即v19+2,后面的为操作数。

二级函数里面都是对a1[40]~a1[44]进行操作,猜测是寄存器。

于是可以分析出:

// xx为[1,5],rx为对应的a1[40]~a1[44]寄存器,是二级函数中的case

// 1->a1[44],2->a1[41],3->a1[43],4->a1[42],5->a1[40]

// imm为立即数

C7 FF 00 xx imm //add rx,imm

C7 FF 01 xx imm //sub rx,imm

C7 FF 02 xx //inc rx

C7 FF 03 xx //dec rx

C7 FF 04 xx imm //and rx,imm

C7 FF 05 xx imm //or rx,imm

C7 FF 06 xx imm //xor rx,imm

C7 FF 07 xx imm //shl rx,imm

C7 FF 08 xx imm //shr rx,imm

然后把下面的机器码中带有C7 FF系列指令以外的先转换成code。

45aef0eb6b9ffc704c5118b914bd0173.png

接下来处理这些异常部分的机器码,比如第一个中断翻译出来是and r1,0,即将r1清空,汇编习惯上更常用xor r1,r1,先用注释标注。

9af75b99349fb661a001b4afa503207c.png

然后根据上下文以及上图可以推断出r1-r5分别对应如下:

r1 -> eax;

r2 -> ebx;

r3 -> ecx;

r4 -> edx;

r5 -> esi;

patch程序,多余字节直接nop(因为不记得有些机器码了所以直接用keypatch。

13699a7de1d44c8f3ed0992dfa7b7b8a.png

然后得到反编译的加密函数。

d4fc48c1b8cef122e59788d8ae8ec8eb.png

根据逻辑逆向写出exp:

from hashlib import md5

dst="938b8f431268f7907a4b6e421301b42120738d68cb19fcf8b26bc4abc89b8d22"

output=[]

for i in range(0,64,2):

output.append(int("0x"+dst[i:i+2],16))

key=[0x42, 0x69, 0x65, 0x72]

tmp2=[output[0]]

for i in range(1,32):

tmp2.append(output[i]^tmp2[i-1]^key[i&3])

tmp1=[]

for i in range(32):

tmp1.append(((tmp2[i]&0xf8)>>3)|((tmp2[(i-1)&0x1F]&0x07)<<5))

v0=0

arr=[]

for _ in range(32):

v0=(v0+17)&0x1F

arr.append(v0)

flag=['-' for _ in range(32)]

for i in range(0,32,2):

flag[arr[i]],flag[arr[i+1]]=chr(tmp1[arr[i+1]]),chr(tmp1[arr[i]])

flag=''.join(flag)

print(md5(flag).hexdigest())

flag:751542a09b8b341dda23ebfc387a5e91

内核安全

easy_kernel

是个ring3调用ring0的题(前半部分。

从ring3看起,v7是flag,走了sub_401005函数。

100eb44db3c666aa8b5f6b02509fc5ee.png

跳到最后有

cd5b3a1555872255c2d3b65df6509308.png

int 2E,查了一下是用户模式进入内核模式的中断,于是去看ring0(即DriverXP.sys,调用号是186。

c1b7180f0ca67f30ec09c2bd882f7f2b.png

2e0ac5912886dbb06fac99e1c80aa4aa.png

可以看到这里有186,并且参数个数也是6个,猜测sub_401340就是调用的函数。

进一步可以看到,a1==-1,刚好也吻合sub_401005的第一个参数。

a7acd5830e558481bcdb954774d7c11c.png

findcrypt可以看到DES,并且查交叉引用和反编译代码证实sub_401680就是DES加密过程。

01386f0293b60009ca56c897f905e09c.png

可以看到这边密钥只用了"}aglf_T_ton_5i_sihT_yrroS{galf"的前八字节。

e127844d39833c0e2a58b1e51240592c.png

第一层加密确认,从ring3继续往下看。

de54bc433e5fac47b96233adb9aa067b.png

这个MEMORY[0x5804E8]实在是没看出来是什么,看汇编是call fword ptr,查了一下是个长调用,返回用retf。

gdt表也懒得查,于是在ring3的汇编这里从头开始一个一个往下看retf,准备把碰到的都试一遍。

7e5e769b48f7e7e10f663a2c3441f0b6.png

诶,看到一个,转函数看看。

6462e782468d86c5399dcbeabf710ec0.png

代到exp里试了一下,发现成了,运气很好hhh,最开始这个就是MEMORY[0x5804E8]。

ade6f1aebc49a5c8f91f398bdbd6611e.png

exp:

from Crypto.Cipher import DES

from hashlib import md5

key='}aglf_T_ton_5i_sihT_yrroS{galf'[:8]

c=[0xB2, 0xC4, 0x86, 0xD5, 0x54, 0x6C, 0x38, 0xAD, 0xBD, 0x69, 0xD4, 0xE9, 0x44, 0x47, 0x36, 0x21, 0x99, 0x91, 0xFB, 0x13, 0x70, 0xD8, 0x6B, 0xE4, 0x80, 0x12, 0xE2, 0x43, 0x2A, 0x4B, 0x49, 0x8E]

for i in range(30,-1,-1):

c[i]^=c[i+1]

cipher=''.join(list(map(chr,c)))

des=DES.new(key)

flag=des.decrypt(cipher)

print(flag)

print(md5(flag).hexdigest())

flag:c1878dfb2b0c23c74ec4e6650d8f7004

固件安全

NodeMCU

strings nodemcu.bin > str.out,导出字符串后直接查找flag即可

c91288d59bced40ca41ec754abce4d93.png

flag:6808dcf0-526e-11eb-92de-acde48001122

STM

用bin2hex将bin文件转为hex文件,然后用ida加载hex。

查找STM的datasheet(以常见的STM32F103CB为参照)可知flash映射在0x08000000地址上

ac27a5b14c66ddcffda59e870b8398b4.png

于是载入ida的时候还要把基址改成0x08000000。

然后查找字符串,直接看Hello World的交叉引用,找到主函数。

543d2585a1e01eca508dbba15316c631.png

6618e4c76d22cb695a05c21923ae2470.png

根据相应的函数功能猜测并命名,sub_8000314是加密函数。

090192c275ec87577ce21a48f459ed2d.png

逻辑很简单,直接写exp:

arr=[0x7D, 0x77, 0x40, 0x7A, 0x66, 0x30, 0x2A, 0x2F, 0x28, 0x40, 0x7E, 0x30, 0x33, 0x34, 0x2C, 0x2E, 0x2B, 0x28, 0x34, 0x30, 0x30, 0x7C, 0x41, 0x34, 0x28, 0x33, 0x7E, 0x30, 0x34, 0x33, 0x33, 0x30, 0x7E, 0x2F, 0x31, 0x2A, 0x41, 0x7F, 0x2F, 0x28, 0x2E, 0x64]

for x in arr:

print(chr((x^0x1E)+3),end='')

flag{1749ac10-5389-11eb-90c1-001c427bd493}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值