# Cyber Apocalypse CTF 2025
## Reverse
### singlestep
- Đầu tiên ta run thử chương trình và mở vào trong ida xem sao.


- Ta dễ dàng nhận thấy đây là một dạng bài self-modify code. Đầu tiên nó `pushfq` để lưu trạng thái thanh ghi lúc đó sau đó xor để decode câu lệnh và `popfq` để trả về trạng thái thanh ghi lúc đầu.
- Sau khi thực thi câu lệnh được giải mã thì nó sẽ mã hóa lại. Và cứ như đến hết chương trình
- Dựa vào `pushfq` và `popfq` ta có thể viết một đoạn code để decode và patch lại
```py=
import idaapi
import idc
with open("log.txt", "w") as f:
start = 0x0000000000001CA0
end = 0x0000000000002161
addr = set()
current_addr = start
while current_addr <= end:
idc.del_items(current_addr, 0, 1)
idc.create_byte(current_addr)
current_addr += 1
current_addr = start
while current_addr <= end:
if idc.create_insn(current_addr):
instruction_size = idc.get_item_size(current_addr)
mnemonic = idc.print_insn_mnem(current_addr).lower()
if mnemonic in ("pushfq", "popfq"):
current_addr += instruction_size
else:
current_addr += 1
else:
current_addr += 1
def patch_nop(start, size):
for i in range(size):
idc.patch_byte(start + i, 0x90)
current_addr = start
checking = False
pushfq_addr = []
popfq_addr = []
index = 0
while current_addr <= end:
if current_addr in addr:
if idc.create_insn(current_addr):
current_addr += idc.get_item_size(current_addr)
else:
current_addr += 1
continue
if not idc.create_insn(current_addr):
current_addr += 1
continue
instruction_size = idc.get_item_size(current_addr)
mnemonic = idc.print_insn_mnem(current_addr).lower()
if mnemonic == "pushfq":
pushfq_addr.append(current_addr)
checking = True
index += 1
elif mnemonic == "popfq":
popfq_addr.append(current_addr)
checking = False
if mnemonic in ("pushfq", "popfq"):
current_addr += instruction_size
continue
if index % 2 == 1 and checking and mnemonic == "xor":
try:
disasm = idaapi.generate_disasm_line(current_addr, 0).lower()
if "dword" in disasm and instruction_size == 10:
addr_patch = (idc.get_wide_dword(current_addr + 2) + current_addr + instruction_size) & 0xffff
if addr_patch not in addr:
addr.add(addr_patch)
number_xor = idc.get_wide_dword(current_addr + 6)
number_patch = idc.get_wide_dword(addr_patch)
number_patch ^= number_xor
idc.patch_dword(addr_patch, number_patch)
f.write(f"Patched DWORD at {hex(addr_patch)}: {hex(number_patch)}\n")
elif "word" in disasm and instruction_size == 9:
addr_patch = (idc.get_wide_word(current_addr + 3) + current_addr + instruction_size) & 0xffff
if addr_patch not in addr:
addr.add(addr_patch)
number_xor = idc.get_wide_word(current_addr + 7)
number_patch = idc.get_wide_word(addr_patch)
number_patch ^= number_xor
idc.patch_word(addr_patch, number_patch)
f.write(f"Patched WORD at {hex(addr_patch)}: {hex(number_patch)}\n")
elif "byte" in disasm and instruction_size == 7:
addr_patch = (idc.get_wide_dword(current_addr + 2) + current_addr + instruction_size) & 0xffff
if addr_patch not in addr:
addr.add(addr_patch)
number_xor = idc.get_wide_byte(current_addr + 6)
number_patch = idc.get_wide_byte(addr_patch)
number_patch ^= number_xor
idc.patch_byte(addr_patch, number_patch)
f.write(f"Patched BYTE at {hex(addr_patch)}: {hex(number_patch)}\n")
except Exception as e:
print(f"Error at {hex(current_addr)}: {e}")
current_addr += instruction_size
continue
current_addr += instruction_size
for i in range(len(pushfq_addr)):
patch_nop(pushfq_addr[i], popfq_addr[i] - pushfq_addr[i]+1)
print("Patching complete.")
```
- Đoạn code trên của mình vẫn bị lỗi ở chỗ này, tuy nhiên vẫn có đọc code được

