白盒AES加密DFA逆向

白盒AES加密DFA逆向

樣本下載

DFA(Differential Fault Analysis)

2591919-74f335f407cad32c.png

簡單來說就是在倒數(shù)第一輪列混合和倒數(shù)第二輪列混合之間(在AES-128中也就是第8輪和第9輪之間),修改此時中間密文的一個字節(jié),會導(dǎo)致最終密文和正確密文有4個字節(jié)的不同。通過多次的修改,得到多組錯誤的密文,然后通過正確密文和這些錯誤密文能夠推算出第10輪的密鑰(加密模式下),繼而能推算出原始密鑰。

實例

public class Wbaes extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public Wbaes() {
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("").build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM();
        vm.setJni(this);
        vm.setVerbose(false);
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/wbaes/ex2/libhoneybee.so"), true);
        module = dm.getModule();
    }

    public void call_wb() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new ByteArray(vm, "everhu".getBytes())));
        Number ret = module.callFunction(emulator, 0x92cd, list.toArray());
        byte[] bytes = (byte[]) vm.getObject(ret.intValue()).getValue();
        System.out.println(Hex.encodeHexString(bytes));
    }

    public static void main(String[] args) {
        Wbaes test = new Wbaes();
        test.call_wb();
    }
}
2591919-92252e061ed5bb14.png

接下來分析白盒AES加密

Java_com_mucfc_honeybee_DataProcessor_desensitization

2591919-0c89c55ca2c9f377.png

wbEncrypt

2591919-3d0b24de67ad8dc6.png

encryptBlock

2591919-1fb59f3a191749cc.png

可以看出encryptBlock并不完全是塊加密,它在加密的前后進(jìn)行了一些異或操作,encdec才是真正執(zhí)行塊加密的函數(shù)

encdec

2591919-f850a5cd6b5c31f2.png

下斷點看看encryptBlock的輸入輸出

2591919-a206af5804b76bc0.png
2591919-e1a3503f63a5472c.png
2591919-6d752b93cc4b9d5b.png

同樣的,看看encdec的輸入輸出

2591919-88eab0bf19066ffb.png
2591919-55846479a36be126.png
2591919-3f3fb2de29d3987f.png

流程如下

2591919-ba45140e685132a7.png

接下來,進(jìn)行dfa攻擊

2591919-abec2c40aaa2b3d2.png
2591919-038f2a6c313cd73b.png

在for循環(huán)執(zhí)行第9輪時,隨機(jī)修改state的一個字節(jié)。

public class Wbaes extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public Wbaes() {
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("").build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM();
        vm.setJni(this);
        vm.setVerbose(false);
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/wbaes/ex2/libhoneybee.so"), true);
        module = dm.getModule();
    }

    public int randInt(int min, int max) {
        Random rand = new Random();
        return rand.nextInt(max - min) + min;
    }

    public void patch() {
        // patch log
        emulator.getMemory().pointer(module.base + 0x9342).setInt(0, 0xbf00bf00);
        emulator.getMemory().pointer(module.base + 0x9354).setInt(0, 0xbf00bf00);
    }

    public void dfa() {
        IHookZz hookZz = HookZz.getInstance(emulator);
        hookZz.wrap(module.base + 0x9e59, new WrapCallback<HookZzArm32RegisterContext>() {
            @Override
            public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                final Pointer output = ctx.getR0Pointer();
                ctx.push(output);

                emulator.attach().addBreakPoint(module.base + 0xA2AC, new BreakPointCallback() {
                    int count = 0;
                    @Override
                    public boolean onHit(Emulator<?> emulator, long address) {
                        count += 1;
//                        System.out.println(count);
                        if (count == 9) {
                            output.setByte(randInt(0, 15), (byte) randInt(0, 255));
                        }
                        return true;
                    }
                });
            }

            @Override
            public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.pop();
                byte[] outputHex = output.getByteArray(0, 16);
                System.out.println(Hex.encodeHexString(outputHex));
            }
        });
    }

    public void call_wb() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new ByteArray(vm, "everhu".getBytes())));
        Number ret = module.callFunction(emulator, 0x92cd, list.toArray());
        byte[] bytes = (byte[]) vm.getObject(ret.intValue()).getValue();
