solved connivance

This commit is contained in:
2026-03-24 00:35:37 +01:00
parent c6fe17930d
commit c223c4e3fa
42 changed files with 291 additions and 117 deletions

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FILE_INFO>
<BASIC_INFO>
<STATE NAME="CONTENT_TYPE" TYPE="string" VALUE="Program" />
<STATE NAME="PARENT" TYPE="string" VALUE="/" />
<STATE NAME="FILE_ID" TYPE="string" VALUE="a4afa9f9d3f559841602854" />
<STATE NAME="FILE_TYPE" TYPE="int" VALUE="0" />
<STATE NAME="READ_ONLY" TYPE="boolean" VALUE="false" />
<STATE NAME="NAME" TYPE="string" VALUE="main" />
</BASIC_INFO>
</FILE_INFO>

View File

@@ -1,4 +0,0 @@
VERSION=1
/
NEXT-ID:0
MD5:d41d8cd98f00b204e9800998ecf8427e

View File

@@ -1,5 +0,0 @@
VERSION=1
/
00000000:main:a4afa9f9d3f559841602854
NEXT-ID:1
MD5:d41d8cd98f00b204e9800998ecf8427e

View File

@@ -1,2 +0,0 @@
IADD:00000000:/main
IDSET:/main:a4afa9f9d3f559841602854

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FILE_INFO>
<BASIC_INFO>
<STATE NAME="OWNER" TYPE="string" VALUE="cato" />
</BASIC_INFO>
</FILE_INFO>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<PROJECT>
<PROJECT_DATA_XML_NAME NAME="DISPLAY_DATA">
<SAVE_STATE>
<ARRAY NAME="EXPANDED_PATHS" TYPE="string">
<A VALUE="CSCG-Connivance:" />
</ARRAY>
<STATE NAME="SHOW_TABLE" TYPE="boolean" VALUE="false" />
</SAVE_STATE>
</PROJECT_DATA_XML_NAME>
<TOOL_MANAGER ACTIVE_WORKSPACE="Workspace">
<WORKSPACE NAME="Workspace" ACTIVE="true" />
</TOOL_MANAGER>
</PROJECT>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FILE_INFO>
<BASIC_INFO>
<STATE NAME="CONTENT_TYPE" TYPE="string" VALUE="ProgramUserData" />
<STATE NAME="PARENT" TYPE="string" VALUE="/" />
<STATE NAME="FILE_ID" TYPE="string" VALUE="a4afa9fa5e11707875609740" />
<STATE NAME="FILE_TYPE" TYPE="int" VALUE="0" />
<STATE NAME="READ_ONLY" TYPE="boolean" VALUE="false" />
<STATE NAME="NAME" TYPE="string" VALUE="udf_a4afa9f9d3f559841602854" />
</BASIC_INFO>
</FILE_INFO>

View File

@@ -1,4 +0,0 @@
VERSION=1
/
NEXT-ID:0
MD5:d41d8cd98f00b204e9800998ecf8427e

View File

@@ -1,2 +0,0 @@
IADD:00000000:/udf_a4afa9f9d3f559841602854
IDSET:/udf_a4afa9f9d3f559841602854:a4afa9fa5e11707875609740

View File

@@ -1,4 +0,0 @@
VERSION=1
/
NEXT-ID:0
MD5:d41d8cd98f00b204e9800998ecf8427e

View File

@@ -1,4 +0,0 @@
VERSION=1
/
NEXT-ID:0
MD5:d41d8cd98f00b204e9800998ecf8427e

View File

@@ -1,17 +0,0 @@
import idc
import struct
addr = here() # or replace with the actual address e.g. 0x47F240
data = bytes([idc.get_wide_byte(addr + i) for i in range(39)])
flag = idc.get_wide_byte(addr + 39)
key = idc.get_qword(addr + 40)
print(f"Data: {data.hex()}")
print(f"Decrypted: {flag}")
print(f"Key: {key:#018x}")
# Decrypt it immediately
decrypted = bytes(~(b ^ ((key >> (8 * (i % 8))) & 0xFF)) & 0xFF for i, b in enumerate(data))
print(f"Plaintext: {decrypted}")
print(f"As string: {decrypted.decode('utf-8', errors='replace')}")

Binary file not shown.

View File

@@ -1,12 +0,0 @@
break *0x4035da
run flag_checker.tfl AAAA
printf "sub_410450 result: %d\n", $r15b
set $r15b = 0
break *0x40365e
continue
printf "At main VM call\n"
set $wrapper = *(long long*)$rsi
set $bc_base = *(long long*)($wrapper+0x10)
set $bc_size = *(long long*)($wrapper+0x18)
printf "bc_base=%p size=%d\n", $bc_base, $bc_size
dump binary memory /tmp/bc_main.bin $bc_base ($bc_base+$bc_size)

View File

