NewStarCTF2023-Reverse-Week2-WP

AndroDbgMe

Use JEB to open (jadx opens with garbled characters), the main logic is as follows:

The application will detect whether it is a debugging environment. If it is a debugging environment, it will output a fixed value s1, s2 is fixed, and perform RC4 encryption. If it is not debugging, input will be used as the parameter encryption of RC4. After installing and using it on a mobile phone, I found that the direct input and output would be garbled. Based on the title of the question, I guessed that the test center is an Android dynamic tune. Therefore, the results can be obtained by direct tuning. For static analysis, you can also find an RC4 decryption script on the Internet.

Use JEB dynamic tuning

When you click the button on your mobile phone, there will be a flag.

AndroGenshin

Use gadx-gui to open the AndroidManifest.xml found under the resource file and find the MainActivity as follows

There are two encryption functions, which according to their names are modified RC4 and modified Base64.

RC4 is a symmetric encryption. All you need to do is use existing codes.

public class it_is_not_RC4 {
    public static String rc4(String keyStr, int[] data) {
        byte[] key = keyStr.getBytes();
        int[] s = new int[256];
        int[] k = new int[256];
        int j = 0;
        for (int i = 0; i < 256; i + + ) {
            s[i] = i;
            k[i] = key[i % key.length];
        }
        for (int i2 = 0; i2 < 256; i2 + + ) {
            j = (s[i2] + j + k[i2]) & 255;
            int temp = s[i2];
            s[i2] = s[j];
            s[j] = temp;
        }
        StringBuilder result = new StringBuilder();
        int j2 = 0;
        int i3 = 0;
        for (int i4 : data) {
            i3 = (i3 + 1) & 255;
            j2 = (s[i3] + j2) & 255;
            int temp2 = s[i3];
            s[i3] = s[j2];
            s[j2] = temp2;
            int rnd = s[(s[i3] + s[j2]) & amp; 255];
            result.append((char) (i4 ^ rnd));
        }
        return result.toString();
    }
    public static void main(String[] args) {
        String username = "genshinimpact";
        int[] base64_table = {125, 239, 101, 151, 77, 163, 163, 110, 58, 230, 186, 206, 84, 84, 189, 193, 30, 63, 104, 178, 130, 211, 164, 94, 75, 16, 32, 33, 193, 160, 120, 47, 30, 127, 157, 66, 163, 181, 177, 47, 0, 236, 106, 107, 144, 231, 250, 16, 36, 34, 91, 9, 188, 81, 5, 241, 235, 3, 54, 150, 40, 119, 202, 150};
        String ret1 =rc4(username, base64_table);
        System.out.println(ret1); // BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqtsvuxwzy1032547698/ +
    }
}

The retval obtained by RC4 encrypting username and base64table (BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqtsvuxwzy1032547698/ +) is used as the Base64 code table. Use this code table to decode the base64 of the parameters in equals.

C_C + +

exeinfo is written in C# and opened using 32/64-bit dnspy

Enter the main function

It’s just some addition and subtraction operations that you can understand if you look carefully

Just solve it in reverse and you’ll be fine.

data = [68, 75, 66, 72, 99, 19, 19, 78, 83, 74, 91,
        86, 35, 39, 77, 85, 44, 89, 47, 92, 49,
        88, 48, 91, 88, 102, 105, 51, 76, 115,
        0x84, 125, 79, 122, 0x99]