//        System.out.println(Hex.encodeHexString(bytes));
    }

    public static void main(String[] args) {
        Wbaes test = new Wbaes();
        test.patch();
        test.dfa();
        for (int i = 0; i<32; i++) {
            test.call_wb();
        }
    }
}

dfa隨機(jī)修改了state的一個字節(jié),并在encdec返回時打印state的值。此外patch了日志打印,方便復(fù)制。

2591919-933bf3de30128b61.png

可以看出每個錯誤密文和正確密文只有4個字節(jié)的不同,說明我們hook的時機(jī)是對的;如果全部不同,說明時機(jī)太早了;只有一個不同則說明時機(jī)太晚了。

接下來就是用JeanGrey/phoenixAES根據(jù)錯誤密文還原密鑰,第一行為正確密文,其他行為錯誤密文。

import phoenixAES

data = """3ddf3ef1c257e990555ee834a1c6f3f2
3d103ef17c57e990555ee883a1c6cbf2
3ddf3cf1c243e9909e5ee834a1c6f37f
3ddf3ee2c2570a90557ae834f1c6f3f2
3ddf3ec0c257df905597e83473c6f3f2
73df3ef1c257e904555e9134a1d9f3f2
3ddf3ef9c2570e9055cde8340ac6f3f2
3df43ef1e957e990555ee859a1c659f2
3ddf75f1c2dce990755ee834a1c6f320
3ddf3ed4c2577a905542e834e1c6f3f2
dddf3ef1c257e9f0555ef434a1d8f3f2
2ddf3ef1c257e9f4555edf34a101f3f2
3ddf3e51c2576f9055b1e8342ec6f3f2
3ddf98f1c2dae990045ee834a1c6f316
42df3ef1c257e9cc555e7d34a1b2f3f2
3ddfbbf1c280e990d25ee834a1c6f3be
3ddf3eb9c257ed90551de8347ac6f3f2
3ddf3ef4c257bb90554ae834cac6f3f2
3d433ef11757e990555ee82ba1c698f2
3ddf3eebc2575b905586e83477c6f3f2
3ddf13f1c2b4e990ae5ee834a1c6f399
3d203ef1a157e990555ee811a1c646f2
3ddf3e2cc257509055ace83418c6f3f2
9ddf3ef1c257e94c555e2534a19bf3f2
3ddf6bf1c204e990c05ee834a1c6f345
45df3ef1c257e9ce555e5434a141f3f2
3ddf3e8ec2570090556fe83449c6f3f2
3d4c3ef15657e990555ee84ea1c605f2
3db13ef1ac57e990555ee8aea1c67bf2
3ddf3e89c25716905527e83441c6f3f2
3ddf3e7bc257ec9055e4e83408c6f3f2
3d533ef11757e990555ee85ca1c610f2
3ddf7df1c2b7e990785ee834a1c6f377
"""

with open('crackfile', 'w') as fp:
    fp.write(data)

phoenixAES.crack_file('crackfile', [], True, False, verbose=2)
2591919-94636e33d457e8be.png

然后并沒有還原出密鑰,一開始懷疑是錯誤密文少了,開始加,從50、100、200、500到1000,并不能正確還原。

但是phoenixAES顯示了每個錯誤密文對應(yīng)的group,說明我們進(jìn)行dfa攻擊的時機(jī)是沒問題的,所以還是要看看so里面的實現(xiàn)。

2591919-e6d0dabc61158df2.png
2591919-10359fe925ab5f64.png

函數(shù)開始和結(jié)束都有個idxTranspose函數(shù)

2591919-860630be2dde8293.png

實際上,state常用4x4的矩陣來展示,而該函數(shù)則是對其進(jìn)行了轉(zhuǎn)置。而在dfa中,錯誤密文共有4種情況,這些數(shù)組轉(zhuǎn)置后錯誤的位置是不變的,所以才會出現(xiàn)上面的情況,明明phoenixAES顯示了每個錯誤密文對應(yīng)的group,但是卻不能正確推算出密鑰。

2591919-8e23b79ad746f351.png
import phoenixAES