- Đây là đoạn code chính:
```c=
unsigned __int64 __fastcall sub_43E0()
{
__int64 v0; // rbp
int v1; // eax
bool v2; // al
int v3; // eax
bool v5; // [rsp-29Eh] [rbp-29Eh]
bool v6; // [rsp-29Dh] [rbp-29Dh]
int v7; // [rsp-29Ch] [rbp-29Ch]
int i; // [rsp-298h] [rbp-298h]
int j; // [rsp-294h] [rbp-294h]
int k; // [rsp-290h] [rbp-290h]
int m; // [rsp-28Ch] [rbp-28Ch]
int n; // [rsp-288h] [rbp-288h]
int v13; // [rsp-284h] [rbp-284h]
__int64 v14; // [rsp-280h] [rbp-280h] BYREF
mt v15; // [rsp-278h] [rbp-278h] BYREF
mt v16; // [rsp-258h] [rbp-258h] BYREF
mt v17; // [rsp-238h] [rbp-238h] BYREF
_BYTE v18[256]; // [rsp-218h] [rbp-218h] BYREF
_BYTE v19[264]; // [rsp-118h] [rbp-118h] BYREF
unsigned __int64 v20; // [rsp-10h] [rbp-10h]
__int64 v21; // [rsp-8h] [rbp-8h]
v21 = v0;
v20 = __readfsqword(0x28u);
memset(v18, 0, sizeof(v18));
memset(v19, 0, 256);
sub_1820(&v15, 4LL, 4LL);
sub_1820(&v16, 4LL, 4LL);
sub_1820(&v17, 4LL, 4LL);
mov(&v15.rows, 0LL, 0LL, 88LL);
mov(&v15.rows, 0LL, 1uLL, -17LL);
mov(&v15.rows, 0LL, 2uLL, 19LL);
mov(&v15.rows, 0LL, 3uLL, -57LL);
mov(&v15.rows, 1uLL, 0LL, 45LL);
mov(&v15.rows, 1uLL, 1uLL, -9LL);
mov(&v15.rows, 1uLL, 2uLL, 10LL);
mov(&v15.rows, 1uLL, 3uLL, -29LL);
mov(&v15.rows, 2uLL, 0LL, -56LL);
mov(&v15.rows, 2uLL, 1uLL, 11LL);
mov(&v15.rows, 2uLL, 2uLL, -12LL);
mov(&v15.rows, 2uLL, 3uLL, 36LL);
mov(&v15.rows, 3uLL, 0LL, -40LL);
mov(&v15.rows, 3uLL, 1uLL, 8LL);
mov(&v15.rows, 3uLL, 2uLL, -9LL);
mov(&v15.rows, 3uLL, 3uLL, 26LL);
puts(a135m);
puts(&byte_1C918);
puts(off_1F010);
printf(a136mpleaseEnte);
v13 = read(0, v18, 0x100uLL);
if ( v13 >= 0 && v18[v13 - 1] == 10 )
v18[v13 - 1] = 0;
if ( sub_3690(v18) != 19 )
goto LABEL_5;
v7 = 0;
v5 = 1;
for ( i = 0; i <= 18; ++i )
{
if ( i % 5 == 4 )
{
v5 = v5 && v18[i] == 45;
}
else
{
v1 = v7++;
v19[v1] = v18[i];
v2 = (char)v18[i] > 64 && (char)v18[i] <= 90;
v5 = v5 && v2;
}
}
if ( !v5 )
{
LABEL_5:
sub_3EA0();
}
else
{
for ( j = 0; j <= 3; ++j )
{
for ( k = 0; k <= 3; ++k )
mov(&v16.rows, j, k, (char)v19[4 * j + k] - 65 - k * j);
}
v6 = 1;
sub_2630(&v15, &v16, &v17);
for ( m = 0; m <= 3; ++m )
{
for ( n = 0; n <= 3; ++n )
{
getnum(&v17, m, n, &v14);
if ( m == n )
v3 = v6 && v14 == 1;
else
v3 = v6 && v14 == 0;
v6 = v3 != 0;
}
}
if ( v6 )
sub_3FC0(v18);
else
sub_3EA0();
}
return v20 - __readfsqword(0x28u);
}
```
- Đọc thì ta có thể dễ dàng nhận thấy nó thực hiện nó thực hiện biến đổi input và chuyển nó thành một ma trận sau đó với ma trận được tạo trong đoạn code rồi kiểm tra ma trận đó có phải là ma trận đơn vị không.
- Ta có thể giải mã như sau:
```py=
import numpy as np
A = np.array([[88, -17, 19, -57],
[45, -9, 10, -29],
[-56, 11, -12, 36],
[-40, 8, -9, 26]])
if np.linalg.det(A) == 0:
print("Ma trận không khả nghịch!")
exit()
B = np.linalg.inv(A)
B = np.round(B, decimals=0).astype(int)
flag = [0]*16
for j in range(4):
for k in range(4):
flag[j*4+k] = int(B[j][k]) + 65 + k * j
for i in range(0,len(flag),4):
print(bytes(flag[i:i+4]).decode(), end='-')
# HTB{t00_mUcH_x0R!!}
# BFCF-EJJL-CKKL-BLJQ
```
- Ta run lại và có được flag: `HTB{t00_mUcH_x0R!!}`

