WIP lakectf

This commit is contained in:
2025-11-28 23:48:04 +01:00
parent 9144e50e85
commit 40216d6dde
62 changed files with 13330 additions and 0 deletions

1
2025/lake/rev/android/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
src_files

View File

@@ -0,0 +1 @@
3.12

View 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.

Binary file not shown.

View File

@@ -0,0 +1,6 @@
def main():
print("Hello from android!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
[project]
name = "android"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"z3-solver>=4.15.4.0",
]

View File

@@ -0,0 +1,164 @@
import os
import re
from z3 import *
# ================= CONFIGURATION =================
# Path to the folder containing the decompiled .java files
SOURCE_DIR = "./src_files/sources/com/lake/ctf"
# Initial Entry Point (From the Native Init function)
START_CLASS_HASH = "86e50149658661312a9e0b35558d84f6c6d3da797f552a9657fe0558ca40cdef"
START_METHOD_HASH = "031b4af5197ec30a926f48cf40e11a7dbc470048a21e4003b7a3c07c5dab1baa"
# ================= Z3 SETUP =================
# UPDATED: Flag length is explicitly 55 based on MainActivity
FLAG_LEN = 55
# UPDATED: The app checks exactly 80 links in the chain
MAX_STEPS = 80
solver = Solver()
flag_vars = [BitVec(f"flag_{i}", 8) for i in range(FLAG_LEN)]
# Add printable ASCII constraints
for i in range(FLAG_LEN):
solver.add(flag_vars[i] >= 32)
solver.add(flag_vars[i] <= 126)
# ================= HELPER CLASSES =================
class MockString:
"""
A helper class to allow us to 'eval' the Java equation directly.
"""
def charAt(self, index):
if index >= FLAG_LEN:
# This is now a critical error since we know the exact length
print(f"[!] Critical: Equation references index {index} which is >= 55.")
return 0
# We must zero-extend to 32 bits because Java intermediate math is int (32-bit)
# but our chars are 8-bit.
return ZeroExt(24, flag_vars[index])
# ================= MAIN LOGIC =================
def load_files():
"""Reads all Java files into a dictionary keyed by Class Hash."""
files_map = {}
if not os.path.exists(SOURCE_DIR):
print(f"Error: Directory {SOURCE_DIR} not found.")
return {}
print(f"[*] Loading files from {SOURCE_DIR}...")
for f in os.listdir(SOURCE_DIR):
if f.endswith(".java") and f.startswith("Check"):
class_hash = f.replace("Check", "").replace(".java", "")
with open(os.path.join(SOURCE_DIR, f), "r") as file_obj:
files_map[class_hash] = file_obj.read()
print(f"[*] Loaded {len(files_map)} files.")
return files_map
def extract_logic(class_hash, method_hash, files_map):
content = files_map.get(class_hash)
if not content:
print(f"[!] Critical: Class hash {class_hash} not found on disk.")
return None, None, None
# Pattern: static boolean Check[METHOD_HASH](String str) { BODY }
method_pattern = re.compile(rf"static boolean Check{method_hash}\(String (\w+)\)\s*\{{(.*?)\}}", re.DOTALL)
match = method_pattern.search(content)
if not match:
print(f"[!] Critical: Method {method_hash} not found in class {class_hash}.")
return None, None, None
param_name = match.group(1)
body = match.group(2)
# Extract the 'if' condition
if_match = re.search(r"if\s*\((.*)\)\s*\{", body)
if not if_match:
return None, None, None
equation_str = if_match.group(1)
# Extract the 'Success' Nop call
if_block_start = body.find("{")
if_block_end = body.find("}", if_block_start)
if_block_content = body[if_block_start:if_block_end]
nop_match = re.search(r'nop\("([a-f0-9]+)",\s*"([a-f0-9]+)"\)', if_block_content)
if not nop_match:
return equation_str, None, None
return equation_str, nop_match.group(1), nop_match.group(2)
def solve():
files_map = load_files()
if not files_map: return
curr_class = START_CLASS_HASH
curr_method = START_METHOD_HASH
step_count = 0
visited = set()
print(f"[*] Starting traversal for exactly {MAX_STEPS} steps...")
# UPDATED: Loop exactly MAX_STEPS times
for i in range(MAX_STEPS):
if not curr_class:
print("[!] Chain ended prematurely!")
break
# Loop detection (just in case, though duplicates are mathematically fine)
if (curr_class, curr_method) in visited:
print(f"[i] Loop detected at step {i+1}. This is fine, just adding redundant constraints.")
visited.add((curr_class, curr_method))
step_count += 1
eq_str, next_class, next_method = extract_logic(curr_class, curr_method, files_map)
if not eq_str:
break
try:
if "==" in eq_str:
lhs, rhs = eq_str.split("==")
ctx = {'str': MockString()}
lhs_expr = eval(lhs.strip(), {}, ctx)
rhs_val = int(eval(rhs.strip(), {}, {}))
solver.add(lhs_expr == rhs_val)
else:
print(f"[!] Warning: Equation format unknown: {eq_str}")
except Exception as e:
print(f"[!] Error parsing equation: {eq_str}")
break
curr_class = next_class
curr_method = next_method
print(f"[*] Extraction complete. {step_count} constraints added.")
print("[*] Solving with Z3...")
if solver.check() == sat:
model = solver.model()
result = []
for i in range(FLAG_LEN):
val = model[flag_vars[i]]
if val is not None:
result.append(chr(val.as_long()))
else:
result.append("?")
final_flag = "".join(result)
print("\n" + "="*50)
print(f"FLAG: {final_flag}")
print("="*50 + "\n")
else:
print("[!] Unsatisfiable! Constraints might be contradictory.")
if __name__ == "__main__":
solve()