@@ -1,11 +0,0 @@
break *0x40356f
run flag_checker.tfl AAAA
set $eax = 0
break *0x40365e
continue
printf "At main sub_405120 call\n"
set $wrapper = *(long long*)$rsi
set $bc_base = *(long long*)($wrapper+0x10)
set $bc_size = *(long long*)($wrapper+0x18)
printf "bc_base=%p size=%d\n", $bc_base, $bc_size
dump binary memory /tmp/bc_main.bin $bc_base ($bc_base+$bc_size)

View File

@@ -0,0 +1,66 @@
import hashlib
def bitwise_not(data: bytes) -> bytes:
"""Inverts all bits in the byte array."""
return bytes(~b & 0xFF for b in data)
def shift_right_1(data: bytes) -> bytes:
"""
Shifts the entire byte array to the right by 1 bit.
Note: This implementation assumes a forward-flowing carry (byte 0 to byte N).
If the VM's BigInt implementation is little-endian, you might need to reverse
the carry logic so it flows from index i+1 to i.
"""
result = bytearray(len(data))
carry = 0
for i in range(len(data)):
# Shift current byte right by 1, and OR it with the carry from the previous byte
result[i] = (data[i] >> 1) | carry
# The lowest bit of the current byte becomes the MSB carry for the next byte
carry = (data[i] & 1) << 7
return bytes(result)
def check_flag(test_flag: bytes, target_hash_blob: bytes) -> bool:
"""Simulates the VM's flag verification logic."""
# 1. Length Check
if len(test_flag) != 29:
print(f"[-] Incorrect length: {len(test_flag)}. Must be 29 bytes.")
return False
# 2. Initial Mutation (BITWISE_NOT)
current_state = bitwise_not(test_flag)
# 3. The Shift-and-Hash Loop
generated_blob = bytearray()
for iteration in range(8):
# Compute SHA256 of the current state
current_hash = hashlib.sha256(current_state).digest()
generated_blob.extend(current_hash)
# Shift the state right by 1 bit for the next iteration
current_state = shift_right_1(current_state)
# 4. Final Verification
if generated_blob == target_hash_blob:
print("[+] Correct! Flag matches.")
return True
else:
print("[-] Incorrect! Hash mismatch.")
return False
# ==========================================
# Example Usage
# ==========================================
if __name__ == "__main__":
# TODO: Load the actual 256-byte target blob from 'romfs:/dragonfly.bin'
# Here is a dummy 256-byte array for testing the script structure
with open("./romfs/damocles.bin", "rb") as flag_hash:
dragonfly_bin_data = flag_hash.read()
# The flag you want to test
my_test_flag = b"dach2026{dummy_test_flag_pay}" # Exactly 29 bytes
check_flag(my_test_flag, dragonfly_bin_data)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,35 @@
import hashlib
def buggy_shift_right_1(data: bytes) -> bytes:
"""Replicates the VM's flawed, per-byte shift that ignores carries."""
return bytes(b >> 1 for b in data)
def bitwise_not(data: bytes) -> bytes:
"""Inverts all bits in the byte array."""
return bytes(~b & 0xFF for b in data)
def generate_dragonfly(flag: bytes) -> bytes:
assert len(flag) == 29, "Flag must be exactly 29 bytes!"
current_state = bitwise_not(flag)
out_blob = bytearray()
# 8 iterations: hash, then shift
for _ in range(8):
out_blob.extend(hashlib.sha256(current_state).digest())
current_state = buggy_shift_right_1(current_state)
return bytes(out_blob)
if __name__ == "__main__":
# A test flag that matches your 29-byte requirement and format
# 9 byte prefix + 19 byte payload + 1 byte suffix = 29 bytes
test_flag = b"dach2026{0123456789abcdefgh!}"
blob = generate_dragonfly(test_flag)
with open("dragonfly.bin", "wb") as f:
f.write(blob)
print(f"[+] Wrote {len(blob)} bytes to dragonfly.bin")
print("[!] Swap this file into the VM environment and test it.")

View File

