SO reverse entry practical tutorial 11: Shield

Posted by pmaiorana on Sun, 16 Jan 2022 22:06:11 +0100

1, Foreword

For the purpose of learning and research, the simulation execution and algorithm analysis of Shield will not be too detailed, but only the key points will be explained.

2, Unidbg simulation execution

Shield's simulation execution is not difficult. As mentioned earlier, JNItrace can basically solve the problem. Because shield encapsulates parameters through interceptors, in order to achieve more elegant implementation, we introduce OkHttp library into the code

But it's not necessary, it's just more elegant.

package com.article13;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.SystemPropertyHook;
import com.github.unidbg.linux.android.SystemPropertyProvider;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import okhttp3.*;
import okio.Buffer;
import okio.BufferedSink;
import org.apache.commons.codec.binary.Base64;

import java.awt.geom.RectangularShape;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

public class xhs extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private Headers headers;
    private Request request;
    private String url;

    xhs(){
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.xhs").build(); // Create an emulator instance to simulate 32-bit or 64 bit, which is distinguished here
        final Memory memory = emulator.getMemory(); // Memory operation interface of simulator
        memory.setLibraryResolver(new AndroidResolver(23)); // Set system class library resolution
        vm = emulator.createDalvikVM(new File("E:\\unidbg-master\\unidbg-master\\unidbg-android\\src\\test\\resources\\example_binaries\\xhs\\xhs-armv7.apk"));
        vm.setVerbose(true);

        DalvikModule dm = vm.loadLibrary(new File("E:\\unidbg-master\\unidbg-master\\unidbg-android\\src\\test\\resources\\example_binaries\\xhs\\libshield.so"), true);

        vm.setJni(this);
        module = dm.getModule();
        System.out.println("call JNIOnLoad");
        dm.callJNI_OnLoad(emulator);
        

        url = "https://edith.xiaohongshu.com/api/sns/v6/homefeed?oid=homefeed_recommend&cursor_score=&geo=eyJsYXRpdHVkZSI6MC4wMDAwMDAsImxvbmdpdHVkZSI6MC4wMDAwMDB9%0A&trace_id=7e9cea5d-3e7c-3240-bf3d-20e221557b61&note_index=0&refresh_type=1&client_volume=0.60&preview_ad=&loaded_ad=%7B%22ads_id_list%22%3A%5B%5D%7D&personalization=1&pin_note_id=&pin_note_source=&unread_begin_note_id=60da0881000000002103ef52&unread_end_note_id=60e2abe5000000000102aced&unread_note_count=6";

        request = new Request.Builder()
                .url(url)
                .addHeader("X-B3-TraceId", "4a4a3a065c180b0f")
                .addHeader("xy-common-params", "fid=16254550111059c4b478ba32dc122790a4f7e9261f0e&device_fingerprint=20210101000953d097cda53c248c488cadcce5ec7882880173338edc38b45d&device_fingerprint1=20210101000953d097cda53c248c488cadcce5ec7882880173338edc38b45d&launch_id=1625473798&tz=Asia%2FShanghai&channel=PMgdt19935737&versionName=6.97.0.1&deviceId=1d41ebdc-86dd-33ea-9ceb-e9210babd74e&platform=android&sid=session.1625455019284508278852&identifier_flag=4&t=1625473812&project_id=ECFAAF&build=6970181&x_trace_page_current=explore_feed&lang=zh-Hans&app_id=ECFAAF01&uis=light")
                .addHeader("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 10; MIX 2S MIUI/V12.0.2.0.QDGCNXM) Resolution/1080*2160 Version/6.97.0.1 Build/6970181 Device/(Xiaomi;MIX 2S) discover/6.97.0.1 NetType/CellNetwork")
                .build();
        
    }

    // First initialization function
    public void callinitializeNative(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // The first parameter is env
        list.add(0); // The second parameter, the instance method is jobject, and the static method is jclazz. You can directly fill in 0, which is generally not used.
        module.callFunction(emulator, 0x6c11d, list.toArray());
    };

    // Second initialization function
    public long callinitialize(){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // The first parameter is env
        list.add(0); // The second parameter, the instance method is jobject, and the static method is jclazz. You can directly fill in 0, which is generally not used.
        list.add(vm.addLocalObject(new StringObject(vm, "main")));
        Number number = module.callFunction(emulator, 0x6b801, list.toArray())[0];
        return number.longValue();
    }

    // objective function 
    public void callintercept(long ptr){
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // The first parameter is env
        list.add(0); // The second parameter, the instance method is jobject, and the static method is jclazz. You can directly fill in 0, which is generally not used.
        DvmObject<?> chain = vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null);
        list.add(vm.addLocalObject(chain));
        list.add(ptr);
        module.callFunction(emulator, 0x6b9e9, list.toArray());
    };
    public static void main(String[] args) {
        xhs test = new xhs();
        test.callinitializeNative();
        long ptr = test.callinitialize();
        System.out.println("call intercept");
        test.callintercept(ptr);


    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "com/xingin/shield/http/ContextHolder->sLogger:Lcom/xingin/shield/http/ShieldLogger;":{
                return vm.resolveClass("com/xingin/shield/http/ShieldLogger").newObject(signature);
            }
            case "com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;":{
                return new StringObject(vm, "1d41ebdc-86dd-33ea-9ceb-e9210babd74e");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    @Override
    public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "com/xingin/shield/http/ShieldLogger->nativeInitializeStart()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->nativeInitializeEnd()V": {
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->initializeStart()V": {
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->initializedEnd()V": {
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->buildSourceStart()V": {
                return;
            }
            case "okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V": {
                BufferedSink bufferedSink = (BufferedSink) vaList.getObjectArg(0).getValue();
                RequestBody requestBody = (RequestBody) dvmObject.getValue();
                if(requestBody != null){
                    try {
                        requestBody.writeTo(bufferedSink);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->buildSourceEnd()V": {
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->calculateStart()V": {
                System.out.println("calculateStart - Start calculation");
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->calculateEnd()V": {
                System.out.println("calculateEnd - End calculation");
                return;
            }
        }
        super.callVoidMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;":{
                return vm.resolveClass("java/nio/charset/Charset").newObject(Charset.defaultCharset());
            }
            case "com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B":{
                String input = (String) vaList.getObjectArg(0).getValue();
                byte[] result = Base64.decodeBase64(input);
                return new ByteArray(vm, result);
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/PackageInfo->versionCode:I":{
                return 6970181;
            }
        }
        return super.getIntField(vm, dvmObject, signature);
    }

    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "com/xingin/shield/http/ContextHolder->sAppId:I":{
                return -319115519;
            }
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;":
                return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObjectArg(0));
            case "android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
                if(((StringObject) dvmObject.getValue()).getValue().equals("s")){
                    System.out.println("getString :"+vaList.getObjectArg(0).getValue());
                    if (vaList.getObjectArg(0).getValue().equals("main")) {
                        return new StringObject(vm, "");
                    }
                    if(vaList.getObjectArg(0).getValue().equals("main_hmac")){
                        return  new StringObject(vm, "eSwKRbpB4OfG+D8ofTHooZrXr1b0B+ZDVsxHEr7K7yTWiiVX/bZl0E8D6+6645aLIAZ0+geJrjMLyyrXA99xApWvssdRm01Cg8PRVJhEvWqyHNAS73/z0OLspXVmhaSs");
                    }
                }
            }
            case "okhttp3/Interceptor$Chain->request()Lokhttp3/Request;": {
                DvmClass clazz = vm.resolveClass("okhttp3/Request");
                return clazz.newObject(request);
            }
            case "okhttp3/Request->url()Lokhttp3/HttpUrl;": {
                DvmClass clazz = vm.resolveClass("okhttp3/HttpUrl");
                Request request = (Request) dvmObject.getValue();
                return clazz.newObject(request.url());
            }
            case "okhttp3/HttpUrl->encodedPath()Ljava/lang/String;": {
                HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
                return new StringObject(vm, httpUrl.encodedPath());
            }
            case "okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;": {
                HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
                return new StringObject(vm, httpUrl.encodedQuery());
            }
            case "okhttp3/Request->body()Lokhttp3/RequestBody;": {
                Request request = (Request) dvmObject.getValue();
                return vm.resolveClass("okhttp3/RequestBody").newObject(request.body());
            }
            case "okhttp3/Request->headers()Lokhttp3/Headers;": {
                Request request = (Request) dvmObject.getValue();
                return vm.resolveClass("okhttp3/Headers").newObject(request.headers());
            }
            case "okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;": {
                System.out.println("write to my buffer:"+vaList.getObjectArg(0).getValue());
                Buffer buffer = (Buffer) dvmObject.getValue();
                buffer.writeString(vaList.getObjectArg(0).getValue().toString(), (Charset) vaList.getObjectArg(1).getValue());
                return dvmObject;
            }
            case "okhttp3/Headers->name(I)Ljava/lang/String;": {
                Headers headers = (Headers) dvmObject.getValue();
                return new StringObject(vm, headers.name(vaList.getIntArg(0)));
            }
            case "okhttp3/Headers->value(I)Ljava/lang/String;": {
                Headers headers = (Headers) dvmObject.getValue();
                return new StringObject(vm, headers.value(vaList.getIntArg(0)));
            }
            case "okio/Buffer->clone()Lokio/Buffer;": {
                Buffer buffer = (Buffer) dvmObject.getValue();
                return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
            }
            case "okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;": {
                Request request = (Request) dvmObject.getValue();
                return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
            }
            case "okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;": {
                Request.Builder builder = (Request.Builder) dvmObject.getValue();
                builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
                return dvmObject;
            }
            case "okhttp3/Request$Builder->build()Lokhttp3/Request;": {
                Request.Builder builder = (Request.Builder) dvmObject.getValue();
                return vm.resolveClass("okhttp3/Request").newObject(builder.build());
            }
            case "okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;": {
                return vm.resolveClass("okhttp3/Response").newObject(null);
            }
        }

        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "okio/Buffer-><init>()V":
                return dvmClass.newObject(new Buffer());
        }
        return super.newObjectV(vm, dvmClass, signature, vaList);
    }

    @Override
    public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "okhttp3/Headers->size()I":
                Headers headers = (Headers) dvmObject.getValue();
                return headers.size();
            case "okhttp3/Response->code()I":
                return 200;
            case "okio/Buffer->read([B)I":
                Buffer buffer = (Buffer) dvmObject.getValue();
                return buffer.read((byte[]) vaList.getObjectArg(0).getValue());
        }
        return super.callIntMethodV(vm, dvmObject, signature, vaList);
    }

}

