This page has been machine-translated from the original page.
I participated in n00bzCTF 2023 as part of 0nePadding, and we placed 79th out of 855 teams.
This time I only participated lightly, but I wanted to jot down the challenges where I learned something.
Table of Contents
MyPin(Rev)
I made a safe with a pin of only two digits.
Running the jar file provided as the challenge binary launched the following application.
The class layout was as follows.
jar -tf My-pin.jar
>
META-INF/
META-INF/MANIFEST.MF
Mypin.class
PinButton.class
ResetButton.class
Secret.classAfter checking the decompiled jar file, I found that the application does the following.
- Each time a button is pressed, the variable
cntis incremented by 1, and the buttons can be used at most 9 times. - When a button is pressed,
"0"or"1"is passed to theprocessfunction of theSecretclass, updating part of the predefinedmydataarray. - The
getDatafunction of theSecretclass has logic that generates some string based on the information in themydataarray. - There are
2**9 (=512)possible input patterns, so one of them should make the output of thegetDatafunction become the flag.
Once I had figured that out, I thought, “The rest will be easy if I rewrite the Java program in Python and brute-force it!” Unfortunately, even after trying every decompilation approach I could think of, the Python script I rewrote never behaved exactly the same as the original Java program.
Reference: GitHub - skylot/jadx: Dex to Java decompiler
In the end, it seems the proper solution was to take advantage of the fact that a jar file (or class file) can be called from your own Java program as a library.
First, extract the jar file into class files with the following command.
jar xf My-pin.jarThis gives you a file named Secret.class, so I wrote the following Java program to brute-force the flag by reusing the challenge binary’s Secret class.
public class Solve {
public static void main(String[] args) {
// 2**9 = 512
for (int i = 0; i < 512; i++) {
String pin = String.format("%9s", Integer.toBinaryString(i)).replace(' ', '0');
Secret secret = Secret.getInstance();
secret.resetInstance();
for (char c : pin.toCharArray()) {
secret.process(c);
}
String data = secret.getData();
if(data.contains("n00bz")) {
System.out.println("PIN: " + pin + ", Flag: " + data);
}
}
}
}Reference: n00bzCTF 2023 - MyPin :: Chocapikk’s blog
Place this as Solve.java in the same directory as Secret.class, then run the following command to recover the flag.
javac Solve.java ; java SolveReference: Java import giving error - Stack Overflow
EZrev(Rev)
Rev is EZ! Author: NoobHacker
Decompiling the provided class file yields the following code.
/*
* Decompiled with CFR 0.150.
*/
import java.util.Arrays;
public class EZrev {
public static void main(String[] arrstring) {
int n;
if (arrstring.length != 1) {
System.out.println("L");
return;
}
String string = arrstring[0];
if (string.length() != 31) {
System.out.println("L");
return;
}
int[] arrn = string.chars().toArray();
for (n = 0; n < arrn.length; ++n) {
arrn[n] = n % 2 == 0 ? (int)((char)(arrn[n] ^ 0x13)) : (int)((char)(arrn[n] ^ 0x37));
}
for (n = 0; n < arrn.length / 2; ++n) {
if (n % 2 == 0) {
int n2 = arrn[n] - 10;
arrn[n] = (char)(arrn[arrn.length - 1 - n] + 20);
arrn[arrn.length - 1 - n] = (char)n2;
continue;
}
arrn[n] = (char)(arrn[n] + 30);
}
int[] arrn2 = new int[]{130, 37, 70, 115, 64, 106, 143, 34, 54, 134, 96, 98, 125, 98, 138, 104, 25, 3, 66, 78, 24, 69, 91, 80, 87, 67, 95, 8, 25, 22, 115};
if (Arrays.equals(arrn, arrn2)) {
System.out.println("W");
} else {
System.out.println("L");
}
}
}After that, you can recover the flag just by writing code that works backwards.
arr2 = [130, 37, 70, 115, 64, 106, 143, 34, 54, 134, 96, 98, 125, 98, 138, 104, 25, 3, 66, 78, 24, 69, 91, 80, 87, 67, 95, 8, 25, 22, 115]
arr = arr2.copy()
for i in range(len(arr)//2):
if i % 2 == 0:
n2 = arr[i]
arr[i] = arr[len(arr)-1-i]
arr[len(arr)-1-i] = n2
print(arr)
for i in range(len(arr)//2):
if i % 2 != 0:
arr[i] = arr2[i] - 30
else:
arr[i] = arr2[len(arr)-1-i] + 10
arr[len(arr)-1-i] = arr2[i] - 20
for i in range(len(arr2)):
if i % 2 == 0:
arr[i] = arr[i] ^ 0x13
else:
arr[i] = arr[i] ^ 0x37
for a in arr:
print(chr(a), end="")zzz(Rev)
Decompiling the ELF file provided as the challenge binary with Ghidra gave the following result.
It looks like the read function is reading 0x1e bytes from fd=0, so we can tell that it receives data from standard input and passes it to the check function.
The check function decompiled as follows.
At a glance, it looked like an angr challenge, so I tried the following template.
(Since the challenge name is zzz, maybe the intended solution was to solve the constraints with z3?)
import angr
proj = angr.Project("chall", auto_load_libs=False)
init_state = proj.factory.entry_state(args = ['chall'])
simgr = proj.factory.simgr(init_state)
simgr.explore(find=(0x401654), avoid=([0x4016ca,0x4011a9]))
# 出力
simgr.found[0].posix.dumps(0)However, I could not recover the flag with the template above.
I suspect that was because I had not specified any constraints in angr.
I was not very sure how to run angr with more detailed constraints, so I decided to use Z3Py instead, which was probably closer to the intended solution.
Based on Ghidra’s decompiled output, I created a solver with the following constraints and was able to obtain the flag.
from z3 import *
flag = [BitVec(f"flag[{i}]", 8) for i in range(0x1e)]
s = Solver()
# 独自の制約を追加
s.add(flag[0] == ord("n"))
s.add(flag[1] == ord("0"))
s.add(flag[2] == ord("0"))
s.add(flag[3] == ord("b"))
s.add(flag[4] == ord("z"))
s.add(flag[5] == ord("{"))
s.add(flag[0x1e-1] == ord("}"))
for i in range(0x1e):
s.add(And(
(flag[i] >= 0x21),
(flag[i] <= 0x7e)
))
# 問題の制約
s.add(flag[0] >> 4 == 0x6)
s.add(flag[1] == flag[2])
s.add(And(
((flag[6] | flag[3]) == 0x7a),
((flag[6] & flag[3]) == 0x42)
))
s.add(flag[0x1c] == flag[4])
s.add(And(
(flag[0x1d] * flag[5] == 0x3c0f),
And(
(flag[8] + flag[6] + flag[7] == 0x12e),
(flag[7] * flag[6] - flag[8] == 0x2a8a)
)
))
s.add(And(
And(
(flag[9] - flag[8] == 5),
(flag[10] - flag[9] == 0x1b),
(flag[10] ^ flag[0xb] == 0x20)
),
And(
(flag[0xc] == flag[0xf]),
(flag[0xb] + flag[0xc] == 0xb4),
(flag[0xc] + flag[0xd] == 0xb9)
)
))
s.add(And(
(flag[0xd] + flag[0xe] - flag[0x10] == flag[0xd]),
And(
(flag[0x11] + flag[0x10] == 0xd9),
(flag[0x11] == flag[0xd])
),
And(flag[0xe] + flag[0x10] == flag[0xe] * 2)
))
s.add(And(
And(
(flag[0x12] == ord('Z')),
(flag[0x12] == flag[0x13]),
((flag[0x15] ^ flag[0x13] ^ flag[0x14]) == 0x7f)
),
((flag[0x14] ^ flag[0x15] ^ flag[0x16]) == flag[0x15]),
(flag[0x15] == ord('_')),
(flag[6] + flag[0x18] == 0xb4)
))
s.add(flag[0x18] + ~flag[0x17] == -0x21)
s.add(flag[0x19] == flag[9])
s.add(And(
(flag[0x1b] + flag[0x1a] == 0xd4),
(flag[0x1b] == flag[0x1c])
))
while s.check() == sat:
m = s.model()
for c in flag:
print(chr(m[c].as_long()),end="")
print("")
breakSummary
This time I participated only lightly.
I need to keep practicing.