These modules,
- generate six permutation states from fixed seed (
T_0
) - opens
key.txt
(T_2
) - reads each numbers from
key.txt
(T_3
) - generates answer by permutation (
T_1
) - compares answer with input (
T_3
) - checks if you got exactly 24 correct answers (
T_3
) - prints 'Success Auth!' if you got it right. (
T_7
)
There are only 32 answers, and we can try 1023 times. Brute force to find a answer and spam it to get 24 matches.
790 1231
s will do the trick.
We're given a ICARUS Verilog assembly file app
, which can be directly executed under vvp simulation engine.
Along with that, we have a simple wrapper script written in python. This python script reads in up to 1023 numbers, and feeds them into the app
via a temp file. If the app
exits with Success Auth!
, it just prints out the flag.
To solve this problem, we need to analyze the app
and find a way to make it print out Success Auth!
. Unlike the verilog source code itself, the compiled assembly was not very human friendly.
The vvp instruction set reference will come in handy.
Given that, we can analyze the app
.
There are eight modules in the app
.
The first module is basically a PRNG, which uses a fixed seed value 516562327
.
T_0 ; PRNG Module
%pushi/vec4 516562327, 0, 33;
%store/vec4 v0x5614c9a85fc0_0, 0, 33; Set seed value
%vpi_func 4 20 "$urandom" 32, v0x5614c9a85fc0_0 {0 0 0};
%pushi/vec4 16, 0, 32;
%mod;
%pad/u 4; Generate random, using seed, truncate.
%store/vec4 v0x5614c9a608a0_0, 0, 4; Store random value
With a fixed seed value, this module generates a total of six numbers. As the seed value is fixed, the generated random numbers are fixed.
The second module is a permutation module and it's the longest, taking up more than the half of lines.
T_1 ;
%wait E_0x5614c9a65a10; always @cnt
%load/vec4 v0x5614c9a4d060_0;
%pad/u 32;
%pushi/vec4 4, 0, 32;
;Routine that permutes numbers
Upon every change of cnt, which is incremented on every number read, this module permutes the password.
This module just opens up and returns the file handle for key.txt
T_2 ;
%pushi/vec4 2, 0, 3;
%store/vec4 v0x5614c9a865f0_0, 0, 3;
%pushi/vec4 0, 0, 5;
%store/vec4 v0x5614c9a861c0_0, 0, 5;
%pushi/vec4 0, 0, 9;
%store/vec4 v0x5614c9a868c0_0, 0, 9; zero variables
%vpi_func 3 30 "$fopen" 32, "key.txt", "r" {0 0 0}; open key.txt
%store/vec4 v0x5614c9a86350_0, 0, 32; The handle for `key.txt`
%load/vec4 v0x5614c9a86350_0;
%cmpi/e 0, 0, 32;
%jmp/0xz T_2.0, 4;
%vpi_call 3 32 "$display", "keyHandler was NULL" {0 0 0};
%vpi_call 3 33 "$finish" {0 0 0}; error handling
This module reads a single number on each line of key.txt
.
T_3 ;
%wait E_0x5614c9a66a20; always @(posedge clk)
%vpi_func 3 38 "$fscanf" 32, v0x5614c9a86350_0, "%d\012", v0x5614c9a86430_0 {0 0 0};
%store/vec4 v0x5614c9a86280_0, 0, 32; read and store a number from file, ending with newline.
%vpi_func 3 39 "$feof" 32, v0x5614c9a86350_0 {0 0 0};
%nor/r;
%flag_set/vec4 8;
%jmp/0xz T_3.0, 8;
After that, it increments cnt
to trigger T_1
%load/vec4 v0x5614c9a861c0_0;
%pushi/vec4 1, 0, 5;
%add;
%store/vec4 v0x5614c9a861c0_0, 0, 5; v0x5614c9a861c0_0(cnt) += 1, this triggers permutation of the password
After module T_1
permutates the answer, it compares it with the input.
%load/vec4 v0x5614c9a866d0_0; permutated answer
%load/vec4 v0x5614c9a86430_0; your input number
%parti/s 5, 0, 2;
%cmp/e; is input == answer?
%jmp/0xz T_3.2, 4;
%load/vec4 v0x5614c9a868c0_0; input == answer
%pushi/vec4 1, 0, 9;
%add;
%store/vec4 v0x5614c9a868c0_0, 0, 9; success += 1
T_3.2 ;
%jmp T_3.1; input != answer
Upon spending all the numbers given as input, it checks how many correct answers were there.
T_3.0 ;
%load/vec4 v0x5614c9a868c0_0; Success Count
%pad/u 32;
%cmpi/e 24, 0, 32; Check Success Count == 24
%jmp/0xz T_3.4, 4;
%pushi/vec4 1, 0, 3;
%assign/vec4 v0x5614c9a865f0_0, 0; assign passReg = b'1
%jmp T_3.5;
T_3.4 ;
%vpi_call 3 49 "$display", "Success Count: %d\012", v0x5614c9a868c0_0 {0 0 0};
%pushi/vec4 0, 0, 3;
%assign/vec4 v0x5614c9a865f0_0, 0; assign passReg = b'0
Given there are exactally 24 correct answers, it assigns 1 into passReg
, for v0x5614c9a865f0_0. More and less would end up with 0.
we skip T_4
, T_5
. It's just a timer alternating between 0 and 1 every 50ns. T_4
initializes the timer, T_5
alternates it.
we also skip T_6
. It just sets reset_n
on all modules at the start.
This module just prints out if you got it right or not.
T_7 ;
%wait E_0x5614c9a65700; Upon event E_0x5614c9a65700, which is reflects v0x5614c9a865f0_0(passReg)
%load/vec4 v0x5614c9a86af0_0;
%pad/u 32;
%cmpi/e 1, 0, 32; Success == 1
%jmp/0xz T_7.0, 4;
%vpi_call 2 26 "$display", "Success Auth!" {0 0 0}; You get flag
%vpi_call 2 27 "$finish" {0 0 0};
%jmp T_7.1;
T_7.0 ;
%load/vec4 v0x5614c9a86af0_0;
%pad/u 32;
%cmpi/e 0, 0, 32;
%jmp/0xz T_7.2, 4;
%vpi_call 2 30 "$display", "Failed To Auth" {0 0 0}; No flag 4 U
%vpi_call 2 31 "$finish" {0 0 0};
The permutation routine is tricky to reverse, but we don't have to.
Here, the permutation routines' output range is limited with 5 bits.
v0x5614c9a866d0_0 .net "password", 4 0, L_0x5614c9a86d30; 5-bit answer
(...code omitted...)
%load/vec4 v0x5614c9a86430_0;
%parti/s 5, 0, 2; bit select and signed extension
However, we can use up to 1023 inputs as answer, which is about a square of possible answer count.
After some extensive testing, we found out that the permutation routine returns 1231
as the seventh number.
Therefore, just spamming 1231
enough will allow us to solve the challenge.
Yet, 1023 1231
s returned about 2^5 successes, as expected.
Tuning it down to 790 1231
s did the trick.