All the contents are described in the previous article.

3, Unidbg algorithm restore

There are also many Shield algorithm restoration articles on the Internet, some of which are pseudo codes deducted from IDA F5, so we need to say it again. The algorithm uses modern cryptography algorithm (AES+HMAC MD5) to ensure strength, and classical encryption (look-up table replacement, similar to Caesar password) to ensure uniqueness. AES and HMAC MD5 have undergone magic modification, and the code itself has not been confused, which greatly reduces the difficulty, but the developers obviously have a certain understanding of cryptography, and the magic modification of the algorithm is very good. MD5 mainly modifies IV(ABCD inversion), the number of bits shifted to the left in the operation cycle, K value and the operation order. In the samples I have seen, the magic modification of MD5 is very thorough and the granularity is very fine.

The following is the Python version of the code. Those interested can be compared with normal MD5

import binascii

SV = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf,
      0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af,
      0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e,
      0x49b40821, 0xf61e2562&0xFF00FF00, 0xc040b340, 0x265e5a51, 0xe9b6c7aa& 0xFF0011FF,
      0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6,
      0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8 & 0xFF110011,
      0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
      0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
      0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, 0xd9d4d039,
      0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97,
      0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d,
      0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
      0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]

# Convert characters into corresponding binary according to ASCII encoding
def binvalue(val, bitsize):
    binval = bin(val)[2:] if isinstance(val, int) else bin(ord(val))[2:]
    if len(binval) > bitsize:
        raise ("binary value larger than the expected size")
    while len(binval) < bitsize:
        binval = "0" + binval
    return binval