### gateway
- Ta sẽ mở file bằng IDA


- Ta thấy mã giải khá là khó đọc. Ở đây ta chú ý vào các lệnh gần `retf` (far return). Chúng ta có thể nhận thấy rằng nó sử dụng kĩ thuật `Heaven's Gate`.
- **Heaven's Gate'** là kĩ thuật chạy một đoạn code 64-bit trong tiến trình 32-bit và ngược lại.
- Ta biết rằng trong kiến trúc x86, thanh ghi CS đóng vai trò trong việc xác định chế độ thực thi hiện tại của bộ xử lý với giá trị của thanh ghi CS:
- Trong chế độ x86 (32-bit): 0x23
- Trong chế độ x64 (64-bit): 0x33
- Dựa vào đó mã kĩ thuật này có thể thực hiện chuyển đổi giữa các chế độ
- Khi `push 0x33` rồi sử dụng `retf`, thanh ghi **CS** sẽ thay đổi làm chuyển đổi chế độ trên **x86/x64**.
- Để hiểu rõ hơn ta nói về cách hoạt động của lệnh `retf`. Trước khi thực hiện lệnh `retf`, stack sẽ có câu trúc như sau:
```
| IP (EIP/RIP) | <-- Top of Stack (ESP/RSP)
| CS (Segment) |
```
- Khi thực hiện retf, CPU pop giá trị CS từ stack, cập nhật thanh ghi CS, rồi tiếp tục thực thi tại địa chỉ mới (IP/EIP/RIP) và như vậy đã thành công chuyển đổi chế độ.
- Khi phân tích một chương trình sử dụng kĩ thuật này ta có lấy các bytecode (32 bit hoặc 64 bit) ra và ghi vào một file khác và mở bằng IDA để dễ đọc hơn hoặc có thể viết đoạn code như sau:
```py=
from capstone.x86 import *
shell = [ 0xF3, 0x0F, 0x1E, 0xFA, 0x55, 0x48, 0x89, 0xE5, 0x48, 0x89,
0x7D, 0xD8, 0x48, 0x89, 0x75, 0xD0, 0x48, 0xC7, 0x45, 0xE8,
0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0xC7, 0x45, 0xF0, 0x00, 0x00,
0x00, 0x00, 0xEB, 0x5D, 0x48, 0x8B, 0x55, 0xD8, 0x48, 0x8B,
0x45, 0xF0, 0x48, 0x01, 0xD0, 0x0F, 0xB6, 0x00, 0x0F, 0xB6,
0xC0, 0x48, 0x31, 0x45, 0xE8, 0x48, 0xC7, 0x45, 0xF8, 0x00,
0x00, 0x00, 0x00, 0xEB, 0x32, 0x48, 0x8B, 0x45, 0xE8, 0x83,
0xE0, 0x01, 0x48, 0x85, 0xC0, 0x74, 0x1D, 0x48, 0x8B, 0x45,
0xE8, 0x48, 0xD1, 0xE8, 0x48, 0x89, 0xC2, 0x48, 0xB8, 0x42,
0x0F, 0x87, 0xD7, 0x95, 0x57, 0x6C, 0xC9, 0x48, 0x31, 0xD0,
0x48, 0x89, 0x45, 0xE8, 0xEB, 0x04, 0x48, 0xD1, 0x6D, 0xE8,
0x48, 0x83, 0x45, 0xF8, 0x01, 0x48, 0x83, 0x7D, 0xF8, 0x07,
0x76, 0xC7, 0x48, 0x83, 0x45, 0xF0, 0x01, 0x48, 0x8B, 0x45,
0xF0, 0x48, 0x3B, 0x45, 0xD0, 0x72, 0x99, 0x48, 0x8B, 0x45,
0xE8, 0x48, 0xF7, 0xD0, 0x5D, 0xC3]
# shell = [ 0xF3, 0x0F, 0x1E, 0xFA, 0x55, 0x48, 0x89, 0xE5, 0x89, 0xF8,
# 0x88, 0x45, 0xEC, 0x48, 0xC7, 0x45, 0xF8, 0x00, 0x00, 0x00,
# 0x00, 0x0F, 0xB6, 0x45, 0xEC, 0x48, 0x01, 0xC0, 0x25, 0xAA,
# 0x00, 0x00, 0x00, 0x48, 0x89, 0x45, 0xF8, 0x0F, 0xB6, 0x45,
# 0xEC, 0x48, 0xD1, 0xE8, 0x83, 0xE0, 0x55, 0x48, 0x09, 0x45,
# 0xF8, 0x48, 0x8B, 0x45, 0xF8, 0x5D, 0xC3]
shell = bytes(shell)
md = Cs(CS_ARCH_X86, CS_MODE_64)
for i in md.disasm(shell, 0x1000):
print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
```
> Tham khảo:
> https://github.com/NTTITON/Malware/blob/master/Heavens%20Gate/Heavens%20Gate.c
> https://sachiel-archangel.medium.com/analysis-of-heavens-gate-part-1-62cca0ace6f0
- Trở lại với bài này thì kiểm tra độ dài input bằng 33 (cả `\n`) rồi thực hiện mã hóa sau đó kiểm tra với một mảng khác. Ta có thể giải mã như sau:
```py=
def cal(a1):
return ((((a1) >> 1) & 0x55)) | (((2 * (a1)) & 0xaa))
def shr(value, shift, bits=64):
return (value & (2**bits - 1)) >> shift
flag ='12345678901234561234567890123456'
flag = [ord(i) for i in flag]
flag = [cal(i) for i in flag]
tran_flag = flag
# print(bytes(tran_flag).decode())
index = [20, 18, 27, 8, 24, 4, 16, 5, 21, 10, 16, 28, 26, 13, 23, 27, 3, 19, 28, 31, 20, 26, 0, 31, 31, 0, 24, 2, 24, 23, 28, 21]
for i in range(32):
tran_flag[index[i]], tran_flag[i] = tran_flag[i], tran_flag[index[i]]
# print(bytes(tran_flag).decode())
import string
character = string.printable
cipher = [3056211200, 492570721, 1282371112, 1125303727, 1020619446, 510309723, 1020619446, 3228338623, 3980337024, 3136348648, 3215664806, 1020619446, 3017814043, 3136348648, 3789941608, 2957471604, 3017814043, 1020619446, 3215664806, 3898923214, 1282371112, 1125303727, 3017814043, 2635397451, 4002955503, 1020619446, 3215664806, 3215664806, 2635397451, 3136348648, 350043079, 2536674690]
enc = [0]*32
flag = [0]*32
for i in range(32):
for char in range(0xff):
x = (-1) & 0xffffffffffffffff
x ^= char
for j in range(8):
if (x & 1) == 0:
x = shr(x, 1)
else:
x = shr(x, 1)
x ^= 0xc96c5795d7870f42
x = (~x) & 0xffffffffffffffff
enc[i] = x & 0xffffffff
if enc[i] == cipher[i]:
flag[i] = char
break
# print(flag)
for i in range(31,-1,-1):
flag[index[i]], flag[i] = flag[i], flag[index[i]]
res = [0]*32
for i in range(32):
for char in range(0x20, 0x80):
x = cal(char)
if x == flag[i]:
res[i] = char
break
print(bytes(res).decode())
# HTB{r3tf@r_t0_tH3_h3@V3n5g@t3!!}
```

