【wp】HWS計劃2021硬體安全冬令營線上選拔賽
逆向手在夾縫中艱難求生系列。
這篇真的存粹是做題筆記了,對內核驅動啥的不太懂,pwn也不會,能做出來的題都是硬逆出來的(
childre最後死活沒整出來,後來看大佬的wp才知道對子進程有修改(。)嗚嗚嗚多進程真的不太會啊。
REVERSE
obfu
一堆密碼堆起來的題(密碼菜雞卑微落淚
按邏輯來說拿到serial number就能拿到flag了。
通過findcrypt找到三個加密演算法的常量,通過交叉引用確定函數用途。


v10和v8相比之下v8更容易出,v8跟輸入沒有關係,相當於已知字元串(=md5("admin")。
然後就是enctrpy函數。

按照順序是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

最後拿到flag:0725f66471f85ba9d742eb583c75959c
decryption
送!分!題!淚目了嗚嗚嗚嗚嗚
加密函數在這裡:

懶得逆邏輯了,直接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

乍一看主函數邏輯很簡單,實際上如果不patch的話是不可能完成的(byte_41A808這裡有不可見字元。
看到這篇wp(HWS計劃2021硬體安全冬令營線上選拔賽 re wp_20000s的部落格-CSDN部落格)里說是「內部載入dll,hook了驅動」,好像是這樣,不過為啥動態調試的時候沒有hook到呢(tcl不懂,選擇手動hook
然後注意到了2047這個奇怪的數字,正常來說ZwLoadDriver應該是不會返回這種東西的(吧,所以從左邊函數表一個一個翻下來找到了關鍵函數sub_412A20()

結合前面v9的賦值可以猜測,應該調用這個函數,並且a2是v9,才能對輸入的Str調用v4進行加密得到byte_41A808。
所以先直接暴力patch


然後下斷點,動態調試,可以看到v4實際上是:

噢,感謝密鑰的提示,不用看了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

邏輯很清晰,直接分析loc_4018F0。

這邊有一個SetUnhandledExceptionFilter捕獲異常。
一開始以為下面的都是花指令,但每次都是0xC7轉不了就很奇怪,異常+0xC7想到了平時用的CC斷點,再加上sub_401630看起來像是個虛擬機,感覺0xC7是一個中斷號(計組剛學,捕獲中斷然後用sub_401630處理。

每一個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。

接下來處理這些異常部分的機器碼,比如第一個中斷翻譯出來是and r1,0,即將r1清空,彙編習慣上更常用xor r1,r1 ,先用注釋標註。

然後根據上下文以及上圖可以推斷出r1-r5分別對應如下:
r1 -> eax;
r2 -> ebx;
r3 -> ecx;
r4 -> edx;
r5 -> esi;
patch程式,多餘位元組直接nop(因為不記得有些機器碼了所以直接用keypatch。

然後得到反編譯的加密函數。

根據邏輯逆向寫出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函數。

跳到最後有

int 2E,查了一下是用戶模式進入內核模式的中斷,於是去看ring0(即DriverXP.sys,調用號是186。


可以看到這裡有186,並且參數個數也是6個,猜測sub_401340就是調用的函數。
進一步可以看到,a1==-1,剛好也吻合sub_401005的第一個參數。

findcrypt可以看到DES,並且查交叉引用和反編譯程式碼證實sub_401680就是DES加密過程。

可以看到這邊密鑰只用了"}aglf_T_ton_5i_sihT_yrroS{galf"的前八位元組。

第一層加密確認,從ring3繼續往下看。

這個MEMORY[0x5804E8]實在是沒看出來是什麼,看彙編是call fword ptr,查了一下是個長調用,返回用retf。
gdt表也懶得查,於是在ring3的彙編這裡從頭開始一個一個往下看retf,準備把碰到的都試一遍。

誒,看到一個,轉函數看看。

代到exp里試了一下,發現成了,運氣很好hhh,最開始這個就是MEMORY[0x5804E8]。

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即可

flag:6808dcf0-526e-11eb-92de-acde48001122
STM
用bin2hex將bin文件轉為hex文件,然後用ida載入hex。
查找STM的datasheet(以常見的STM32F103CB為參照)可知flash映射在0x08000000地址上

於是載入ida的時候還要把基址改成0x08000000。
然後查找字元串,直接看Hello World的交叉引用,找到主函數。


根據相應的函數功能猜測並命名,sub_8000314是加密函數。

邏輯很簡單,直接寫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}