def string_to_bit_array(text):
    array = list()
    for char in text:
        binval = binvalue(char, 8)
        array.extend([int(x) for x in list(binval)])
    return array


# Cycle shift left
def leftCircularShift(k, bits):
    bits = bits % 32
    k = k % (2 ** 32)
    upper = (k << bits) % (2 ** 32)
    result = upper | (k >> (32 - (bits)))
    return (result)


# Block
def blockDivide(block, chunks):
    result = []
    size = len(block) // chunks
    for i in range(0, chunks):
        result.append(int.from_bytes(block[i * size:(i + 1) * size], byteorder="little"))
    return result


# The F function acts on the "bit"
# if x then y else z
def F(X, Y, Z):
    compute = ((X & Y) | ((~X) & Z))
    return compute


# if z then x else y
def G(X, Y, Z):
    return ((X & Z) | (Y & (~Z)))


# if X = Y then Z else ~Z
def H(X, Y, Z):
    return (X ^ Y ^ Z)


def I(X, Y, Z):
    return (Y ^ (X | (~Z)))


# Four F functions
def FF(a, b, c, d, M, s, t):
    xhsTemp = leftCircularShift((a + F(b, c, d) + M + t), s)
    result = b + xhsTemp
    return (result)


def GG(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + G(b, c, d) + M + t), s)
    return (result)