28
2025/lake/rev/android/uv.lock generated Normal file
View File

@@ -0,0 +1,28 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "android"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "z3-solver" },
]
[package.metadata]
requires-dist = [{ name = "z3-solver", specifier = ">=4.15.4.0" }]
[[package]]
name = "z3-solver"
version = "4.15.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/8e/0c8f17309549d2e5cde9a3ccefa6365437f1e7bafe71878eaf9478e47b18/z3_solver-4.15.4.0.tar.gz", hash = "sha256:928c29b58c4eb62106da51c1914f6a4a55d0441f8f48a81b9da07950434a8946", size = 5018600, upload-time = "2025-10-29T18:12:03.062Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/63/33/a3d5d2eaeb0f7b3174d57d405437eabb2075d4d50bd9ea0957696c435c7b/z3_solver-4.15.4.0-py3-none-macosx_13_0_arm64.whl", hash = "sha256:407e825cc9211f95ef46bdc8d151bf630e7ab2d62a21d24cd74c09cc5b73f3aa", size = 37052538, upload-time = "2025-10-29T18:11:46.233Z" },
{ url = "https://files.pythonhosted.org/packages/47/84/fd7ffac1551cd9f8d44fe41358f738be670fc4c24dfd514fab503f2cf3e7/z3_solver-4.15.4.0-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:00bd10c5a6a5f6112d3a9a810d0799227e52f76caa860dafa5e00966bb47eb13", size = 39807925, upload-time = "2025-10-29T18:11:49.81Z" },
{ url = "https://files.pythonhosted.org/packages/21/c9/bb51a96af0091324c81b803f16c49f719f9f6ea0b0bb52200f5c97ec4892/z3_solver-4.15.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e103a6f203f505b8b8b8e5c931cc407c95b61556512d4921c1ddc0b3f41b08e", size = 29268352, upload-time = "2025-10-29T18:11:53.032Z" },
{ url = "https://files.pythonhosted.org/packages/bf/2e/0b49f7e4e53817cfb09a0f6585012b782dfe0b666e8abefcb4fac0570606/z3_solver-4.15.4.0-py3-none-manylinux_2_34_aarch64.whl", hash = "sha256:62c7e9cbdd711932301f29919ad9158de9b2f58b4d281dd259bbcd0a2f408ba1", size = 27226534, upload-time = "2025-10-29T18:11:55.59Z" },
{ url = "https://files.pythonhosted.org/packages/26/91/33de49538444d4aafbe47415c450c2f9abab1733e1226f276b496672f46c/z3_solver-4.15.4.0-py3-none-win32.whl", hash = "sha256:be3bc916545c96ffbf89e00d07104ff14f78336e55db069177a1bfbcc01b269d", size = 13191672, upload-time = "2025-10-29T18:11:58.424Z" },
{ url = "https://files.pythonhosted.org/packages/03/d6/a0b135e4419df475177ae78fc93c422430b0fd8875649486f9a5989772e6/z3_solver-4.15.4.0-py3-none-win_amd64.whl", hash = "sha256:00e35b02632ed085ea8199fb230f6015e6fc40554a6680c097bd5f060e827431", size = 16259597, upload-time = "2025-10-29T18:12:01.14Z" },
]