Garys Sauce - Writeup

Created Sunday 01 May 2022

simulation/flag_tb.v

    flag_capture fc(.clk(clk), .rst(rst), .BTNL(BTNL), .SW(SW), .disp(disp));

The workhorse is the main loop that takes the last character (8 bits) of the flag, sets the signals (SW) based on their binary values. These signals are bound to the SW variables for the flag_capture function. That is where we need to look next.

	    reg [7:0] mask = 8'b11111111;
	    initial 
	    begin
	        SW = 16'b0000000000000000;
	        BTNL = 1'b0;
	
	        for (i=1; i <= 38; i=i+1) begin
	            SW = flag & mask; #20; BTNL = 1'b1; #20; BTNL = 1'b0; #40;
	            
			// The flag is processed from the back, and shifted right by 1 char 
	            flag = flag >> 8;
	        end
	    end

src/flag_capture.v module flag_capture(input clk, input rst, input [7:0] SW, input BTNL, output [7:0] disp); wire clk_1hz; clk_1hz cl(.clk(clk), .clk_1hz(clk_1hz)); garys_sauce gs(.clk(clk_1hz), .rst(rst), .BTNL(BTNL), .switches(SW), .Disp(disp)); endmodule

Not much in here, other than the SW inputs being passed to garys_sauce function. Let’s go there next.

src/garys_sauce.v

	// a few interesting things here ... an intermediate value
	    wire [7:0] intermediate;
	
	//...
	// When BTNL is high, the outputs Disp has the value of intermediate
	        if (BTNL) begin
	            Disp[7:0] = intermediate;
	// ...
	// The msb 4 bits of switches are multiplied with lsb 4 bits and the product..
	// I assumed that this implementation using Full and Half Adders was correct
	    fourbit_mult fbm(.a(switches[7:4]), .b(switches[3:0]), .s(mult_out));
	    
	// .. is fed in to the myxor function, resulting in the intermediate values 
	    myxor x(.in(mult_out), .out(intermediate));

From flag_capture.v, we can see that intermediate is tied to the display variables. Let’s check out myxor.

src/myxor.v

	// output = input ^ 0x0d
	module myxor(input [7:0] in, output [7:0] out);
	    reg [7:0] value = 8'hd;  // hex 0x0d
	    assign out = in ^ value;
	endmodule

The file has the key (0x0d) that is XOR-ed with input and returned as the output. Hence,
display = intermediate = (sw[7:4] * sw[3:0]) ^ 0x0d sw[7:0] contains the value of one character from the flag.

From the trace images, we are given the values of display and four bits of the switches, sw[0], sw[3], sw[5] and sw[6].

We should read the values of disp, SW, when BTNL is high. For example: For i=1 (i.e, the last character of the flag),

disp[7:0] is 0x56 (from the 3rd row of the image above) SW[0, 3, 5, 6] are [1,1,1,1]

From the relation given above,

SW = disp ^ 0x0d = 0x56 ^ 0x0d = 0x5b.

MSB LSB 0111 x 1101 = 01011011 [0x5b] SW[0,3,5,6] = [1,1,1,1] 1101 x 0111 = 01011011 [0x5b] SW[0,3,5,6] != [1,1,1,1]

>>> chr(int('01111101', 2))}' //(correct character) >>> chr(int('11010111', 2))×

Finally, we can automate the solution in a simple python script. To optimize the factoring, I created a dictionary of all possible product of 4 bit values, and stored their respective MSB and LSB factors. Then I laboriously captured the switch values and display values into an array and iterated through them to decipher the original character from the flag. I assembled it into a flag and reversed it, so that the order of the characters is correct, giving the final flag as flag{6082d68407f62c5c0f29820e0c42dea2}. The complete solution can be found in solve_gary.py