为了避免任何不必要的麻烦,仅仅提供思路,不提供分析的样本下载,仅仅是自己学习和交流,不便之处请见谅。
0x0 前言
阅读本文大概需要浪费20分钟时间
需要一份trace后的汇编执行流(可以通过unidbg获取)
0x1 sha256
没什么特别的理由选它,只是因为刚好看到了,首先看它的伪代码
Note 1: All variables are 32 bit unsigned integers and addition is calculated modulo 232
Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 ≤ i ≤ 63
Note 3: The compression function uses 8 working variables, a through h
Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
and when parsing message block data from bytes to words, for example,
the first word of the input message "abc" after padding is 0x61626380
Initialize hash values:
(first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
h0 := 0x6a09e667
h1 := 0xbb67ae85
h2 := 0x3c6ef372
h3 := 0xa54ff53a
h4 := 0x510e527f
h5 := 0x9b05688c
h6 := 0x1f83d9ab
h7 := 0x5be0cd19
Initialize array of round constants:
(first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
k[0..63] :=
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
Pre-processing (Padding):
begin with the original message of length L bits
append a single '1' bit
append K '0' bits, where K is the minimum number >= 0 such that (L + 1 + K + 64) is a multiple of 512
append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits
such that the bits in the message are: <original message of length L> 1 <K zeros> <L as 64 bit integer> , (the number of bits will be a multiple of 512)
Process the message in successive 512-bit chunks:
break message into 512-bit chunks
for each chunk
create a 64-entry message schedule array w[0..63] of 32-bit words
(The initial values in w[0..63] don't matter, so many implementations zero them here)
copy chunk into first 16 words w[0..15] of the message schedule array
Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array:
for i from 16 to 63
s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10)
w[i] := w[i-16] + s0 + w[i-7] + s1
Initialize working variables to current hash value:
a := h0
b := h1
c := h2
d := h3
e := h4
f := h5
g := h6
h := h7
Compression function main loop:
for i from 0 to 63
S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
ch := (e and f) xor ((not e) and g)
temp1 := h + S1 + ch + k[i] + w[i]
S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
maj := (a and b) xor (a and c) xor (b and c)
temp2 := S0 + maj
h := g
g := f
f := e
e := d + temp1
d := c
c := b
b := a
a := temp1 + temp2
Add the compressed chunk to the current hash value:
h0 := h0 + a
h1 := h1 + b
h2 := h2 + c
h3 := h3 + d
h4 := h4 + e
h5 := h5 + f
h6 := h6 + g
h7 := h7 + h
Produce the final hash value (big-endian):
digest := hash := h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7
findcrypto等静态识别插件的识别点是魔数或者k表,但一来这部分很容易在未运行时加密,运行时才动态解密,二来它是个很容易的魔改点,所以我选择的识别点在
Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array:
for i from 16 to 63
s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10)
w[i] := w[i-16] + s0 + w[i-7] + s1
主要原因:
次要原因 :这一处的代码如果不做特殊处理,很大概率会被编译在一个基本块,其次这是个足够次数的循环(48次),它的特征足够多,9次运算
#一条trace的栗子: 0x400512dc: "bne #0x400512ca"
trace.pc = 0x400512dc
trace.insn = bne
trace.rd = 0x400512ca
if "bne" in trace.insn or "beq" in trace.insn :
#筛选出条件跳转的执行流
call_line = trace.pc - trace.rd
if 0 < call_line < 100:
#往一个更低的地址跳,循环体的大小有待商榷
if alltrace.pc.count(trace.pc) == 48:
输出trace.dst -> trace.pc 的汇编
然后可以得到48的倍数次的基本块,可以从其中找到一个典型的trace块
[04eb8002] 0x40025dee: "add.w r2, r4, r0, lsl #2" r4=0xbffff2e8 r0=0x0 => r2=0xbffff2e8
[0130 ] 0x40025df2: "adds r0, #1" r0=0x0 => r0=0x1
[3028 ] 0x40025df4: "cmp r0, #0x30" r0=0x1 => cpsr: N=1, Z=0, C=0, V=0
[966b ] 0x40025df6: "ldr r6, [r2, #0x38]" r2=0xbffff2e8 => r6=0x61336165
[5368 ] 0x40025df8: "ldr r3, [r2, #4]" r2=0xbffff2e8 => r3=0x742f6170
[576a ] 0x40025dfa: "ldr r7, [r2, #0x24]" r2=0xbffff2e8 => r7=0x32333765
[4feaf645] 0x40025dfc: "ror.w r5, r6, #0x13" r6=0x61336165 => r5=0x6c2cac26
[85ea9625] 0x40025e00: "eor.w r5, r5, r6, lsr #10" r5=0x6c2cac26 r6=0x61336165 => r5=0x6c34e0fe
[3944 ] 0x40025e04: "add r1, r7" r1=0x2f726573 r7=0x32333765 => r1=0x61a59cd8
[85ea7646] 0x40025e06: "eor.w r6, r5, r6, ror #17" r5=0x6c34e0fe r6=0x61336165 => r6=0xdc865067
[4feab345] 0x40025e0a: "ror.w r5, r3, #0x12" r3=0x742f6170 => r5=0xd85c1d0b
[85ead305] 0x40025e0e: "eor.w r5, r5, r3, lsr #3" r5=0xd85c1d0b r3=0x742f6170 => r5=0xd6d9f125
[85eaf315] 0x40025e12: "eor.w r5, r5, r3, ror #7" r5=0xd6d9f125 r3=0x742f6170 => r5=0x3631afe7
[2944 ] 0x40025e16: "add r1, r5" r1=0x61a59cd8 r5=0x3631afe7 => r1=0x97d74cbf
[3144 ] 0x40025e18: "add r1, r6" r1=0x97d74cbf r6=0xdc865067 => r1=0x745d9d26
[1164 ] 0x40025e1a: "str r1, [r2, #0x40]" r1=0x745d9d26 r2=0xbffff2e8
[1946 ] 0x40025e1c: "mov r1, r3" r3=0x742f6170 => r1=0x742f6170
[e6d1 ] 0x40025e1e: "bne #0x40025dee"
9处运算指令与上面的伪代码一一对应,0x40025df4控制循环次数48次,输出w[i-16],整个循环体中,除了循环条件外开始的第一个add,即0x40025e16 中的r1,也就是疑似填充后的明文块,验证是否确实为sha256的方法来自于伪代码中的
append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits
such that the bits in the message are: <original message of length L> 1 <K zeros> <L as 64 bit integer> , (the number of bits will be a multiple of 512)
即判断明文块的末尾附加的长度是否等于消息的长度,大致格式如下
00000000 d8 9c a5 61 35 30 33 34 33 38 33 36 32 30 80 00 |Ø.¥a5034383620..|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 |...............p|
同理可得输出的密文,这个方法在微调(嗯,微调)之后可以在我分析过的样本上,可以比较快速的处理sha256(嗯,没错,就分析了一两个)
0x2 尾声
目前看来这个分析方法不进行微调的话,适用范围不高,仅仅只能处理经过ollvm中的fla和bcf的混淆,对于其他混淆,下次在讲