solved connivance
This commit is contained in:
@@ -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>
|
||||
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
@@ -1,5 +0,0 @@
|
||||
VERSION=1
|
||||
/
|
||||
00000000:main:a4afa9f9d3f559841602854
|
||||
NEXT-ID:1
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
@@ -1,2 +0,0 @@
|
||||
IADD:00000000:/main
|
||||
IDSET:/main:a4afa9f9d3f559841602854
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
@@ -1,2 +0,0 @@
|
||||
IADD:00000000:/udf_a4afa9f9d3f559841602854
|
||||
IDSET:/udf_a4afa9f9d3f559841602854:a4afa9fa5e11707875609740
|
||||
@@ -1,4 +0,0 @@
|
||||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
@@ -1,4 +0,0 @@
|
||||
VERSION=1
|
||||
/
|
||||
NEXT-ID:0
|
||||
MD5:d41d8cd98f00b204e9800998ecf8427e
|
||||
@@ -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.
Binary file not shown.
BIN
2026/cscg/rev/connivance/decrypted_connviance.bin
Normal file
BIN
2026/cscg/rev/connivance/decrypted_connviance.bin
Normal file
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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)
|
||||
66
2026/cscg/rev/connivance/find_flag.py
Normal file
66
2026/cscg/rev/connivance/find_flag.py
Normal 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)
|
||||
BIN
2026/cscg/rev/connivance/flag_checker.bin
Normal file
BIN
2026/cscg/rev/connivance/flag_checker.bin
Normal file
Binary file not shown.
BIN
2026/cscg/rev/connivance/flag_checker_after.bin
Normal file
BIN
2026/cscg/rev/connivance/flag_checker_after.bin
Normal file
Binary file not shown.
BIN
2026/cscg/rev/connivance/flag_checker_before.bin
Normal file
BIN
2026/cscg/rev/connivance/flag_checker_before.bin
Normal file
Binary file not shown.
35
2026/cscg/rev/connivance/generate_fake_dragonfly.py
Normal file
35
2026/cscg/rev/connivance/generate_fake_dragonfly.py
Normal 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.")
|
||||
@@ -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
|
||||
BIN
2026/cscg/rev/connivance/hello_world.bin
Normal file
BIN
2026/cscg/rev/connivance/hello_world.bin
Normal file
Binary file not shown.
BIN
2026/cscg/rev/connivance/input.bin
Normal file
BIN
2026/cscg/rev/connivance/input.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
93
2026/cscg/rev/connivance/recover_flag.py
Normal file
93
2026/cscg/rev/connivance/recover_flag.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
2026/cscg/rev/connivance/solve
Executable file
BIN
2026/cscg/rev/connivance/solve
Executable file
Binary file not shown.
97
2026/cscg/rev/connivance/solve.c
Normal file
97
2026/cscg/rev/connivance/solve.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user