This page has been machine-translated from the original page.
I will work through the Rev challenges from the SECCON qualifiers one by one.
I plan to add the other challenges later.
For the challenges and write-ups, refer to the following official repository.
Reference: GitHub - SECCON/SECCON2022onlineCTF
Table of Contents
baby_cmp(Rev)
baby_mode = 1 👶
When I decompiled the challenge binary in Ghidra, it turned out to be a simple password-checking program.
I was able to recover the flag with a template angr script.
import angr
import claripy
proj = angr.Project("chall.baby", auto_load_libs=False)
obj = proj.loader.main_object
print("Entry", hex(obj.entry))
find = 0x4012c9
avoids = 0x40127a
flag = claripy.BVS("flag", 0x23 * 8)
state = proj.factory.entry_state(args=["./chall.baby", flag])
for i in range(0x23):
state.solver.add(flag.get_byte(i) >= 0x21)
state.solver.add(flag.get_byte(i) <= 0x7f)
simgr = proj.factory.simgr(state)
simgr.explore(find=find, avoid=avoids)
# Output
simgr.found[0].solver.eval(flag, cast_to=bytes)
# SECCON{y0u_f0und_7h3_baby_flag_YaY}eguite(Rev)
Crack me.
Running the binary shows that it is a GUI program that performs license verification, as shown below.
I started by decompiling it with Ghidra.
Because it is a GUI program, locating the actual verification logic takes a bit of work, but searching for the string Invalid license let me identify the relevant code.
The onClick function below appears to be the one called when the Check button is pressed.
For reference, here is Ghidra’s decompiled output.
You can see that it performs fairly complex validation on the input.
/* eguite::Crackme::onclick */
bool eguite::Crackme::onclick(long param_1)
{
uint *puVar1;
byte bVar2;
void *pvVar3;
uint *puVar4;
long lVar5;
uint uVar6;
undefined **ppuVar7;
uint *puVar8;
uint uVar9;
uint *puVar10;
uint *puVar11;
uint *local_68;
uint *local_60;
undefined4 local_58;
undefined4 uStack_54;
undefined4 uStack_50;
undefined4 uStack_4c;
void *local_48;
long local_40;
undefined8 local_38;
if (*(long *)(param_1 + 0x90) != 0x2b) {
return false;
}
puVar11 = *(uint **)(param_1 + 0x80);
if ((*(uint *)((long)puVar11 + 3) ^ 0x7b4e4f43 | *puVar11 ^ 0x43434553) != 0) {
return false;
}
if (*(byte *)((long)puVar11 + 0x2a) != 0x7d) {
return false;
}
puVar1 = (uint *)((long)puVar11 + 0x2b);
lVar5 = 0x13;
puVar4 = puVar11;
do {
if (puVar4 == puVar1) goto LAB_0016027f;
bVar2 = *(byte *)puVar4;
if ((char)bVar2 < '\0') {
if (bVar2 < 0xe0) {
puVar4 = (uint *)((long)puVar4 + 2);
}
else if (bVar2 < 0xf0) {
puVar4 = (uint *)((long)puVar4 + 3);
}
else {
if ((*(byte *)((long)puVar4 + 3) & 0x3f |
(*(byte *)((long)puVar4 + 2) & 0x3f) << 6 | (*(byte *)((long)puVar4 + 1) & 0x3f) << 0xc
| (bVar2 & 7) << 0x12) == 0x110000) goto LAB_0016027f;
puVar4 = puVar4 + 1;
}
}
else {
puVar4 = (uint *)((long)puVar4 + 1);
}
lVar5 = lVar5 + -1;
} while (lVar5 != 0);
if (puVar4 == puVar1) {
LAB_0016027f:
ppuVar7 = &PTR_s_src/main.rsgetrandom_getrandom(_00725118;
goto LAB_0016046d;
}
bVar2 = *(byte *)puVar4;
uVar9 = (uint)bVar2;
if ((char)bVar2 < '\0') {
uVar6 = bVar2 & 0x1f;
uVar9 = *(byte *)((long)puVar4 + 1) & 0x3f;
if (bVar2 < 0xe0) {
uVar9 = uVar6 << 6 | uVar9;
}
else {
uVar9 = *(byte *)((long)puVar4 + 2) & 0x3f | uVar9 << 6;
if (bVar2 < 0xf0) {
uVar9 = uVar9 | uVar6 << 0xc;
}
else {
uVar9 = *(byte *)((long)puVar4 + 3) & 0x3f | uVar9 << 6 | (bVar2 & 7) << 0x12;
if (uVar9 == 0x110000) goto LAB_0016027f;
}
}
}
if (uVar9 != 0x2d) {
return false;
}
lVar5 = 0x1a;
puVar4 = puVar11;
do {
if (puVar4 == puVar1) goto LAB_0016036f;
bVar2 = *(byte *)puVar4;
if ((char)bVar2 < '\0') {
if (bVar2 < 0xe0) {
puVar4 = (uint *)((long)puVar4 + 2);
}
else if (bVar2 < 0xf0) {
puVar4 = (uint *)((long)puVar4 + 3);
}
else {
if ((*(byte *)((long)puVar4 + 3) & 0x3f |
(*(byte *)((long)puVar4 + 2) & 0x3f) << 6 | (*(byte *)((long)puVar4 + 1) & 0x3f) << 0xc
| (bVar2 & 7) << 0x12) == 0x110000) goto LAB_0016036f;
puVar4 = puVar4 + 1;
}
}
else {
puVar4 = (uint *)((long)puVar4 + 1);
}
lVar5 = lVar5 + -1;
} while (lVar5 != 0);
if (puVar4 == puVar1) {
LAB_0016036f:
ppuVar7 = &PTR_s_src/main.rsgetrandom_getrandom(_00725130;
}
else {
bVar2 = *(byte *)puVar4;
uVar9 = (uint)bVar2;
if ((char)bVar2 < '\0') {
uVar6 = bVar2 & 0x1f;
uVar9 = *(byte *)((long)puVar4 + 1) & 0x3f;
if (bVar2 < 0xe0) {
uVar9 = uVar6 << 6 | uVar9;
}
else {
uVar9 = *(byte *)((long)puVar4 + 2) & 0x3f | uVar9 << 6;
if (bVar2 < 0xf0) {
uVar9 = uVar9 | uVar6 << 0xc;
}
else {
uVar9 = *(byte *)((long)puVar4 + 3) & 0x3f | uVar9 << 6 | (bVar2 & 7) << 0x12;
if (uVar9 == 0x110000) goto LAB_0016036f;
}
}
}
if (uVar9 != 0x2d) {
return false;
}
lVar5 = 0x21;
puVar4 = puVar11;
do {
if (puVar4 == puVar1) goto LAB_0016045f;
bVar2 = *(byte *)puVar4;
if ((char)bVar2 < '\0') {
if (bVar2 < 0xe0) {
puVar4 = (uint *)((long)puVar4 + 2);
}
else if (bVar2 < 0xf0) {
puVar4 = (uint *)((long)puVar4 + 3);
}
else {
if ((*(byte *)((long)puVar4 + 3) & 0x3f |
(*(byte *)((long)puVar4 + 2) & 0x3f) << 6 |
(*(byte *)((long)puVar4 + 1) & 0x3f) << 0xc | (bVar2 & 7) << 0x12) == 0x110000)
goto LAB_0016045f;
puVar4 = puVar4 + 1;
}
}
else {
puVar4 = (uint *)((long)puVar4 + 1);
}
lVar5 = lVar5 + -1;
} while (lVar5 != 0);
if (puVar4 != puVar1) {
bVar2 = *(byte *)puVar4;
uVar9 = (uint)bVar2;
if (-1 < (char)bVar2) {
LAB_0016048a:
if (uVar9 != 0x2d) {
return false;
}
local_58 = 7;
uStack_54 = 0;
uStack_50 = 0xc;
uStack_4c = 0;
local_68 = puVar11;
local_60 = puVar1;
<>::from_iter(&local_48,&local_68);
pvVar3 = local_48;
core::num::<impl_u64>::from_str_radix(&local_68,local_48,local_38,0x10);
puVar4 = local_60;
if ((char)local_68 != '\0') {
puVar4 = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(pvVar3);
}
local_58 = 0x14;
uStack_54 = 0;
uStack_50 = 6;
uStack_4c = 0;
local_68 = puVar11;
local_60 = puVar1;
<>::from_iter(&local_48,&local_68);
pvVar3 = local_48;
core::num::<impl_u64>::from_str_radix(&local_68,local_48,local_38,0x10);
puVar10 = local_60;
if ((char)local_68 != '\0') {
puVar10 = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(pvVar3);
}
local_58 = 0x1b;
uStack_54 = 0;
uStack_50 = 6;
uStack_4c = 0;
local_68 = puVar11;
local_60 = puVar1;
<>::from_iter(&local_48,&local_68);
pvVar3 = local_48;
core::num::<impl_u64>::from_str_radix(&local_68,local_48,local_38,0x10);
puVar8 = local_60;
if ((char)local_68 != '\0') {
puVar8 = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(pvVar3);
}
local_58 = 0x22;
uStack_54 = 0;
uStack_50 = 8;
uStack_4c = 0;
local_68 = puVar11;
local_60 = puVar1;
<>::from_iter(&local_48,&local_68);
core::num::<impl_u64>::from_str_radix(&local_68,local_48,local_38,0x10);
puVar11 = local_60;
if ((char)local_68 != '\0') {
puVar11 = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(local_48);
}
if ((byte *)((long)puVar4 + (long)puVar10) != (byte *)0x8b228bf35f6a) {
return false;
}
if ((byte *)((long)puVar8 + (long)puVar10) != (byte *)0xe78241) {
return false;
}
if ((byte *)((long)puVar11 + (long)puVar8) == (byte *)0xfa4c1a9f) {
if ((byte *)((long)puVar4 + (long)puVar11) == (byte *)0x8b238557f7c8) {
return ((ulong)puVar8 ^ (ulong)puVar10 ^ (ulong)puVar11) == 0xf9686f4d;
}
return false;
}
return false;
}
uVar6 = bVar2 & 0x1f;
uVar9 = *(byte *)((long)puVar4 + 1) & 0x3f;
if (bVar2 < 0xe0) {
uVar9 = uVar6 << 6 | uVar9;
goto LAB_0016048a;
}
uVar9 = *(byte *)((long)puVar4 + 2) & 0x3f | uVar9 << 6;
if (bVar2 < 0xf0) {
uVar9 = uVar9 | uVar6 << 0xc;
goto LAB_0016048a;
}
uVar9 = *(byte *)((long)puVar4 + 3) & 0x3f | uVar9 << 6 | (bVar2 & 7) << 0x12;
if (uVar9 != 0x110000) goto LAB_0016048a;
}
LAB_0016045f:
ppuVar7 = &PTR_s_src/main.rsgetrandom_getrandom(_00725148;
}
LAB_0016046d:
core::panicking::panic(&DAT_0048e6b0,0x2b,ppuVar7);
do {
invalidInstructionException();
} while( true );
}For this kind of pattern, I think you could recover the flag by describing the constraints in Z3Py, but I could not quite write an appropriate script.
In cases like this, it is better to patiently read through the logic.
First, in the following snippet, I investigated with GDB and found that rdi + 0x90 stores the length of the input string.
In other words, the flag string length must be 0x2b.
if (*(long *)(rdi + 0x90) != 0x2b) {
return false;
}Next, consider the following snippet.
rdi + 0x80 stored the input string.
input = *(uint **)(rdi + 0x80);
if ((*(uint *)((long)input + 3) ^ 0x7b4e4f43 | *input ^ 0x43434553) != 0) {
return false;
}
if (*(byte *)((long)input + 0x2a) != 0x7d) {
return false;
}Here, it appears to compare the beginning and end of the input string against hard-coded values.
As shown below, these values correspond to the flag prefix, so this section is checking the flag format.
Next, this part is a little long, but let us go through it.
The region after input + 0x2b should not contain any meaningful values yet, so I renamed it to buf.
Inside the do-while loop, it performs various operations while decrementing i, which starts at 0x13.
However, if you look closely, every byte of the input string is always greater than 0, so the code inside if ((char)c < 0x0) never runs. The loop simply increments input_ptr 0x13 times and then ends.
buf = (uint *)((long)input + 0x2b);
i = 0x13;
input_ptr = input;
do {
if (input_ptr == buf) goto FAIL;
c = *(byte *)input_ptr;
if ((char)c < 0x0) {
if (c < 0xe0) {
input_ptr = (uint *)((long)input_ptr + 2);
}
else if (c < 0xf0) {
input_ptr = (uint *)((long)input_ptr + 3);
}
else {
if ((*(byte *)((long)input_ptr + 3) & 0x3f |
(*(byte *)((long)input_ptr + 2) & 0x3f) << 6 |
(*(byte *)((long)input_ptr + 1) & 0x3f) << 0xc | (c & 7) << 0x12) == 0x110000)
goto FAIL;
input_ptr = input_ptr + 1;
}
}
else {
input_ptr = (uint *)((long)input_ptr + 1);
}
i = i + -1;
} while (i != 0);Let us continue following the logic.
In the next lines, it extracts the character from input_ptr after advancing by 0x13, stores it in i_x13, and compares it. Since this value also never becomes less than 0, in practice it is only checking whether i_x13 equals 0x2d (-).
c = *(byte *)input_ptr;
i_x13 = (uint)c;
if ((char)c < '\0') {
uVar2 = c & 0x1f;
uVar5 = *(byte *)((long)input_ptr + 1) & 0x3f;
if (c < 0xe0) {
uVar5 = uVar2 << 6 | uVar5;
}
else {
uVar5 = *(byte *)((long)input_ptr + 2) & 0x3f | uVar5 << 6;
if (c < 0xf0) {
uVar5 = uVar5 | uVar2 << 0xc;
}
else {
uVar5 = *(byte *)((long)input_ptr + 3) & 0x3f | uVar5 << 6 | (c & 7) << 0x12;
if (uVar5 == 0x110000) goto FAIL;
}
}
}
if (i_x13 != 0x2d) {
return false;
}Let us continue with the following logic.
Here, it resets input_ptr back to the beginning of the input string.
Although various operations are defined inside the do-while loop, the input characters still never become less than 0 here, so it effectively just advances the index to 0x1a.
i = 0x1a;
input_ptr = input;
do {
if (input_ptr == buf) goto LAB_5555555b436f;
c = *(byte *)input_ptr;
if ((char)c < '\0') {
if (c < 0xe0) {
input_ptr = (uint *)((long)input_ptr + 2);
}
else if (c < 0xf0) {
input_ptr = (uint *)((long)input_ptr + 3);
}
else {
if ((*(byte *)((long)input_ptr + 3) & 0x3f |
(*(byte *)((long)input_ptr + 2) & 0x3f) << 6 |
(*(byte *)((long)input_ptr + 1) & 0x3f) << 0xc | (c & 7) << 0x12) == 0x110000)
goto LAB_5555555b436f;
input_ptr = input_ptr + 1;
}
}
else {
input_ptr = (uint *)((long)input_ptr + 1);
}
i = i + -1;
} while (i != 0);The pattern should be clear by now.
Including the logic so far, if we remove the lines that can be ignored, the code looks like this.
buf = (uint *)((long)input + 0x2b);
i = 0x13;
input_ptr = input;
c = *(byte *)input_ptr;
if (c != 0x2d) {
return false;
}
i = 0x1a;
input_ptr = input;
c = *(byte *)input_ptr;
if (c != 0x2d) {
return false;
}
i = 0x21;
input_ptr = input;
c = *(byte *)input_ptr;
if (c != 0x2d) {
return false;
}In other words, these 176 lines of code are simply checking the flag prefix and whether the characters at specific positions are -.
Now that we have read this far, let us look at the second half of the code.
A = 7;
B = 0;
C = 0xc;
D = 0;
input_ptr2 = input;
buf2 = buf;
<>::from_iter(&input_arr,&input_ptr2);
input_arr_ptr = input_arr;
core::num::<impl_u64>::from_str_radix(&input_ptr2,input_arr,local_38,0x10);
input_ptr = buf2;
if ((char)input_ptr2 != '\0') {
input_ptr = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(input_arr_ptr);
}Ghidra’s decompilation did not seem to recover the arguments correctly, but it is calling the from_iter function.
After checking the actual behavior in GDB, it turned out to create a collection by extracting a 12-character substring starting at index 7.
In other words, it extracts the substring between SECCON{ and the first -.
It then uses from_str_radix to convert that string to a number.
Reference: from_iter - Rust
Reference: Rust: Convert a base-n string to a number
Reference: u64 - Rust
At that point, I expected the later patterns to be the same.
When I actually lined up the decompiled output below, it became clear that the program extracts each hyphen-separated segment of a flag in the format SECCON{ABCDEFGHIJKL-NOPQRS-UVWXYZ-bcdefghi} and converts each segment to a number.
A = 7;
B = 0;
C = 0xc;
D = 0;
input_ptr2 = input;
buf2 = buf;
<>::from_iter(&input_arr,&input_ptr2);
input_arr_ptr = input_arr;
core::num::<impl_u64>::from_str_radix(&input_ptr2,input_arr,local_38,0x10);
input_ptr = buf2;
if ((char)input_ptr2 != '\0') {
input_ptr = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(input_arr_ptr);
}
A = 0x14;
B = 0;
C = 6;
D = 0;
input_ptr2 = input;
buf2 = buf;
<>::from_iter(&input_arr,&input_ptr2);
input_arr_ptr = input_arr;
core::num::<impl_u64>::from_str_radix(&input_ptr2,input_arr,local_38,0x10);
puVar5 = buf2;
if ((char)input_ptr2 != '\0') {
puVar5 = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(input_arr_ptr);
}
A = 0x1b;
B = 0;
C = 6;
D = 0;
input_ptr2 = input;
buf2 = buf;
<>::from_iter(&input_arr,&input_ptr2);
input_arr_ptr = input_arr;
core::num::<impl_u64>::from_str_radix(&input_ptr2,input_arr,local_38,0x10);
puVar3 = buf2;
if ((char)input_ptr2 != '\0') {
puVar3 = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(input_arr_ptr);
}
A = 0x22;
B = 0;
C = 8;
D = 0;
input_ptr2 = input;
buf2 = buf;
<>::from_iter(&input_arr,&input_ptr2);
core::num::<impl_u64>::from_str_radix(&input_ptr2,input_arr,local_38,0x10);
input = buf2;
if ((char)input_ptr2 != '\0') {
input = (uint *)0x0;
}
if (local_40 != 0) {
__rust_dealloc(input_arr);
}In other words, despite how complicated this code looks, almost all of the actual flag validation comes down to the following part.
if ((byte *)((long)input_ptr + (long)puVar5) != (byte *)0x8b228bf35f6a) {
return false;
}
if ((byte *)((long)puVar3 + (long)puVar5) != (byte *)0xe78241) {
return false;
}
if ((byte *)((long)input + (long)puVar3) == (byte *)0xfa4c1a9f) {
if ((byte *)((long)input_ptr + (long)input) == (byte *)0x8b238557f7c8) {
return ((ulong)puVar3 ^ (ulong)puVar5 ^ (ulong)input) == 0xf9686f4d;
}
return false;
}However, because I had renamed variables rather roughly in Ghidra, it became hard to tell what this code was doing.
The result decompiled in IDA looked like this.
if ( v21 + v24 == 0x8B228BF35F6ALL
&& v24 + v26 == 15172161
&& v28 + v26 == 4199291551LL
&& v28 + v21 == 0x8B238557F7C8LL )
{
return (v28 ^ v24 ^ v26) == 4184371021LL;
}Using these as constraints, I created the following solver.
from z3 import *
s = Solver()
i1 = BitVec('i1', 64)
i2 = BitVec('i2', 64)
i3 = BitVec('i3', 64)
i4 = BitVec('i4', 64)
s.add(i1 + i2 == 0x8b228bf35f6a)
s.add(i2 + i3 == 0xe78241)
s.add(i4 + i3 == 0xfa4c1a9f)
s.add(i4 + i1 == 0x8B238557F7C8)
s.add(i4 ^ i2 ^ i3 == 0xf9686f4d)
if s.check() == sat:
model = s.model()
parts = [f"{model[key].as_long():x}"for key in [i1, i2, i3, i4]]
flag = "SECCON{" + "-".join(parts) + "}"
print(flag)
else:
print("unsat")Running the script above yields the correct flag and also passes the program’s verification.
Devil Hunter(Rev)
Clam Devil; Asari no Akuma
The challenge provides the following shell script and CBC file.
#!/bin/sh
if [ -z "$1" ]
then
echo "[+] ${0} <flag.txt>"
exit 1
else
clamscan --bytecode-unsigned=yes --quiet -dflag.cbc "$1"
if [ $? -eq 1 ]
then
echo "Correct!"
else
echo "Wrong..."
fi
fiClamBCafhaio`lfcf|aa```c``a```|ah`cnbac`cecnb`c``beaacp`clamcoincidencejb:4096
Seccon.Reversing.{FLAG};Engine:56-255,Target:0;0;0:534543434f4e7b
Teddaaahdabahdacahdadahdaeahdafahdagahebdeebaddbdbahebndebceaacb`bbadb`baacb`bb`bb`bdaib`bdbfaah
Eaeacabbae|aebgefafdf``adbbe|aecgefefkf``aebae|amcgefdgfgifbgegcgnfafmfef``
G`ad`@`bdeBceBefBcfBcfBofBnfBnbBbeBefBfgBefBbgBcgBifBnfBgfBnbBfdBldBadBgd@`bad@Aa`bad@Aa`
A`b`bLabaa`b`b`Faeac
Baa``b`abTaa`aaab
Bb`baaabbaeAc`BeadTbaab
BTcab`b@dE
A`aaLbhfb`dab`dab`daahabndabad`bndabad`b`b`aa`b`d`b`d`b`d`b`b`bad`bad`b`b`aa`b`d`b`b`aa`ah`aa`aa`b`b`aa`b`d`b`d`b`d`b`b`bad`bad`b`b`b`b`b`d`b`d`b`b`b`b`bad`b`b`bad`b`d`aa`b`b`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`d`b`d`aa`Fbcgah
Bbadaedbbodad@dbadagdbbodaf@db`bahabbadAgd@db`d`bb@habTbaab
Baaaiiab`dbbaBdbhb`d`bbbbaabTaaaiabac
Bb`dajbbabajb`dakh`ajB`bhb`dalj`akB`bhb`bamn`albadandbbodad@dbadaocbbadanamb`bb`aabbabaoAadaabaanab`bb`aAadb`dbbaa`ajAahb`d`bb@h`Taabaaagaa
Bb`bbcaabbabacAadaabdakab`bbca@dahbeabbacbeaaabfaeaahbeaBmgaaabgak`bdabfab`d`bb@h`Taabgaadag
Bb`bbhaabbabacAadaabiakab`bbha@db`d`bb@haab`d`bb@h`Taabiaagae
Bb`dbjabbaabjab`dbkah`bjaB`bhb`dblaj`bkaB`bhb`bbman`blabadbnadbbodad@dbadboacbbadbnabmab`bb`bgbboab`bbab`baacb`bb`dbbbh`bjaBnahb`dbcbj`bbbB`bhb`bbdbn`bcbb`bbebc`Add@dbadbfbcbbadagbebb`bbgbc`Addbdbbadbhbcbbadbfbbgbb`b`fbbabbhbb`dbiba`bjaAdhaabjbiab`dbibBdbhb`d`bbbibaaTaabjbaeaf
Bb`bbkbgbagaablbeab`bbkbHbj`hnicgdb`bbmbc`Add@dbadbnbcbbadagbmbb`bbobc`AddAadbadb`ccbbadbnbbobb`bbacgbb`caabbceab`bbacHcj`hnjjcdaabcck`blbbbcb`bbdcc`Add@dbadbeccbbadagbdcb`bbfcc`AddAbdbadbgccbbadbecbfcb`bbhcgbbgcaabiceab`bbhcHoigndjkcdaabjck`bccbicb`bbkcc`Add@dbadblccbbadagbkcb`bbmcc`AddAcdbadbnccbbadblcbmcb`bbocgbbncaab`deab`bbocHcoaljkhgdaabadk`bjcb`db`bbbdc`Add@dbadbcdcbbadagbbdb`bbddc`AddAddbadbedcbbadbcdbddb`bbfdgbbedaabgdeab`bbfdHcoalionedaabhdk`badbgdb`bbidc`Add@dbadbjdcbbadagbidb`bbkdc`AddAedbadbldcbbadbjdbkdb`bbmdgbbldaabndeab`bbmdHoilnikkcdaabodk`bhdbndb`bb`ec`Add@dbadbaecbbadagb`eb`bbbec`AddAfdbadbcecbbadbaebbeb`bbdegbbceaabeeeab`bbdeHdochfheedaabfek`bodbeeb`bbgec`Add@dbadbhecbbadagbgeb`bbiec`AddAgdbadbjecbbadbhebieb`bbkegbbjeaableeab`bbkeHdiemjoeedaabmek`bfebleb`bbnec`Add@dbadboecbbadagbneb`bb`fc`AddAhdbadbafcbbadboeb`fb`bbbfgbbafaabcfeab`bbbfHoimmoklfdaabdfk`bmebcfb`dbefo`bdfb`d`bbbef`Tbaag
Bb`dbffbb`bffaabgfn`bffTcaaabgfE
Aab`bLbaab`b`b`dab`dab`d`b`d`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`aa`b`d`b`d`Fbfaac
Bb`d`bb@habb`d`bbG`lckjljhaaTbaaa
Bb`dacbbaaacb`dadbbabadb`baen`acb`bafn`adb`bagh`afAcdb`bahi``agb`baik`ahBoodb`bajm`aiaeb`bakh`ajAhdb`bali`aeBhadb`baml`akalb`bana`afAadaaaoeab`banAddb`db`ao`anb`dbaao`amb`d`bbb`aabb`d`bbbaaaaTaaaoabaa
BTcab`bamE
Snfofdg`bcgof`befafcgig`bjc`ej`The CBC file appears to be a compiled ClamAV custom detection signature.
Reference: Bytecode Signatures - ClamAV Documentation
This suggests that the flag should be a file that satisfies the detection condition when running clamscan --bytecode-unsigned=yes --quiet -dflag.cbc flag.txt.
For CBC files, it seems that the clambc command can use the --printsrc and --printbcir options to obtain the original source or the disassembled intermediate representation.
In this challenge, the --printsrc option did not work properly, so I used --printbcir to obtain the disassembly.
clambc --printbcir flag.cbc > decomp.txtI did not fully understand how to read it at first, but it seems that each function is assigned a Function id and the output lists opcodes together with operands.
########################################################################
####################### Function id 0 ################################
########################################################################
found a total of 4 globals
GID ID VALUE
------------------------------------------------------------------------
0 [ 0]: i0 unknown
1 [ 1]: [22 x i8] unknown
2 [ 2]: i8* unknown
3 [ 3]: i8* unknown
------------------------------------------------------------------------
found 2 values with 0 arguments and 2 locals
VID ID VALUE
------------------------------------------------------------------------
0 [ 0]: i1
1 [ 1]: i32
------------------------------------------------------------------------
found a total of 2 constants
CID ID VALUE
------------------------------------------------------------------------
0 [ 2]: 21(0x15)
1 [ 3]: 0(0x0)
------------------------------------------------------------------------
found a total of 4 total values
------------------------------------------------------------------------
FUNCTION ID: F.0 -> NUMINSTS 5
BB IDX OPCODE [ID /IID/MOD] INST
------------------------------------------------------------------------
0 0 OP_BC_CALL_DIRECT [32 /160/ 0] 0 = call F.1 ()
0 1 OP_BC_BRANCH [17 / 85/ 0] br 0 ? bb.1 : bb.2
1 2 OP_BC_CALL_API [33 /168/ 3] 1 = setvirusname[4] (p.-2147483645, 2)
1 3 OP_BC_JMP [18 / 90/ 0] jmp bb.2
2 4 OP_BC_RET [19 / 98/ 3] ret 3
------------------------------------------------------------------------
########################################################################This is more of an educated guess, but it seems to first call call F.1 () and then branch with br 0 ? bb.1 : bb.2 depending on the result.
bb probably corresponds to the number in the leftmost column. If execution jumps to bb.1, it reaches 1 = setvirusname[4] (p.-2147483645, 2), which seems to register the file’s detection information.
So it looks like we want the return value of F.1 to be 0.
Next, let us look at function F.1.
It is fairly long—about 100 lines—so I thought I might be able to brute-force my way through it, but I could not really understand how to read it.
------------------------------------------------------------------------
FUNCTION ID: F.1 -> NUMINSTS 115
BB IDX OPCODE [ID /IID/MOD] INST
------------------------------------------------------------------------
0 0 OP_BC_GEPZ [36 /184/ 4] 5 = gepz p.4 + (104)
0 1 OP_BC_GEPZ [36 /184/ 4] 7 = gepz p.6 + (105)
0 2 OP_BC_CALL_API [33 /168/ 3] 8 = seek[3] (106, 107)
0 3 OP_BC_COPY [34 /174/ 4] cp 108 -> 2
0 4 OP_BC_JMP [18 / 90/ 0] jmp bb.2
1 5 OP_BC_ICMP_ULT [25 /129/ 4] 9 = (18 < 109)
1 6 OP_BC_COPY [34 /174/ 4] cp 18 -> 2
1 7 OP_BC_BRANCH [17 / 85/ 0] br 9 ? bb.2 : bb.3
2 8 OP_BC_COPY [34 /174/ 4] cp 2 -> 10
2 9 OP_BC_SHL [8 / 44/ 4] 11 = 10 << 110
2 10 OP_BC_ASHR [10 / 54/ 4] 12 = 11 >> 111
2 11 OP_BC_TRUNC [14 / 73/ 3] 13 = 12 trunc ffffffffffffffff
2 12 OP_BC_GEPZ [36 /184/ 4] 14 = gepz p.4 + (112)
2 13 OP_BC_GEP1 [35 /179/ 4] 15 = gep1 p.14 + (13 * 65)
2 14 OP_BC_CALL_API [33 /168/ 3] 16 = read[1] (p.15, 113)
2 15 OP_BC_ICMP_SLT [30 /153/ 3] 17 = (16 < 114)
2 16 OP_BC_ADD [1 / 9/ 0] 18 = 10 + 115
2 17 OP_BC_COPY [34 /174/ 4] cp 116 -> 0
2 18 OP_BC_BRANCH [17 / 85/ 0] br 17 ? bb.7 : bb.1
3 19 OP_BC_CALL_API [33 /168/ 3] 19 = read[1] (p.3, 117)
3 20 OP_BC_ICMP_SGT [27 /138/ 3] 20 = (19 > 118)
3 21 OP_BC_COPY [34 /171/ 1] cp 3 -> 21
3 22 OP_BC_ICMP_EQ [21 /106/ 1] 22 = (21 == 119)
3 23 OP_BC_AND [11 / 55/ 0] 23 = 20 & 22
3 24 OP_BC_COPY [34 /174/ 4] cp 120 -> 0
3 25 OP_BC_BRANCH [17 / 85/ 0] br 23 ? bb.4 : bb.7
4 26 OP_BC_CALL_API [33 /168/ 3] 24 = read[1] (p.3, 121)
4 27 OP_BC_ICMP_SGT [27 /138/ 3] 25 = (24 > 122)
4 28 OP_BC_COPY [34 /174/ 4] cp 123 -> 1
4 29 OP_BC_COPY [34 /174/ 4] cp 124 -> 0
4 30 OP_BC_BRANCH [17 / 85/ 0] br 25 ? bb.7 : bb.5
5 31 OP_BC_COPY [34 /174/ 4] cp 1 -> 26
5 32 OP_BC_SHL [8 / 44/ 4] 27 = 26 << 125
5 33 OP_BC_ASHR [10 / 54/ 4] 28 = 27 >> 126
5 34 OP_BC_TRUNC [14 / 73/ 3] 29 = 28 trunc ffffffffffffffff
5 35 OP_BC_GEPZ [36 /184/ 4] 30 = gepz p.4 + (127)
5 36 OP_BC_GEP1 [35 /179/ 4] 31 = gep1 p.30 + (29 * 65)
5 37 OP_BC_LOAD [39 /198/ 3] load 32 <- p.31
5 38 OP_BC_CALL_DIRECT [32 /163/ 3] 33 = call F.2 (32)
5 39 OP_BC_SHL [8 / 44/ 4] 34 = 26 << 128
5 40 OP_BC_ASHR [10 / 54/ 4] 35 = 34 >> 129
5 41 OP_BC_TRUNC [14 / 73/ 3] 36 = 35 trunc ffffffffffffffff
5 42 OP_BC_MUL [3 / 18/ 0] 37 = 130 * 131
5 43 OP_BC_GEP1 [35 /179/ 4] 38 = gep1 p.7 + (37 * 65)
5 44 OP_BC_MUL [3 / 18/ 0] 39 = 132 * 36
5 45 OP_BC_GEP1 [35 /179/ 4] 40 = gep1 p.38 + (39 * 65)
5 46 OP_BC_STORE [38 /193/ 3] store 33 -> p.40
5 47 OP_BC_ADD [1 / 9/ 0] 41 = 26 + 133
5 48 OP_BC_ICMP_ULT [25 /129/ 4] 42 = (41 < 134)
5 49 OP_BC_COPY [34 /174/ 4] cp 41 -> 1
5 50 OP_BC_BRANCH [17 / 85/ 0] br 42 ? bb.5 : bb.6
6 51 OP_BC_LOAD [39 /198/ 3] load 43 <- p.7
6 52 OP_BC_ICMP_EQ [21 /108/ 3] 44 = (43 == 135)
6 53 OP_BC_MUL [3 / 18/ 0] 45 = 136 * 137
6 54 OP_BC_GEP1 [35 /179/ 4] 46 = gep1 p.7 + (45 * 65)
6 55 OP_BC_MUL [3 / 18/ 0] 47 = 138 * 139
6 56 OP_BC_GEP1 [35 /179/ 4] 48 = gep1 p.46 + (47 * 65)
6 57 OP_BC_LOAD [39 /198/ 3] load 49 <- p.48
6 58 OP_BC_ICMP_EQ [21 /108/ 3] 50 = (49 == 140)
6 59 OP_BC_AND [11 / 55/ 0] 51 = 44 & 50
6 60 OP_BC_MUL [3 / 18/ 0] 52 = 141 * 142
6 61 OP_BC_GEP1 [35 /179/ 4] 53 = gep1 p.7 + (52 * 65)
6 62 OP_BC_MUL [3 / 18/ 0] 54 = 143 * 144
6 63 OP_BC_GEP1 [35 /179/ 4] 55 = gep1 p.53 + (54 * 65)
6 64 OP_BC_LOAD [39 /198/ 3] load 56 <- p.55
6 65 OP_BC_ICMP_EQ [21 /108/ 3] 57 = (56 == 145)
6 66 OP_BC_AND [11 / 55/ 0] 58 = 51 & 57
6 67 OP_BC_MUL [3 / 18/ 0] 59 = 146 * 147
6 68 OP_BC_GEP1 [35 /179/ 4] 60 = gep1 p.7 + (59 * 65)
6 69 OP_BC_MUL [3 / 18/ 0] 61 = 148 * 149
6 70 OP_BC_GEP1 [35 /179/ 4] 62 = gep1 p.60 + (61 * 65)
6 71 OP_BC_LOAD [39 /198/ 3] load 63 <- p.62
6 72 OP_BC_ICMP_EQ [21 /108/ 3] 64 = (63 == 150)
6 73 OP_BC_AND [11 / 55/ 0] 65 = 58 & 64
6 74 OP_BC_MUL [3 / 18/ 0] 66 = 151 * 152
6 75 OP_BC_GEP1 [35 /179/ 4] 67 = gep1 p.7 + (66 * 65)
6 76 OP_BC_MUL [3 / 18/ 0] 68 = 153 * 154
6 77 OP_BC_GEP1 [35 /179/ 4] 69 = gep1 p.67 + (68 * 65)
6 78 OP_BC_LOAD [39 /198/ 3] load 70 <- p.69
6 79 OP_BC_ICMP_EQ [21 /108/ 3] 71 = (70 == 155)
6 80 OP_BC_AND [11 / 55/ 0] 72 = 65 & 71
6 81 OP_BC_MUL [3 / 18/ 0] 73 = 156 * 157
6 82 OP_BC_GEP1 [35 /179/ 4] 74 = gep1 p.7 + (73 * 65)
6 83 OP_BC_MUL [3 / 18/ 0] 75 = 158 * 159
6 84 OP_BC_GEP1 [35 /179/ 4] 76 = gep1 p.74 + (75 * 65)
6 85 OP_BC_LOAD [39 /198/ 3] load 77 <- p.76
6 86 OP_BC_ICMP_EQ [21 /108/ 3] 78 = (77 == 160)
6 87 OP_BC_AND [11 / 55/ 0] 79 = 72 & 78
6 88 OP_BC_MUL [3 / 18/ 0] 80 = 161 * 162
6 89 OP_BC_GEP1 [35 /179/ 4] 81 = gep1 p.7 + (80 * 65)
6 90 OP_BC_MUL [3 / 18/ 0] 82 = 163 * 164
6 91 OP_BC_GEP1 [35 /179/ 4] 83 = gep1 p.81 + (82 * 65)
6 92 OP_BC_LOAD [39 /198/ 3] load 84 <- p.83
6 93 OP_BC_ICMP_EQ [21 /108/ 3] 85 = (84 == 165)
6 94 OP_BC_AND [11 / 55/ 0] 86 = 79 & 85
6 95 OP_BC_MUL [3 / 18/ 0] 87 = 166 * 167
6 96 OP_BC_GEP1 [35 /179/ 4] 88 = gep1 p.7 + (87 * 65)
6 97 OP_BC_MUL [3 / 18/ 0] 89 = 168 * 169
6 98 OP_BC_GEP1 [35 /179/ 4] 90 = gep1 p.88 + (89 * 65)
6 99 OP_BC_LOAD [39 /198/ 3] load 91 <- p.90
6 100 OP_BC_ICMP_EQ [21 /108/ 3] 92 = (91 == 170)
6 101 OP_BC_AND [11 / 55/ 0] 93 = 86 & 92
6 102 OP_BC_MUL [3 / 18/ 0] 94 = 171 * 172
6 103 OP_BC_GEP1 [35 /179/ 4] 95 = gep1 p.7 + (94 * 65)
6 104 OP_BC_MUL [3 / 18/ 0] 96 = 173 * 174
6 105 OP_BC_GEP1 [35 /179/ 4] 97 = gep1 p.95 + (96 * 65)
6 106 OP_BC_LOAD [39 /198/ 3] load 98 <- p.97
6 107 OP_BC_ICMP_EQ [21 /108/ 3] 99 = (98 == 175)
6 108 OP_BC_AND [11 / 55/ 0] 100 = 93 & 99
6 109 OP_BC_SEXT [15 / 79/ 4] 101 = 100 sext 1
6 110 OP_BC_COPY [34 /174/ 4] cp 101 -> 0
6 111 OP_BC_JMP [18 / 90/ 0] jmp bb.7
7 112 OP_BC_COPY [34 /174/ 4] cp 0 -> 102
7 113 OP_BC_TRUNC [14 / 70/ 0] 103 = 102 trunc ffffffffffffffff
7 114 OP_BC_RET [19 / 95/ 0] ret 103
------------------------------------------------------------------------Referring to the official write-up, it seems that the following solver can recover the flag, so I used it as a clue while trying to understand the logic.
from z3 import *
hashlist = [
0x739e80a2, 0x3aae80a3, 0x3ba4e79f, 0x78bac1f3,
0x5ef9c1f3, 0x3bb9ec9f, 0x558683f4, 0x55fad594,
0x6cbfdd9f
]
flag = [BitVec(f'flag_{i}', 32) for i in range(len(hashlist))]
s = Solver()
for hashvalue, block in zip(hashlist, flag):
h = 0xacab3c0
for i in range(4):
h = RotateLeft(h ^ ((block >> (i*8)) & 0xff), 8)
s.add(h == hashvalue)
r = s.check()
if r != sat:
print(r)
exit(1)
m = s.model()
s_flag = b""
for block in flag:
s_flag += int.to_bytes(m[block].as_long(), 4, 'little')
print("SECCON{" + s_flag.decode() + "}")DoroboH(Rev)
I found a suspicious process named “araiguma.exe” running on my computer. Before removing it, I captured my network and dumped the process memory. Could you investigate what the malware is doing?
doroboh
The program is a malware. Do not run it unless you understand its behavior.
TODO
eldercmp(Rev)
baby_mode = 0 👴
chall.elder
- The program is tested on Ubuntu 20.04 and 22.04 LTS (host machine). It might not run correctly on the other platforms such as WSL.
TODO