text2 = "NEWSTAR"
#text2 Converting to int is easier to calculate
data2= [78, 69, 87, 83, 84, 65, 82]
for i in range(7):
    data[i] -= (i ^ (-(data2[i] % 4)))
    data[i + 7] -= data2[i] % 5
    data[i + 14] -= (2 * i)
    data[i + 21] -= (i ^ 2)
    data[i + 28] -= (data2[i] // 5) + 10

for i in range(35):
    data[i] + = 32
    data[i] -= i
    print(chr(data[i]),end='')#flag{45dg_ng78_d8b5_1a7d_gh47_kd5b}

easy_enc

Open 64-bit IDA, create a thread and execute 4 encryption functions on the input str (if you are not sure, you can adjust the order to know the execution order of the four functions)

data part

The encryption functions are not complicated and easy to understand, but there are many multiplication and modular operations that can lead to data loss. Therefore, we cannot solve it directly in reverse, and we need to use (Z3) blasting.

from z3 import *
import numpy as np

data = [0xE8, 0x80, 0x84, 0x08, 0x18, 0x3c, 0x78, 0x68, 0x00, 0x70, 0x7C, 0x94, 0xC8, 0xE0, 0x10,
        0xEC, 0xB4, 0xAC, 0x68, 0xA8, 0xC, 0x1C, 0x90, 0xCC, 0x54, 0x3C, 0x14, 0xDC, 0x30]
data_len = len(data)

# Solve the first function z3
# Uppercase letters -52(4) & + 65(A)

# Lowercase letters -89(Y) & + 97(a)

# Number -45(-) + 48(0)


# The second function
# input[i] + = str[i%len]


# The third function
# Bitwise negation


# The fourth function
# Each bit * 0x34

prefix = "NewStarCTF"
solver = Solver()

tmp = [BitVec(f'tmp_{i}', 8) for i in range(len(data))]

#Add constraints
for i in range(data_len):
    char_code = data[i]
    t1 = (tmp[i] - 52) % 26 + 65
    t2 = t1 + ord(prefix[i])
    t3 = ~t2
    t4 = t3 * 0x34
    t4 = t4 & 0xFF
    container1 = t4 == char_code

    t1 = (tmp[i] - 89) % 26 + 97
    t2 = t1 + ord(prefix[i])
    t3 = ~t2
    t4 = t3 * 0x34
    t4 = t4 & 0xFF
    container2 = t4 == char_code

    solver.add(Or(And(tmp[i] >= ord('A'), tmp[i] <= ord('Z'),container1),
                And(tmp[i]>= ord('a'), tmp[i] <= ord('z'), container2)
                    )
                )

    # t1 = (tmp[i] - 52) % 26 + 65
    # t2 = t1 + ord(prefix[i])
    #t3 = ~t2
    #t4 = t3 * 0x34
    # t4 = t4 & 0xFF
    # container3 = t4 == char_code




if solver.check() == sat:
    # Get solution
    model = solver.model()
    # print(model)
    # # Print solution
    solution = [chr(model[tmp[i]].as_long()) if model[tmp[i]] is not None else None for i in range(len(data))]
    print("".join(solution[i] for i in range(len(data))))
else:
    print("No solution")

# BruteForceIsAGoodwaytoGetFlag

Perals

Use IDA64 to open it and find that some places are popular. Check the assembly and see the adding instructions, which causes IDA analysis to report an error. My solution is to put breakpoints in all the popular places of JUMP() and then dynamically debug it again to make IDA analysis normal.

The main function is like this

Enter the encode function

v4 The value determined by a2 (fixed value 25) can be obtained directly from the stack.

Then a1[j]=v4[a1[j]] inverse v4.index(data[i])

import hashlib

def md5_encode(data):
    md5 = hashlib.md5()
    md5.update(data.encode('utf-8'))
    return md5.hexdigest()

data = [ 0xD0, 0xD0, 0x85, 0x85, 0x80, 0x80, 0xC5, 0x8A, 0x93, 0x89,
  0x92, 0x8F, 0x87, 0x88, 0x9F, 0x8F, 0xC5, 0x84, 0xD6, 0xD1,
  0xD2, 0x82, 0xD3, 0xDE, 0x87]
table = [ 0xE6, 0xE7, 0xE4, 0xE5, 0xE2, 0xE3, 0xE0, 0xE1, 0xEE, 0xEF,
  0xEC, 0xED, 0xEA, 0xEB, 0xE8, 0xE9, 0xF6, 0xF7, 0xF4, 0xF5,
  0xF2, 0xF3, 0xF0, 0xF1, 0xFE, 0xFF, 0xFC, 0xFD, 0xFA, 0xFB,
  0xF8, 0xF9, 0xC6, 0xC7, 0xC4, 0xC5, 0xC2, 0xC3, 0xC0, 0xC1,
  0xCE, 0xCF, 0xCC, 0xCD, 0xCA, 0xCB, 0xC8, 0xC9, 0xD6, 0xD7,
  0xD4, 0xD5, 0xD2, 0xD3, 0xD0, 0xD1, 0xDE, 0xDF, 0xDC, 0xDD,
  0xDA, 0xDB, 0xD8, 0xD9, 0xA6, 0xA7, 0xA4, 0xA5, 0xA2, 0xA3,
  0xA0, 0xA1, 0xAE, 0xAF, 0xAC, 0xAD, 0xAA, 0xAB, 0xA8, 0xA9,
  0xB6, 0xB7, 0xB4, 0xB5, 0xB2, 0xB3, 0xB0, 0xB1, 0xBE, 0xBF,
  0xBC, 0xBD, 0xBA, 0xBB, 0xB8, 0xB9, 0x86, 0x87, 0x84, 0x85,
  0x82, 0x83, 0x80, 0x81, 0x8E, 0x8F, 0x8C, 0x8D, 0x8A, 0x8B,
  0x88, 0x89, 0x96, 0x97, 0x94, 0x95, 0x92, 0x93, 0x90, 0x91,
  0x9E, 0x9F, 0x9C, 0x9D, 0x9A, 0x9B, 0x98, 0x99, 0x66, 0x67,
  0x64, 0x65, 0x62, 0x63, 0x60, 0x61, 0x6E, 0x6F, 0x6C, 0x6D,
  0x6A, 0x6B, 0x68, 0x69, 0x76, 0x77, 0x74, 0x75, 0x72, 0x73,
  0x70, 0x71, 0x7E, 0x7F, 0x7C, 0x7D, 0x7A, 0x7B, 0x78, 0x79,
  0x46, 0x47, 0x44, 0x45, 0x42, 0x43, 0x40, 0x41, 0x4E, 0x4F,
  0x4C, 0x4D, 0x4A, 0x4B, 0x48, 0x49, 0x56, 0x57, 0x54, 0x55,
  0x52, 0x53, 0x50, 0x51, 0x5E, 0x5F, 0x5C, 0x5D, 0x5A, 0x5B,
  0x58, 0x59, 0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21,
  0x2E, 0x2F, 0x2C, 0x2D, 0x2A, 0x2B, 0x28, 0x29, 0x36, 0x37,
  0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3E, 0x3F, 0x3C, 0x3D,
  0x3A, 0x3B, 0x38, 0x39, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03,
  0x00, 0x01, 0x0E, 0x0F, 0x0C, 0x0D, 0x0A, 0x0B, 0x08, 0x09,
  0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, 0x1E, 0x1F,
  0x1C, 0x1D, 0x1A, 0x1B, 0x18, 0x19]

flag =''

for i in range(len(data)):
    flag + =chr(table.index(data[i]))
print(md5_encode(flag)) #d780c9b2d2aa9d40010a753bc15770de

Pzthon

Python packaged exe can see more information using exeinfo

If you want to unpack the program packaged by pyinstaller, you need the corresponding python version to unpack it.

Use 010 to open PZthon.exe and search for the keyword MEI to find out that the Python version is 3.9

I installed python3.9 on the virtual machine and used the script pyinstxtractor.py to unpack and obtain pyc (the virtual machine was reset and no files were left)

Find the PZthon.pyc file in this output file. Use the online website python decompilation – online tool (tool.lu)

Get source code

Reverse the process

enc = [
    115,
    121,
    116,
    114,
    110,
    76,
    37,
    96,
    88,
    116,
    113,
    112,
    36,
    97,
    65,
    125,
    103,
    37,
    96,
    114,
    125,
    65,
    39,
    112,
    70,
    112,
    118,
    37,
    123,
    113,
    69,
    79,
    82,
    84,
    89,
    84,
    77,
    76,
    36,
    112,
    99,
    112,
    36,
    65,
    39,
    116,
    97,
    36,
    102,
    86,
    37,
    37,
    36,
    104]

flag = ""
for i in enc:
    flag + = chr(i ^ 21)
print(flag) #flag{Y0uMade1tThr0ughT2eSec0ndPZGALAXY1eve1T2at1sC001}

R4ndom

IDA64 opens the main program with clear logic

But Rkey is generated by rand() and I was dumbfounded at first sight.

But then I learned about pseudo-random (the random number generated using a fixed seed is fixed). Find the Srand() function used to set the seed. In the b function,

There is an anti-debugging function in function a. If you want to adjust the function, you can skip it by changing the flag directly.

The srand() function generates random numbers differently in win and linux. Use this script to generate Rkey under linux.

//Run in linux system
#include<stdio.h>
#include<stdlib.h>
void main(){
unsigned int seed = 0x5377654E;
int i =0;
srand(seed);
for (i=0;i<42;i + + ){
printf("0x x ,",rand()%5);
}
}

Then there is the encryption of shift and module operations. I directly blasted it with Z3.

from z3 import *
flag =""
res = [153, 245, 13, 62, 207, 14, 130, 217, 106, 3, 18, 219, 28, 192, 83, 195, 205, 146, 20, 153,
        177, 225, 174, 255, 17, 0, 73, 226, 186, 155, 8, 118, 56, 36, 79, 139, 129, 36, 161, 187, 237, 197]

Rkey = [0x33, 0x89, 0xac, 0xd7, 0x54, 0xcc, 0x4a, 0xa5, 0x35, 0xd1, 0xdb, 0xa3, 0xe6, 0x93, 0x0f, 0x7f, 0x95, 0x4d, 0xe7, 0x65,
        0x80, 0xaf, 0x6b, 0xd2, 0xcc, 0xcd, 0x14, 0xad, 0x8d, 0x69, 0xc6, 0x40, 0xf2, 0xf2, 0x18, 0x47, 0x40, 0xe2, 0x6c, 0x75, 0xb4, 0x48]

#Create symbolic variable tmp
tmp = [BitVec(f'tmp_{i}', 8) for i in range(len(res))]

# Create Z3 solver
solver = Solver()

#Add constraints
for i in range(len(res)):
    constraint = (16 * ((tmp[i] + Rkey[i]) >> 4) + 15) & amp; (tmp[i] + Rkey[i]) == res[i]
    solver.add(constraint)

# Check if solution exists
if solver.check() == sat:
    # Get solution
    model = solver.model()
    
    # print solution
    for i in range(len(res)):
        # print(f"tmp[{i}] = {model[tmp[i]].as_long()}")
        flag + =(chr(model[tmp[i]].as_long()))
else:
    print("No solution")
print(flag) #flag{B8452786-DD8E-412C-E355-2B6F27DAB5F9}

SMC

There is an anti-debugging function that changes the flag bit to skip

Change the ZF flag to 1 when running the JZ instruction

The JZ line turns green, indicating that the conditions are met and the jump is about to take place.

Sub_401042() contains some XOR operations on memory, similar to unpacking.

Because the main encryption function (byte_403040) is still some data. Move over directly. Then F7 enters and finds that it is on the data side (explosive).

Because IDA analyzes this as a data segment during static analysis, we enter the encryption function and press P to let IDA recognize it as function F5 to take a look at the encryption logic.

Write code

data =[ 0x7C, 0x82, 0x75, 0x7B, 0x6F, 0x47, 0x61, 0x57, 0x53, 0x25,
  0x47, 0x53, 0x25, 0x84, 0x6A, 0x27, 0x68, 0x27, 0x67, 0x6A,
  0x7D, 0x84, 0x7B, 0x35, 0x35, 0x48, 0x25, 0x7B, 0x7E, 0x6A,
  0x33, 0x71]
flag =""
for i in data:
    flag + = chr((i - 0x5)^ 0x11 )
print(flag) #flag{SMC_1S_1nt3r3sting!!R1ght?}

When writing this question in WP, I found that IDA was completely messed up when I opened it the second time. It can only be used once:) I need to delete the IDA record file before using it once.