@@ -1,9 +0,0 @@
break *0x40356f
run flag_checker.tfl AAAA
set $eax = 0
break *0x404010
commands 2
printf "HASH pc=%lld\n", *(long long*)$rdx
continue
end
continue

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
import hashlib
import time
def solve_flag(target_blob: bytes):
"""
Reverses the 8-stage hashing loop by walking backwards from the 7th shift
down to the 0th shift, recovering 1 bit of the flag per iteration.
"""
# 1. Setup Known Constraints
known_prefix = b"dach2026{"
known_suffix = b"}"
total_len = 29
# Calculate indices for the 19 unknown characters
unknown_indices = list(range(len(known_prefix), total_len - len(known_suffix)))
# Pre-calculate the bitwise NOT of the known parts
full_known_state = bytearray(total_len)
for i, b in enumerate(known_prefix):
full_known_state[i] = ~b & 0xFF
full_known_state[total_len - 1] = ~known_suffix[0] & 0xFF
# Split the target blob into the 8 independent target hashes
target_hashes = set(target_blob[i*32 : (i+1)*32] for i in range(8))
# This array will accumulate our recovered bits for the 19 unknown bytes
# By the end, it will contain the fully unshifted, inverted bytes.
recovered_unknowns = [0] * len(unknown_indices)
print("[*] Starting Bit-by-Bit Backtracking Attack...")
start_time = time.time()
# 2. Walk backwards from Shift 7 down to Shift 0
for shift_amount in range(7, -1, -1):
print(f"\n[*] Cracking Shift Layer {shift_amount} (Guessing bit {shift_amount} of payload)...")
# Pre-build the known parts of the state for this specific shift layer
base_state = bytearray(total_len)
for i in range(total_len):
if i < len(known_prefix) or i == total_len - 1:
base_state[i] = full_known_state[i] >> shift_amount
match_found = False
# Brute-force the next bit for all 19 unknown bytes (2^19 combinations = 524,288)
# This takes a few seconds per layer in pure Python.
for guess in range(1 << len(unknown_indices)):
test_state = bytearray(base_state)
# Inject our guessed bits into the test state
for i, idx in enumerate(unknown_indices):
guess_bit = (guess >> i) & 1
test_state[idx] = (recovered_unknowns[i] << 1) | guess_bit
# Hash and check against the pool of valid target hashes
if hashlib.sha256(test_state).digest() in target_hashes:
print(f" [+] Match found! Extracted bits: {bin(guess)[2:].zfill(19)}")
# Commit the guessed bits to our recovered array
for i in range(len(unknown_indices)):
recovered_unknowns[i] = (recovered_unknowns[i] << 1) | ((guess >> i) & 1)
match_found = True
break
if not match_found:
print(f" [-] CRITICAL FAILURE: Could not find a valid bit permutation at layer {shift_amount}.")
print(" Double check the dragonfly.bin data and prefix.")
return None
# 3. Final Assembly
# We now have the complete, unshifted inverted state.
final_inverted_state = bytearray(full_known_state)
for i, idx in enumerate(unknown_indices):
final_inverted_state[idx] = recovered_unknowns[i]
# Un-invert it to reveal the plaintext flag
final_flag = bytes(~b & 0xFF for b in final_inverted_state)
elapsed = time.time() - start_time
print(f"\n[+] Exploit completed in {elapsed:.2f} seconds.")
print(f"[!] RECOVERED FLAG: {final_flag.decode('ascii', errors='ignore')}")
return final_flag
# ==========================================
# Execution / Test Mock
# ==========================================
if __name__ == "__main__":
with open("./romfs/dragonfly.bin", "rb") as f:
target_blob = f.read()
solve_flag(target_blob)

BIN
2026/cscg/rev/connivance/solve Executable file

Binary file not shown.

View File

@@ -0,0 +1,97 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <openssl/sha.h>
int main() {
FILE *f = fopen("romfs/dragonfly.bin", "r");
if (!f) {
perror("Failed to open file");
return 1;
}
// Get file size
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *dragonfly = malloc(file_size);
if (!dragonfly) {
perror("malloc failed");
fclose(f);
return 1;
}
fread(dragonfly, 1, file_size, f);
fclose(f);
uint8_t flag[0x1d] = {0}; // 29 bytes
for (int i = 7; i >= 0; i--) {
uint8_t *target_hash = dragonfly + SHA256_DIGEST_LENGTH * (7 - i);
int found = 0;
uint64_t max_n = 1ULL << sizeof(flag);
for (uint64_t n = 0; n < max_n; n++) {
uint64_t tmp = n;
for (int j = 0; j < sizeof(flag); j++) {
uint8_t bit = tmp & 1;
tmp >>= 1;
flag[j] = (flag[j] & 0xFE) | bit;
}
// Compute SHA-256
uint8_t hash[SHA256_DIGEST_LENGTH];
SHA256(flag, sizeof(flag), hash);
if (memcmp(hash, target_hash, SHA256_DIGEST_LENGTH) == 0) {
found = 1;
break;
}
if (n % 1000000 == 0) {
printf("\r%09lu", n);
fflush(stdout);
}
}
printf("\n");
if (!found) {
printf("Could not find it on level: %d\n", i);
break;
}
// Print flag in hex
for (int j = 0; j < sizeof(flag); j++) {
printf("%02x", flag[j]);
}
printf("\n");
// Shift left by 1
for (int j = 0; j < sizeof(flag); j++) {
flag[j] = (flag[j] << 1) & 0xFF;
}
}
// Print final flag
for (int j = 0; j < sizeof(flag); j++) {
printf("%02x", flag[j]);
}
printf("\n");
// Bitwise NOT
for (int j = 0; j < sizeof(flag); j++) {
flag[j] = (~flag[j]) & 0xFF;
}
for (int j = 0; j < sizeof(flag); j++) {
printf("%02x", flag[j]);
}
printf("\n");
printf("%s\n", flag);
free(dragonfly);
return 0;
}