data = """3ddf3ef1c257e990555ee834a1c6f3f2
3d103ef17c57e990555ee883a1c6cbf2
3ddf3cf1c243e9909e5ee834a1c6f37f
3ddf3ee2c2570a90557ae834f1c6f3f2
3ddf3ec0c257df905597e83473c6f3f2
73df3ef1c257e904555e9134a1d9f3f2
3ddf3ef9c2570e9055cde8340ac6f3f2
3df43ef1e957e990555ee859a1c659f2
3ddf75f1c2dce990755ee834a1c6f320
3ddf3ed4c2577a905542e834e1c6f3f2
dddf3ef1c257e9f0555ef434a1d8f3f2
2ddf3ef1c257e9f4555edf34a101f3f2
3ddf3e51c2576f9055b1e8342ec6f3f2
3ddf98f1c2dae990045ee834a1c6f316
42df3ef1c257e9cc555e7d34a1b2f3f2
3ddfbbf1c280e990d25ee834a1c6f3be
3ddf3eb9c257ed90551de8347ac6f3f2
3ddf3ef4c257bb90554ae834cac6f3f2
3d433ef11757e990555ee82ba1c698f2
3ddf3eebc2575b905586e83477c6f3f2
3ddf13f1c2b4e990ae5ee834a1c6f399
3d203ef1a157e990555ee811a1c646f2
3ddf3e2cc257509055ace83418c6f3f2
9ddf3ef1c257e94c555e2534a19bf3f2
3ddf6bf1c204e990c05ee834a1c6f345
45df3ef1c257e9ce555e5434a141f3f2
3ddf3e8ec2570090556fe83449c6f3f2
3d4c3ef15657e990555ee84ea1c605f2
3db13ef1ac57e990555ee8aea1c67bf2
3ddf3e89c25716905527e83441c6f3f2
3ddf3e7bc257ec9055e4e83408c6f3f2
3d533ef11757e990555ee85ca1c610f2
3ddf7df1c2b7e990785ee834a1c6f377
"""

idx = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]

def transpose(data):
    return bytes([data[idx[i]] for i in range(16)])

with open('crackfile', 'w') as fp:
    for item in data.splitlines():
        if item:
            item = transpose(bytes.fromhex(item)).hex()
            fp.write(item + '\n')

phoenixAES.crack_file('crackfile', [], True, False, verbose=2)
2591919-25d2e1073f89ad17.png

得到了第10輪的密鑰,然后使用SideChannelMarvels/Stark還原初始密鑰

2591919-f661d3c28b4939b6.png

因此5415246EED9AEA9477EB680542E48DDA就是AES的密鑰,驗證一下

cryptor = AES.new(bytes.fromhex('5415246EED9AEA9477EB680542E48DDA'), AES.MODE_ECB)
data = cryptor.decrypt(bytes.fromhex('3ddf3ef1c257e990555ee834a1c6f3f2'))
print(data.hex())
2591919-80366be4200fbc7c.png

同樣需要對輸入輸出進(jìn)行轉(zhuǎn)置

from Crypto.Cipher import AES

idx = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]

def transpose(data):
    return bytes([data[idx[i]] for i in range(16)])

cryptor = AES.new(bytes.fromhex('5415246EED9AEA9477EB680542E48DDA'), AES.MODE_ECB)
data = cryptor.decrypt(transpose(bytes.fromhex('3ddf3ef1c257e990555ee834a1c6f3f2')))
print(data.hex())
print(transpose(data).hex())
2591919-9617647c561ff69e.png

encdec的輸入一致。

代碼

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

idx = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]

_KEY = bytes.fromhex('5415246EED9AEA9477EB680542E48DDA')

def transpose(data):
    return bytes([data[idx[i]] for i in range(16)])

def encrypt(data):
    cryptor = AES.new(_KEY, AES.MODE_ECB)
    data = pad(data, AES.block_size)
    bucket = []
    for x in range(0, len(data), 16):
        block = data[x: x+16]
        block = bytes(block[i]^(block[i-1] if i else 0xa6) for i in range(16))
        block = transpose(block)
        block = cryptor.encrypt(block)
        block = transpose(block)
        bucket.append(block[0] ^ 0xe)
        for i in range(1, 16):
            bucket.append(block[i] ^ (bucket[-1] if i!=1 else block[0]))
    return bytes(bucket)

if __name__ == '__main__':
    data = encrypt(b'everhu')
    print(data.hex())

Reference

強(qiáng)推白龍的知識星球,里面的白盒加密系列深入淺出,讓人很方便理解dfa的原理,原文見于第三講——差分故障攻擊的原理

或者也可以看看一種還原白盒AES秘鑰的方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內(nèi)容