UIUCTF 2025 - Damaged SoC
Due to previous X ray damage, a part of SoC bFlash memory is corrupted. The board is unable to pass self-verification and boot. :/
Looks like our SoC was hit by a cosmic ray that flipped a few bits, let’s try to make sense of it and reverse the damage in order to get our board back up and running.
Quick rundown
We’re given a SystemVerilog-simulated IC that is (fortunately) already precompiled for us with Verilator; I won’t go in depth into what SystemVerilog is and how it works in this writeup, but feel free to dive deeper on your own, the gist of it is — it’s a hardware description and verification language that uses the synthesizable subset to describe CPUs, memories, MMIO blocks and other components to model hardware, which can then be simulated by open or closed-source simulators like Verilator, Modelsim, etc.
The challenge hands us a metric ton of files:
├── infra
│ └── src
│ ├── modules
│ │ ├── adder.sv
│ │ ├── barrel_rotator32.sv
│ │ ├── barrel_shifter32.sv
│ │ ├── configurations.sv
│ │ ├── core_branch.sv
│ │ ├── core_forward.sv
│ │ ├── core_hazard.sv
│ │ ├── data_mem.sv
│ │ ├── mips_decoder.sv
│ │ ├── mips_define.sv
│ │ ├── mux.sv
│ │ ├── register.sv
│ │ └── structures.sv
│ ├── SOC.sv
│ └── units
│ ├── alu.sv
│ ├── au.sv
│ ├── core_EX.sv
│ ├── core_ID.sv
│ ├── core_IF.sv
│ ├── core_MEM.sv
│ ├── core.sv
│ ├── cp0.sv
│ ├── lu.sv
│ ├── regfile.sv
│ ├── stdout.sv
│ ├── timer.sv
│ └── VGA.sv
├── memory.mem
└── SOC_run_sim
but we don’t need to go through all of them to understand what it does, in short:
- infra/src contains the register transfer level for the whole SoC, it’s the high abstraction layer that describes how data is transformed as it is passed from register to register, it contains
SOC.sv
, which is the top level,data_mem.sv
, which models the memory (more on this later), CPU pipeline stages, peripherals (UART/stdout, etc.) and all the support modules that together model the hardware the simulator runs. - memory.mem is a text-hex file in
$readmemh
format (text, lines with@ADDR
followed by hex bytes). Verilog allows you to initialize memory from a text file with either hex or binary values, in our case,data_mem.sv
shows$readmemh("memory.mem", data_seg);
- SOC_run_sim, our executable, runs the simulation, loads a hex memory file (
memory.mem
) and starts up a MIPS64 little-endian CPU.
But let’s cut to the chase, what is all of this actually doing?
$ ./SOC_run_sim
Bootloading
Starting verification:
Incorrect key
HALT
Well… not much right now.
Why is this happening? Well, the author mentioned that the memory is “corrupted”, analyzing memory.mem
we can see what’s happening:
@00000000
28 09 00 00
00 00 00 00
ef bf bd ef
bf bd ef bf
...
The first few lines contain several EF BF BD
(the UTF-8 replacement character, “�”), a clear sign of corruption.
Our goal is to recover the boot ROM from the corrupted image, decompile, understand the “key” check (as we will see, the flag itself), craft the correct string and patch the memory so the verification passes and the board “boots.”
Decompiling
Like many modern reverse engineering challenges, this step won’t be as easy as throwing the binary into IDA or Ghidra, it’s a little trickier than that. Running SOC_run_sim
through IDA awakens cosmic horrors that are best left undisturbed:
Enter sus.py
, courtesy of my teammate @nect
f = open('memory.mem')
block={}
cur = -1
acc = b""
for l in f.readlines():
if l.startswith('@'):
if cur >= 0:
block[cur] = acc
acc = b""
cur = int(l[1:], 16)
else:
for x in l.split():
acc += int(x,16).to_bytes(1)
if cur >= 0:
block[cur] = acc
o = open('out.bin', 'wb')
last_addr = 0
for addr, mem in sorted(block.items()):
print("Addr:", addr)
print(mem)
o.write(b"\0"*(addr-last_addr))
last_addr=addr+len(mem)
o.write(mem)
It reads memory.mem
, groups blocks by address (@...
) and concatenates bytes, filling gaps with zeros, and writes everything to out.bin
.
We can now repeat the previous step and decompile the newly obtained file with IDA,
- File format: Binary file (raw).
- CPU: mipsl, ABI n64.
- Base address:
0x0
.
Much better.
The main code starts at 0x100
, it immediately prints:
"Bootloading\n"
"Starting verification:"
using a “UART print” routine (sub_838
) that writes to MMIO 0x20000010
.
Running strings
on out.bin
lends us a few extra insights (and a little waste of time):
GCC: (GNU) 15.1.
xVB40
hB4x
<!CB4!
2B4&
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
Bootloading
Starting verification:
Incorrect key
HALT
===verification passed!===
vq{uv|qw
Incorrect key
vq{uv|qw
flag[15] = key[0]-16 = 'v'(118)-16 = 102 = 'f'
flag[16] = key[1]-16 = 'q'(113)-16 = 97 = 'a'
flag[17] = key[2]-16 = '{'(123)-16 = 107 = 'k'
flag[18] = key[3]-16 = 'u'(117)-16 = 101 = 'e'
flag[19] = key[4]-16 = 'v'(118)-16 = 102 = 'f'
flag[20] = key[5]-16 = '|'(124)-16 = 108 = 'l'
flag[21] = key[6]-16 = 'q'(113)-16 = 97 = 'a'
flag[22] = key[7]-16 = 'w'(119)-16 = 103 = 'g'`
… Let’s move on.
Time to Rev
In sub_100
the firmware sets the key pointer to the data blob starting at unk_8
(memory offset 0x00000008
in memory.mem
) and passes it to sub_208
as arg_18
, the pointer to the candidate flag buffer (which I’ll call buf
in the following examples)
dli $v0, unk_8
sd $v0, 0x70+var_58($sp)
The core of the flag verification lies in the function labelled sub_208
by IDA, its prologue checks that v0 == 0
, if it does, it jumps to the verification step loc_224
, otherwise it makes a simulator syscall (0x6D8
) and returns:
ROM:0000000000000208 nop
ROM:000000000000020C lw $v0, arg_0($sp) ; load first argument (stack slot IDA named arg_0)
ROM:0000000000000210 beqz $v0, loc_224 ; if (v0 == 0) jump to verification
ROM:0000000000000214 nop ; branch-delay slot
ROM:0000000000000218 syscall 0x6D8 ; else: make a simulator syscall/trap
ROM:000000000000021C jr $ra ; and return immediately
ROM:0000000000000220 nop
1. Prefix check: uiuctf{
(bytes 0..6)
ld $v0, arg_18($sp) ; buf
lb $v0, 0($v0) ; buf[0]
xori $v0, 0x75 ; 'u'
sltiu $v0, 1
andi $v0, 0xFF
move $v1, $v0
ld $v0, arg_18($sp)
daddiu $v0, 1
lb $v0, 0($v0) ; buf[1]
xori $v0, 0x69 ; 'i'
... (same pattern for 'u','c','t','f','{')
daddiu $v0, 6
lb $v0, 0($v0) ; buf[6]
xori $v0, 0x7B ; '{'
...
li $v0, 7
bne $v1, $v0, loc_318 ; require all 7 matches
in pseudoC:
if ( (*a12 == 0x75LL) // 'u'
+ (a12[1] == 0x69LL) // 'i'
+ (a12[2] == 0x75LL) // 'u'
+ (a12[3] == 0x63LL) // 'c'
+ (a12[4] == 0x74LL) // 't'
+ (a12[5] == 0x66LL) // 'f'
+ (a12[6] == 0x7BLL) == 7LL ) // '{'
flag = uiuctf{
2. Mirror pattern for bytes 7 to 12
A loop runs with i = 0..5 (stored in idx
at 0xC($sp)
), splitting into cases:
- i == 0 or 2 →
buf[i] == buf[i+7] + 0x20
(lowercase equals uppercase + 32) ->loc_410
routine - i == 1 →
buf[i+7] == '_'
->loc_45C
routine - i == 3,4,5 →
buf[i] == buf[i+7]
->loc_45C
/loc_4A0
routines
loc_3F4:
lw $v0, 0xC($sp) ; idx
beqz $v0, loc_410 ; idx == 0 → case 1 (lowercase = uppercase+0x20)
...
lw $v1, 0xC($sp)
li $v0, 2
bne $v1, $v0, loc_45C ; idx != 2 → cases 2/3
...
loc_410: ; case 1: idx==0 or idx==2
lw $v0, 0xC($sp) ; idx
ld $v1, 0x18($sp) ; buf
daddu $v0, $v1, $v0
lb $v0, 0($v0) ; a0 = buf[idx]
move $a0, $v0
lw $v0, 0xC($sp)
daddiu $v0, 7
ld $v1, 0x18($sp)
daddu $v0, $v1, $v0
lb $v0, 0($v0) ; v0 = buf[idx+7]
addiu $v0, 0x20 ; v0 += 0x20
xor $v0, $a0, $v0 ; buf[idx] == buf[idx+7] + 0x20
sltiu $v0, 1
andi $v0, 0xFF
... accumulate into ok-bit ...
loc_45C: ; cases 2/3
lw $v1, 0xC($sp)
li $v0, 1
bne $v1, $v0, loc_4A0 ; if idx != 1 → case 3
...
; case B: idx == 1 → enforce underscore at buf[idx+7]
lw $v0, 0xC($sp)
daddiu $v0, 7
ld $v1, 0x18($sp)
daddu $v0, $v1, $v0
lb $v0, 0($v0) ; buf[idx+7]
xori $v0, 0x5F ; '_'
sltiu $v0, 1
...
loc_4A0: ; case 3: idx in {3,4,5} → equality
lw $v0, 0xC($sp)
ld $v1, 0x18($sp)
daddu $v0, $v1, $v0
lb $v1, 0($v0) ; buf[idx]
lw $v0, 0xC($sp)
daddiu $v0, 7
ld $a0, 0x18($sp)
daddu $v0, $a0, $v0
lb $v0, 0($v0) ; buf[idx+7]
xor $v0, $v1, $v0 ; buf[idx] == buf[idx+7]
sltiu $v0, 1
...
in pseudoC:
for (int i = 0; i < 6; i++) {
if (i == 0 || i == 2) ok &= (buf[i] == (char)(buf[i+7] + 0x20));
else if (i == 1) ok &= (buf[i+7] == '_');
else ok &= (buf[i] == buf[i+7]); // i in {3,4,5}
}
TL;DR: positions 7..12 mirror 0..5 as uppercase/same; position 8 is _
flag = uiuctf{U_Uctf
3. Stand-alone character checks
buf[13] == '_'
:
ld $v0, 0x18($sp)
daddiu $v0, 0xD
lb $v0, 0($v0)
xori $v0, 0x5F ; '_'
sltiu $v0, 1
...
buf[14] == 'm'
andbuf[15] == '1'
:
ld $v0, 0x18($sp)
daddiu $v0, 0xE
lb $v1, 0($v0)
li $v0, 0x6D ; 'm'
bne $v1, $v0, loc_630 ; fail if not 'm'
...
ld $v0, 0x18($sp)
daddiu $v0, 0xF
lb $v1, 0($v0)
li $v0, 0x31 ; '1'
bne $v1, $v0, loc_630
buf[28] == '_'
:
ld $v0, 0x18($sp)
daddiu $v0, 0x1C
lb $v1, 0($v0)
li $v0, 0x5F ; '_'
bne $v1, $v0, loc_630
buf[23] + buf[24] == 'S'
ld $v0, 0x18($sp)
daddiu $v0, 0x17 ; buf[23]
lb $v0, 0($v0)
move $v1, $v0
ld $v0, 0x18($sp)
daddiu $v0, 0x18 ; buf[24]
lb $v0, 0($v0)
addu $v0, $v1, $v0 ; sum
li $v0, 0x53 ; 'S'
bne $v1, $v0, loc_630
'#' (0x23) + '0' (0x30) = 'S' (0x53)
flag = uiuctf{U_Uctf_m1.......#0...._
4. Tail check: bytes 29..37 must be abcdefghi
Loop indexed by arg_8
, 9 iterations:
loc_330:
lw $v0, arg_8($sp)
addiu $v0, 0x1E ; +30
daddiu $v0, -1 ; +29
ld $v1, arg_18($sp)
daddu $v0, $v1, $v0
lb $v1, 0($v0) ; buf[29 + i]
dli $v0, 0
lw $a0, arg_8($sp)
daddiu $v0, aAbcdefghijklmn ; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"
daddu $v0, $a0, $v0
lb $v0, 0($v0) ; table[i] → starts at 'a'
xor $v0, $v1, $v0 ; buf[29+i] == table[i]
...
lw $v0, arg_8($sp)
addiu $v0, 1
sw $v0, arg_8($sp)
loc_390:
lw $v0, arg_8($sp)
slti $v0, 9 ; i < 9
bnez $v0, loc_330
in pseudoC:
for ( i = 0; i < 9LL; ++i )
v14 = (unsigned __int8)v14 & ((char)a12[i + 29] == (unsigned __int64)aAbcdefghijklmn[i]);
flag = uiuctf{U_Uctf_m1.......#0...._abcdefghi}
5. Central 8+4 byte mixing (bytes 16..23 and 24..27)
This is the trickiest part, this block derives two accumulators from 8 bytes at [16..23]
and 4 bytes at [24..27]
, mixes with constants, rotates, cross-mixes, and compares to targets:
Load + constants + first XOR:
ld $v0, 0x18($sp)
ld $v0, 0x10($v0) ; packs buf[16..23] into 64b (little-endian)
sd $v0, 0x20($sp) ; save as x
ld $v0, 0x18($sp)
lw $v0, 0x18($v0) ; packs buf[24..27] into 32b (little-endian)
sw $v0, 0x28($sp) ; save as y
dli $v0, 0x1337C0DE12345678
sd $v0, 0x30($sp) ; c64
li $v0, 0x3EADBE3F
sw $v0, 0x38($sp) ; c32
ld $v1, 0x30($sp) ; c64
ld $v0, 0x20($sp) ; x
xor $v0, $v1, $v0
sd $v0, 0x30($sp) ; x ^= c64
lw $v1, 0x38($sp) ; c32
lw $v0, 0x28($sp) ; y
xor $v0, $v1, $v0
sw $v0, 0x38($sp) ; y ^= c32
Rotate-left and adds:
; rol64(x,8) via shifts/ors then add 0x0123456789ABCDEF
...
dli $v0, 0x123456789ABCDEF
daddu $v0, $v1, $v0 ; x += 0x0123456789ABCDEF
sd $v0, 0x30($sp)
; rol32(y,4) then add 0x87654321
li $v0, 0xFFFFFFFF87654321
addu $v0, $v1, $v0 ; y += 0x87654321
sw $v0, 0x38($sp)
Cross-mix + final XORs + compare:
; x ^= (uint64)y << 32
; y ^= (uint32)x
...
dli $v0, 0xFEDCBA9876543210
xor $v0, $v1, $v0 ; x ^= 0xFEDC...
sd $v0, 0x30($sp)
li $v0, 0x13579BDF
xor $v0, $v1, $v0 ; y ^= 0x13579BDF
sw $v0, 0x38($sp)
dli $v0, 0xC956B3009784E40F
sd $v0, 0x48($sp)
li $v0, 0xFFFFFFFF83C5A9D1
sw $v0, 0x50($sp)
ld $v1, 0x30($sp) ; x
ld $v0, 0x48($sp) ; target64
bne $v1, $v0, loc_7D8 ; fail if x != target
lw $v1, 0x38($sp) ; y
lw $v0, 0x50($sp) ; target32
bne $v1, $v0, loc_7D8 ; fail if y != target
in pseudoC:
uint64_t x = u64le(&buf[16]) ^ 0x1337C0DE12345678ULL;
uint32_t y = u32le(&buf[24]) ^ 0x3EADBE3F;
x = rol64(x, 8) + 0x0123456789ABCDEFULL;
y = rol32(y, 4) + 0x87654321U;
x ^= ((uint64_t)y) << 32;
y ^= (uint32_t)x;
x ^= 0xFEDCBA9876543210ULL;
y ^= 0x13579BDFU;
ok &= (x == 0xC956B3009784E40FULL);
ok &= (y == 0x83C5A9D1U);
I used the following python script to demangle this part (with a bit of overlap with bytes already discovered in section 4):
#decipher.py
def ror64(val, r):
return ((val >> r) | (val << (64 - r))) & 0xFFFFFFFFFFFFFFFF
def ror32(val, r):
return ((val >> r) | (val << (32 - r))) & 0xFFFFFFFF
def find_key_bytes():
target_E = 0xC956B3009784E40F
target_F = 0x83C5A9D1
E = target_E ^ 0xFEDCBA9876543210
F = target_F ^ 0x13579BDF
print(f"After final inverse XOR: E=0x{E:016x}, F=0x{F:08x}")
F_before_mix = F ^ (E & 0xFFFFFFFF)
E_before_mix = E ^ (F_before_mix << 32)
print(f"After inverse Feistel: E=0x{E_before_mix:016x}, F=0x{F_before_mix:08x}")
E_before_add = (E_before_mix - 0x0123456789ABCDEF) & 0xFFFFFFFFFFFFFFFF
F_before_add = (F_before_mix - 0x87654321) & 0xFFFFFFFF
print(f"After subtraction: E=0x{E_before_add:016x}, F=0x{F_before_add:08x}")
E_before_rot = ror64(E_before_add, 8)
F_before_rot = ror32(F_before_add, 4)
print(f"After inverse rotation: E=0x{E_before_rot:016x}, F=0x{F_before_rot:08x}")
E_original = E_before_rot ^ 0x1337C0DE12345678
F_original = F_before_rot ^ 0x3EADBE3F
print(f"Original values: E=0x{E_original:016x}, F=0x{F_original:08x}")
E_bytes = E_original.to_bytes(8, 'little')
F_bytes = F_original.to_bytes(4, 'little')
char23 = E_bytes[7] #ultimo byte di E
char24 = F_bytes[0] #primo byte di F
print(f"\nVerify sum: 0x{char23:02x} + 0x{char24:02x} = 0x{char23 + char24:02x} (Has to be 0x53)")
print(f"\nBytes 16-23: {E_bytes.hex()} = '{E_bytes.decode('ascii', errors='replace')}'")
print(f"Bytes 24-27: {F_bytes.hex()} = '{F_bytes.decode('ascii', errors='replace')}'")
return E_bytes + F_bytes
result = find_key_bytes()
Bytes 16-23: 70736c3076657223 = 'psl0ver#'
Bytes 24-27: 30643030 = '0d00'
flag = uiuctf{U_Uctf_m1psl0ver#0d00_abcdefghi}
Now, if everything is correct, we should be able to patch our memory.mem file and see \n===verification passed!===\n
printed in the output.
Patching it up
Remember data_mem.sv
?
In Verilog, the memory initialization function is defined as follows:
$readmemh("hex_memory_file.mem", memory_array, [start_address], [end_address])
start_address
is optional and undefined in our case, so memory starts at 0x0.
The firmware reads the candidate key starting at absolute address 0x00000008 (unk_8
in the disassembly), and in memory.mem
the @00000000
block clearly shows those ef bf bd
bytes starting at offset 0x08
.
Now let’s ASCII-encode the flag and add a null terminator at the end, while keeping the rest unchanged:
//memory.mem
@00000000
28 09 00 00
00 00 00 00
75 69 75 63
74 66 7b 55
5f 55 63 74
66 5f 6d 31
70 73 6c 30
76 65 72 23
30 64 30 30
5f 61 62 63
64 65 66 67
68 69 7d 00
47 43 43 3a
20 28 47 4e
Make sure to keep the file name the same as well, the elf automatically takes memory.mem
as input.
Everything is back up and running!
Here is the final script I used during the CTF:
from typing import Tuple
# do a barrel roll
def rol32(v: int, r: int) -> int:
return ((v << r) & 0xFFFFFFFF) | (v >> (32 - r))
def ror32(v: int, r: int) -> int:
return ((v >> r) | (v << (32 - r))) & 0xFFFFFFFF
def rol64(v: int, r: int) -> int:
return ((v << r) & 0xFFFFFFFFFFFFFFFF) | (v >> (64 - r))
def ror64(v: int, r: int) -> int:
return ((v >> r) | (v << (64 - r))) & 0xFFFFFFFFFFFFFFFF
# cryptoninja block
CT_X = 0xC956B3009784E40F
CT_Y = 0xFFFFFFFF83C5A9D1 & 0xFFFFFFFF # 32 bit
def invert_block() -> Tuple[bytes, bytes]:
x3 = CT_X ^ 0xFEDCBA9876543210
y3 = (CT_Y ^ 0x13579BDF) & 0xFFFFFFFF # 32 bit
x3_low = x3 & 0xFFFFFFFF
y2 = x3_low ^ y3
x2 = x3 ^ (y2 << 32)
x1 = (x2 - 0x0123456789ABCDEF) & 0xFFFFFFFFFFFFFFFF
y1 = (y2 - 0xFFFFFFFF87654321) & 0xFFFFFFFF
x0 = ror64(x1, 8)
y0 = ror32(y1, 4)
eight = x0 ^ 0x1337C0DE12345678 # 64 bit
four = y0 ^ 0x3EADBE3F # 32 bit
return eight.to_bytes(8, 'little'), four.to_bytes(4, 'little')
def build_flag() -> str:
eight, four = invert_block()
flag = (
b"uiuctf{" # prefix
b"U_Uctf_" # xor
b"m1" # dedicated check
+ eight # bytes 16‑23
+ four # bytes 24‑27
+ b"_" # offset 28
+ b"abcdefghi" # offset 29‑37
+ b"}" # closure
)
return flag.decode()
#verifica
def verify(flag: str) -> bool:
assert flag.startswith("uiuctf{") and flag.endswith("}")
assert flag[7] == "U"
assert flag[8] == "_"
assert flag[9:13] == "Uctf"
assert flag[13] == "_"
assert flag[14:16] == "m1"
assert (ord(flag[23]) + ord(flag[24])) == 0x53
assert flag[28] == "_"
assert flag[29:38] == "abcdefghi"
import struct
key = flag.encode()
eight = int.from_bytes(key[16:24], 'little')
four = int.from_bytes(key[24:28], 'little')
x = rol64((eight ^ 0x1337C0DE12345678), 8)
x = (x + 0x0123456789ABCDEF) & 0xFFFFFFFFFFFFFFFF
y = rol32((four ^ 0x3EADBE3F), 4)
y = (y + 0xFFFFFFFF87654321) & 0xFFFFFFFF
x ^= (y << 32)
y ^= x & 0xFFFFFFFF
x ^= 0xFEDCBA9876543210
y ^= 0x13579BDF
return x == CT_X and y == CT_Y
if __name__ == "__main__":
flag = build_flag()
print("Flag:", flag)
assert verify(flag), "something went wrong!"
Trivia and extras
Fun fact: the flag format was actually changed due to this solve during the course of the competition, after an exchange I had with one of the moderators; it initially did not contain a #
, the full regex was uiuctf{[a-zA-Z0-9_&]+}
, this made me double check and second guess the flag for quite a while before actually submitting it. No big deal though, it was fixed shortly after.
During our first approach to the challenge, simonedimaria managed to recompile the source files with debugging logs:
Interrupe Handler Address: 0000000008000040
---- Damaged region dump ----
mem[8] = ef
mem[9] = bf
mem[a] = bd
mem[b] = ef
mem[c] = bf
mem[d] = bd
mem[e] = ef
mem[f] = bf
mem[10] = bd
mem[11] = ef
mem[12] = bf
mem[13] = bd
mem[14] = ef
mem[15] = bf
mem[16] = bd
mem[17] = ef
mem[18] = bf
mem[19] = bd
mem[1a] = ef
mem[1b] = bf
mem[1c] = bd
mem[1d] = ef
mem[1e] = bf
mem[1f] = bd
mem[20] = ef
mem[21] = bf
mem[22] = bd
mem[23] = ef
mem[24] = bf
mem[25] = bd
mem[26] = ef
mem[27] = bf
mem[28] = bd
mem[29] = ef
mem[2a] = bf
mem[2b] = bd
mem[2c] = ef
mem[2d] = bf
mem[2e] = 7d
-----------------------------
PC=0x000000000000010c, inst=0x24190148, rs_data(t0)=0x0000000000000000, rt_data(i)=0x0000000000000000
writeback regnum = 29, data = 0000000000000d00
PC=0x0000000000000110, inst=0x03200009, rs_data(t0)=0x0000000000000000, rt_data(i)=0x0000000000000000
writeback regnum = 28, data = 0000000000000af0
PC=0x0000000000000114, inst=0x00000000, rs_data(t0)=0x0000000000000000, rt_data(i)=0x0000000000000000
writeback regnum = 31, data = 0000000000000d00
writeback regnum = 25, data = 0000000000000148
write addr: 0000000000000cf8, data: 0000000000000d00, type: 4
writeback regnum = 29, data = 0000000000000c90
writeback regnum = 2, data = 0000000000000000
...
Only to later realize that those were only puts
-related logs, his contribution was nonetheless crucial to the final solution.