- Flag: `HTB{r3tf@r_t0_tH3_h3@V3n5g@t3!!}`
### heart protector
```py=
import idaapi
import ida_dbg
import time
def get_all_registers():
"""Get all register values for x64 architecture after ensuring debugger is synchronized."""
ida_dbg.refresh_debugger_memory()
# time.sleep(0.1)
register_names = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "rip"]
registers = {}
for reg in register_names:
value = idaapi.get_reg_val(reg)
registers[reg] = value if value is not None else "N/A"
return registers
class VMDebugHook(ida_dbg.DBG_Hooks):
def __init__(self, breakpoint_list):
super().__init__()
self.count = 0
self.log_file = open("log.txt", "w")
self.breakpoint_list = breakpoint_list
def dbg_process_start(self, pid, tid, ea, name, base, size):
print(f"Process started: PID={pid}, TID={tid}, EA={hex(ea)}")
self.continue_execution()
return 0
def dbg_process_exit(self, pid, tid, ea, code):
print(f"Process exited with code {code}. Unhooking...")
self.log_file.close()
self.unhook()
return 0
def dbg_run_to(self, tid, ea):
print(f"Running to {hex(ea)}")
self.continue_execution()
return 0
def dbg_bpt(self, tid, ea):
"""Handle breakpoint hits and log detailed VM operations."""
print(f"Breakpoint hit at {hex(ea)}, TID={tid}")
post_registers = get_all_registers()
registers = post_registers
breakpoint_ops = {
0: f"vm->stack[{registers['rax']}] = vm->memory[{registers['rdi']}] = {registers['rcx'] & 0xff}\n",
1: f"vm->memory[{registers['rdi']}] = {registers['rax']}\n",
2: f"++vm->memory[{registers['rdi']}]\n",
3: f"--vm->memory[{registers['rdi']}]\n",
4: f"vm->memory[{registers['rdi']}] += vm->memory[{registers['rbp']}] = {registers['rax']}\n",
5: f"vm->memory[{registers['rdi']}] += {registers['r14']}\n",
6: f"vm->memory[{registers['rdi']}] -= {registers['r14']}\n",
7: f"vm->memory[{registers['rdi']}] = __ROR4__(vm->memory[{registers['rdi']}], 32 - ({registers['rbp']} & 0x1F)); | ror = {idaapi.get_dword(registers['rbx'] + registers['rdi']*4 + 0x90)} >> {registers['rcx'] & 0xff}\n",
8: f"vm->memory[{registers['rdi']}] = __ROR4__(vm->memory[{registers['rdi']}], {registers['r12']}); | ror = {idaapi.get_dword(registers['rbx'] + registers['rdi']*4 + 0x90)} >> {registers['rcx'] & 0xff}\n",
9: f"v15 = vm->memory[{registers['rbp']}]\n",
10: f"vm->memory[{registers['rdi']}] = {registers['r12']}\n",
11: f"vm->memory[{registers['r14']}] == {registers['r12']} | {idaapi.get_dword(registers['rbx'] + registers['r14']*4 + 0x90)} == {registers['r12']}\n",
12: f"vm->memory[{registers['rbp']}] == vm->memory[{registers['r13']}] | {idaapi.get_dword(registers['rbx'] + registers['rbp']*4 + 0x90)} == {registers['rax']}\n",
13: f"vm->regs[0] = vm->memory[{registers['rdi']}] == 0\n",
14: f"mov key to stack\n",
15: f"vm->memory[{registers['rdi']}] *= {registers['rbp']} | x = {(registers['rbp'] & 0xffffffff)} * {idaapi.get_dword(registers['rbx'] + registers['rdi']*4 + 0x90)}\n",
16: f"v141 = vm->memory[{registers['rbp']}] * *((_DWORD *)&vm->size + v140) * {registers['rax']} | x = {(registers['rax'] & 0xffffffff)} * {idaapi.get_dword(registers['rbx'] + registers['rbp']*4 + 0x90)}\n",
17: f"v141 = vm->memory[{registers['rbp']}] ^ *((_DWORD *)&vm->size + v140) ^ {registers['rax']} | x = {(registers['rax'] & 0xffffffff)} ^ {idaapi.get_dword(registers['rbx'] + registers['rbp']*4 + 0x90)}\n"
}
if ea in self.breakpoint_list:
op_index = self.breakpoint_list.index(ea)
op_str = breakpoint_ops[op_index]
self.log_file.write(f"Operation: {op_str}")
if 'rdi' in op_str and registers['rdi'] != 'N/A' and registers['rbx'] != 'N/A':
mem_addr = registers['rbx'] + registers['rdi'] * 4 + 0x90
mem_val = idaapi.get_dword(mem_addr)
self.log_file.write(f"Memory at {hex(mem_addr)}: {hex(mem_val)}\n")
self.log_file.flush()
self.continue_execution()
return 0
def continue_execution(self):
"""Continue process execution."""
idaapi.continue_process()
self.count += 1
print(f"Continued execution #{self.count}")
# Setup breakpoints
offset = [55260, 56824, 55522, 55627, 55799, 56113, 56288, 56470, 56665, 56817,
56982, 57145, 57398, 57588, 59709, 59889, 60107, 60290]
base = 0x00007FF607701000
breakpoint_list = [base + i for i in offset]
for addr in breakpoint_list:
if idaapi.add_bpt(addr, 0, idaapi.BPT_SOFT):
print(f"Breakpoint set at {hex(addr)}")
else:
print(f"Failed to set breakpoint at {hex(addr)}")
try:
debug_hook = VMDebugHook(breakpoint_list)
if debug_hook.hook():
print("Debugger hook installed successfully.")
else:
print("Failed to install debugger hook.")
idaapi.continue_process()
except Exception as e:
print(f"Error during setup: {str(e)}")
```
```py=
import struct
def ror(a, r):
return 0xffffffff & ((a >> r) | (a << (32-r)))
def rol(a, r):
return ror(a, 32-r)
def fnv1(data):
val = 0x811c9dc5
for c in data:
val = ((0x1000193 * val) ^ c) & 0xffffffff
return val
def add_ror13(data):
val = 0
for i in data:
val += i
val = ror(val, 0xd)
return val
def add_mul(data):
val = 0
for i in range(len(data)):
val = val + data[i] * (211 ** i)
val -= 1869
return val ^ 9944768
key = b'a9060b622a6d95eb'
key = [i for i in key]
sum_key1 = fnv1(key)
sum_key2 = fnv1([key[5], key[6], key[7]])
sum_key3 = fnv1([key[10], key[12], key[14]])
sum_key4 = key[4] ^ 137
sum_key_5 = add_mul(key[0:4])
sum_key_6 = add_ror13([key[11], key[13], key[15]])
# print(hex(sum_key1), hex(sum_key2), hex(sum_key3), hex(sum_key4), hex(sum_key_5), hex(sum_key_6))
# print(sum_key1, sum_key2 & 0xff, sum_key3 & 0xff, sum_key4 & 0xff, sum_key_5 & 0xff, sum_key_6 & 0xff)
check_sum = [0x7586cbd1]
data = open("./code.bin","rb").read()
data = data[-20:]
for i in range(0, len(data), 4):
check_sum.append(struct.unpack("<I", data[i:i+4])[0])
assert check_sum[0] == sum_key1
assert check_sum[3] == sum_key2
assert check_sum[1] == sum_key3
assert check_sum[4] == sum_key4
assert check_sum[2] == sum_key_5
assert check_sum[5] == sum_key_6
# a9060b622a6d95eb
```
```py=
import struct
def ror(a, r):
return 0xffffffff & ((a >> r) | (a << (32-r)))
def rol(a, r):
return ror(a, 32-r)
def fnv1(data):
val = 0x811c9dc5
for c in data:
val = ((0x1000193 * val) ^ c) & 0xffffffff
return val
def add_ror13(data):
val = 0
for i in data:
val += i
val = ror(val, 0xd)
return val
def add_mul(data):
val = 0
for i in range(len(data)):
val = val + data[i] * (211 ** i)
val -= 1869
return val ^ 9944768
key = b'a9060b622a6d95eb'
key = [i for i in key]
sum_key1 = fnv1(key)
sum_key2 = fnv1([key[5], key[6], key[7]])
sum_key3 = fnv1([key[10], key[12], key[14]])
sum_key4 = key[4] ^ 137
sum_key_5 = add_mul(key[0:4])
sum_key_6 = add_ror13([key[11], key[13], key[15]])
check_sum = [0x7586cbd1]
data = open("./code.bin","rb").read()
data = data[-20:]
for i in range(0, len(data), 4):
check_sum.append(struct.unpack("<I", data[i:i+4])[0])
# assert check_sum[0] == sum_key1
# assert check_sum[3] == sum_key2
# assert check_sum[1] == sum_key3
# assert check_sum[4] == sum_key4
# assert check_sum[2] == sum_key_5
# assert check_sum[5] == sum_key_6
key_4 = 137 ^ check_sum[4]
check_fnv1 = [check_sum[3],check_sum[1]]
check_add_ror13 = check_sum[5]
check_add_mul = check_sum[2]
key_add_ror13 = ""
key_fnv1 = ['']*2
import itertools
char = '0123456789abcdef'
for k in itertools.product(char,repeat=3):
k = [ord(i) for i in k]
sum_k = fnv1(k)
add_ror13_k = add_ror13(k)
k = "".join(chr(i) for i in k)
if sum_k in check_fnv1:
index = check_fnv1.index(sum_k)
key_fnv1[index] = k
if add_ror13_k == check_add_ror13:
key_add_ror13 = k
for k in itertools.product(char,repeat=4):
k = [ord(i) for i in k]
add_mul_k = add_mul(k)
k = "".join(chr(i) for i in k)
if add_mul_k == check_add_mul:
key_add_mul = k
key = ['']*16
key[0] = key_add_mul[0]
key[1] = key_add_mul[1]
key[2] = key_add_mul[2]
key[3] = key_add_mul[3]
key[4] = chr(key_4)
key[5] = key_fnv1[0][0]
key[6] = key_fnv1[0][1]
key[7] = key_fnv1[0][2]
key[10] = key_fnv1[1][0]
key[12] = key_fnv1[1][1]
key[14] = key_fnv1[1][2]
key[11] = key_add_ror13[0]
key[13] = key_add_ror13[1]
key[15] = key_add_ror13[2]
for k in itertools.product(char,repeat=2):
key[8] = k[0]
key[9] = k[1]
k = [ord(i) for i in key]
sum_key1 = fnv1(k)
if sum_key1 == check_sum[0]:
key = "".join(chr(i) for i in k)
break
print(key)
# a9060b622a6d95eb
```
```py=
from Crypto.Cipher import AES
key = b'a9060b622a6d95eb'
cipher = open("./heart.png.malakar","rb").read()
iv = cipher[:10]
cipher = cipher[10:]
p = AES.new(key, AES.MODE_GCM, iv)
plaintext = p.decrypt(cipher)
png = b'\x89PNG' + plaintext[5:]
with open("heart.png", "wb") as f:
f.write(plaintext)
```