def HH(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + H(b, c, d) + M + t), s)
    # print(hex(leftCircularShift((a + H(b, c, d) + M + t), s)))
    return (result)

def HH1(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + H(b, c, d) + M + t), s)
    return (result)

def II(a, b, c, d, M, s, t):
    result = b + leftCircularShift((a + I(b, c, d) + M + t), s)
    return (result)


# data conversion
def fmt8(num):
    bighex = "{0:08x}".format(num)
    binver = binascii.unhexlify(bighex)
    result = "{0:08x}".format(int.from_bytes(binver, byteorder='little'))
    return (result)


# Calculate bit length
def bitlen(bitstring):
    return len(bitstring) * 8


def md5sum(msg):
    # Calculate the bit length. If the content is too long, 64 bits cannot be put down. Take 64bit lower.
    msgLen = bitlen(msg) % (2 ** 64)
    # To fill in a 0x80 first is actually to fill in a 1 followed by the corresponding number of 0. Because the encoding of a plaintext requires at least 8 bits, 0b10000000, that is, 0x80, is filled directly
    msg = msg + b'\x80'  # 0x80 = 1000 0000
    zeroPad = (448 - (msgLen + 8) % 512) % 512
    zeroPad //= 8
    # msg = msg + b'\x00' * zeroPad + bytes.fromhex('4022000000000000')
    print(msgLen)
    msg = msg + b'\x00' * zeroPad + msgLen.to_bytes(8, byteorder='little')
    # Calculate the number of cycles, 512 for one cycle
    msgLen = bitlen(msg)
    iterations = msgLen // 512
    # initialize variable
    # The first and most obvious point of the magic change of the algorithm

    D = 0x67452301
    C = 0xefcdab89
    B = 0x98badcfe
    A = 0x10325476

    # main loop
    for i in range(0, 2):
        a = A
        b = B
        c = C
        d = D
        block = msg[i * 64:(i + 1) * 64]
        M = blockDivide(block, 16)

        # Rounds
        a = FF(a, b, c, d, M[0], 6, SV[0])
        d = FF(d, a, b, c, M[1], 13, SV[1])
        c = FF(c, d, a, b, M[2], 17, SV[2])
        b = FF(b, c, d, a, M[3], 21, SV[3])
        a = FF(a, b, c, d, M[4], 7, SV[4])
        d = FF(d, a, b, c, M[5], 12, SV[5])
        c = FF(c, d, a, b, M[6], 17, SV[6])
        b = FF(b, c, d, a, M[7], 20, SV[7])
        a = FF(a, b, c, d, M[8], 7, SV[8])
        d = FF(d, a, b, c, M[9], 12, SV[9])
        c = FF(c, d, a, b, M[10], 16, SV[10])
        b = FF(b, c, d, a, M[11], 22, SV[11])
        a = FF(a, b, c, d, M[12], 7, SV[12])
        d = FF(d, a, b, c, M[13], 13, SV[13])
        c = FF(c, d, a, b, M[14], 17, SV[14])
        b = FF(b, c, d, a, M[15], 22, SV[15])

        a = GG(a, b, c, d, M[1], 5, SV[16])
        d = GG(d, a, b, c, M[6], 9, SV[17])
        c = GG(c, d, a, b, M[11], 14, SV[18])
        b = GG(b, c, d, a, M[0], 20, SV[19])
        a = GG(a, b, c, d, M[5], 5, SV[20]) # 21 step
        d = GG(d, a, b, c, M[10], 9, SV[21]) # 22 step
        c = GG(c, d, a, b, M[15], 14, SV[22]) # 23 step
        b = GG(b, c, d, a, M[4], 20, SV[23])
        a = GG(a, b, c, d, M[9], 5, SV[24])
        d = GG(d, a, b, c, M[14], 9, SV[25])
        c = GG(c, d, a, b, M[3], 14, SV[26]) # 27 step
        b = GG(b, c, d, a, M[8], 20, SV[27])
        a = GG(a, b, c, d, M[13], 5, SV[28]) # 29 step
        d = GG(d, a, b, c, M[2], 9, SV[29]) # 30 step
        c = GG(c, d, a, b, M[7], 14, SV[30])
        b = GG(b, c, d, a, M[12], 20, SV[31])

        a = HH(a, b, c, d, M[5], 4, SV[32]) # 33 step
        d = HH(d, a, b, c, M[8], 11, SV[33])
        c = HH(c, d, a, b, M[11], 16, SV[34])

        b = HH(b, c, d, a, M[14], 23, SV[35]) # 36
        a = HH(a, b, c, d, M[1], 4, SV[36]) # 37
        d = HH(d, a, b, c, M[4], 11, SV[37]) # 38
        c = HH(c, d, a, b, M[7], 16, SV[38]) # 39

        # Normal step 40
        # b = HH(b, c, d, a, M[10], 23, SV[39])
        a = HH(a, b, c, d, M[13], 4, SV[40]) # Step 40

        b = HH(b, c, a, d, M[10], 23, SV[39]) # Step 41
        c = HH(c, d, a, b, M[3], 16, SV[42]) # Step 42
        d = HH(d, a, b, c, M[0], 11, SV[41]) # 43
        b = HH(b, c, d, a, M[6], 23, SV[43]) # 44
        a = HH(a, b, c, d, M[9], 4, SV[44]) # 45
        d = HH(d, a, b, c, M[12], 11, SV[45]) # 46
        c = HH(c, d, a, b, M[15], 16, SV[46]) # 47
        b = HH(b, c, d, a, M[2], 23, SV[47]) # 48

        a = II(a, b, c, d, M[0], 6, SV[48])
        d = II(d, a, b, c, M[7], 10, SV[49])
        c = II(c, d, a, b, M[14], 15, SV[50])
        b = II(b, c, d, a, M[5], 21, SV[51]) # 52
        a = II(a, b, c, d, M[12], 6, SV[52])
        d = II(d, a, b, c, M[3], 10, SV[53])
        c = II(c, d, a, b, M[10], 15, SV[54])
        b = II(b, c, d, a, M[1], 21, SV[55]) #56
        a = II(a, b, c, d, M[8], 6, SV[56])
        d = II(d, a, b, c, M[15], 10, SV[57])
        c = II(c, d, a, b, M[6], 15, SV[58])
        b = II(b, c, d, a, M[13], 21, SV[59]) # 60
        a = II(a, b, c, d, M[4], 6, SV[60])
        d = II(d, a, b, c, M[11], 10, SV[61])
        c = II(c, d, a, b, M[2], 15, SV[62])# 63
        b = II(b, c, d, a, M[9], 21, SV[63])
        A = (A + a) % (2 ** 32)
        B = (B + b) % (2 ** 32)
        C = (C + c) % (2 ** 32)
        D = (D + d) % (2 ** 32)
    result = fmt8(A) + fmt8(B) + fmt8(C) + fmt8(D)
    return result


if __name__ == "__main__":
    data = bytes.fromhex("d7ab505697331bfab20a1d121157e174b2a4939f375fe5a9c1aeb06f8118c65264470147c736f2a298e66cf40589c1d57852aa3469596f9e755a0752c50297038a06f86db83b6932f1d4a55bfdeb79ac")
    print("plainText: ", data)
    print("result: ", md5sum(data))

The second is the magic reform of AES. There are three main ideas to realize AES in engineering

  • Standard implementation (standard S-box and various inverse S-boxes)
  • Implementation of table merging (space for time, transforming operation into table lookup)
  • White box AES (difficult and complex)
    The three implementations are closely related. The specific operation of each round of AES has four steps: column confusion, S-box replacement, key XOR and cyclic left shift. Standard implementation is to do every step honestly. It is best to understand the code of standard AES and troubleshoot problems. Reverse is also its best analysis. The table merging method integrates the three steps of column confusion, circular left shift and S-box replacement, but the last round of AES is not complete, so the last round is similar to the standard implementation. White box AES is simply understood as four steps in one, burying the key in encryption to prevent people from getting the key directly.

From the standard implementation to table merging concurrency, the purpose is to improve the operation speed. The cost is that there are eight large merged tables as long as S-box and inverse S-box in SO. From the first two to white box AES, it is to hide the key and deal with decompilation.

Shield adopts AES implemented by table merging. If you look carefully, it feels like the modified OpenSSL code. The selection of AES magic change point is also very hidden. The Rcon required in key expansion is modified and hard coded in the sample, which is very good.

Nothing else is very interesting. The HMAC method in HMAC-MD5 is also standard.