Machine Problem 1
Created: February 22, 2024 at 20:20
Last Modified: February 22, 2024 at 20:20
Authors: Some people call me sycasecI ain’t readin allat:
- we break at
0x56556191(retfromvuln) to observe behavior ofeip - following the suggested information-giving commands in
gdb, we get the desirableespin vuln being0xffffcdf8 - we try to craft the shell code
- compiling the given
asmwith necessary incantations fails - we look for an exit one shell code in shell-storm
- we analyze shell code
\x90 * 12for padding\xf8\xcd\xff\xffredirecting oureip\x31\xc0\x40\x89\xc3\xcd\x80exit one shell code
- we also try inserting an
execve("/bin/bash")shell code (ofc from shell-storm) - $$$ profit
exit one shell code full:
|
|
However we also try a different method of inserting the shell code, as described further down below.
And with that,
Know thy enemy
Let’s first take a look at the instructions for main and vuln:
Then, we define hook-stop to allow us to view the next 1 instruction on the eip, and 16 words starting from the esp register. This is gonna give us an upside-down view of the stack starting from the $esp register. This is beneficial to us since the $esp is pointed to the very bottom of the current stack frame, allowing us to see the contents of the current frame being executed.
|
|
I hope this is a helpful visual of what we’re doing:
--------------------------- ╮
0xdeadbeef / $esp: {0x..} 0x.. 0x.. 0x.. │
--------------------------- │
stuff ├─ stack (x/16wx $esp) 16 words from esp
--------------------------- │
stuff │
--------------------------- ╯
We can see main allocating its own stack frame in the next instruction, by moving ebp to esp
we can confirm this by getting information on the stack frame with info frame
As we saw in dissassemble main above, the next instruction is to call <vuln>. Let’s single step and and view our registers to confirm that main’s stack frame is indeed in 0xffffcdf8:
Both esp and ebp at this point are at 0xffffcdf8 because main isn’t allocating any variables. The next instructions are to call <vuln> and allocate stack frame for vuln. lets move to vuln <+1> and look at the stack.
Lets analyze it a bit *(edited for clarity):
-
0x56556195 <main+3>: call 0x5655-617d <vuln>- stores return address in 1, which is at the address
0xffffcdf5->0xffffcdf8 - *this is the
ripor where theeipwill jump to after executing all ofvuln’s instructions.
- stores return address in 1, which is at the address
-
0x5655617d <vuln>: push %ebp- pushes the previous
ebp - *you guessed it, this is the
sfpor the previousebp(in this case, it’smain’s `ebp)
- pushes the previous
*The next 5 instructions (x/5i $eip) shows us:
mov %esp,%ebp: → move ebp into current esp (creating a new stack frame forvuln())sub $0x8,%esp: → decrement esp by 8 byteslea -0x8(%ebp),%eax: → allocate address forbuffer[8]push %eax: → pushbuffer[8]address into stackcall 0xf7c741b0: → call<_IO_gets>(gets()) subroutine
We can confirm these are exactly what the program is going to do by checking registers now:
We move a single step and review the registers:
The esp and ebp are now the same because of the mov %esp,%ebp instruction. Remember that the instruction shown by the command x/1i $eip in gdb is the next instruction, not the current one executed. Anyway, let’s push on while keeping a close eye at the stack:
Since our hook-stop was defined to view 16 words from the esp, we can see that the instruction <vuln+6>: lea -0x8(%ebp),%eax did allocate 8 bytes for buffer[8]. To further confirm this, lets print the location of buffer now with print &buffer:
In case it wasn’t obvious, our view of the stack printing from the $esp register gives us an inverted view of the stack, where the memory addresses increase as we go down.
We can see that:
- at address
0xffffcdf0→0xffffcdf4that the address0xffffcdf8is stored, which should be oursfp (old ebp) - right next to it is the
rip (old eip)(yes it is5655619a), storing where theeipshould continue execution. Looking at ourdisas main, that is the instruction right aftercall vuln
What do we do now?
First, lets grab our shell code:
Ghost in the (egg) Shell
The provided asm doesnt really compile on my end, even with the provided incantation
So let’s do what any decent “hacker” would do and look for it online. The glory days of insecure software have been long gone, so surely there is a myriad of old, low-level exploits online. The site that struck out to me immediately after searching for the keyword shell codes in google is:
ofc, because i’m lazy:
|
|
(thank you Charles Stevenson)
And what do you know, the mp1.pdf actually already provided us with this holy sauce. I didn’t even need to look any further:
|
|
We don’t really know what this does or why this works, all we know is that it should make our program exit with code 01. So let’s do some digging and analyze it a bit.
|
|
Now I can tell you that I know what exactly it is doing given these commands, but I can’t really explain why. I looked through the intel developer’s manual but I can’t really find an explanation as to why setting eax = 1 results in the syscall exit, or why it looks at ebx to provide the exit code, or why cd 80 (int $0x80) results in an interrupt. So for now let’s just press the i believe button on this and press on.
rip and tear
The call to gets() in the next instruction allows us to write into the start of buffer, 0xffffcde8 and beyond.
- we know that the green addresses is the
buffer[8], - we know that the teal addresses is the
sfp (old ebp), - we know that the red addresses is the
rip (main() eip)
From here, we can proceed in two ways:
-
We can snipe
buffer[8]and insert our shell code there. It fits, since the shell code is only 7 bytes long, and redirect rip to the start of our buffer at0xffffcde8. Lets call this the snipe method, because idk, I’m bad at naming things. -
since we can write to higher memory addresses anyway, we can just redirect rip to
0xffffcdf8where the rest of our shell code would be located anyway
- Let’s call this the matrix method, because as I said, I’m bad at naming things. Anwyay, here’s a quick visualization of our goal:
|
|
Because we can, lets do both!
Snipe method
Let’s outline the steps of our creating the snipe shell code:
- insert
exit 01shell code (\x31\xc0\x40\x89\xc3\xcd\x80) into thebuffer - insert
NOP’s (\x90) to pad the remaining 1 byte and also pad thesfp- we don’t really need to do anything with the
sfp, we’re just padding it so we can get to therip.
- we don’t really need to do anything with the
- redirect the rip to the start our buffer at
0xffffcde8
Of course we can just write the bytes in the terminal directly to payload, but for the purpose of re-usability lets do it in python:
|
|
Our final payload should look like something like this in bytes:
\x31\xc0\x40\x89\xc3\xcd\x80 \x90\x90\x90\x90\x90 \xe8\xcd\xff\xff
- the python
struct.pack()simply packs thebuf_addrin bytes, and as you may have already guessed (or you already knew about that, idk), the"<I"specifies it to be in little-endian format. - note that the spaces shouldn’t be there, I’m just adding those in to make it readable to humans.
now let’s rerun vuln with the payload automatically inserted when it asks for input with r < payload
I set a break point at the part right before the esp is incremented to deallocate the stack frame for vuln, so we can look at the stack from the esp at that point:
as we can see, the buffer starting at 0xffffcde8 already has the exit(1) shell code. It has been padded with a nop (\x90), but that will be fine since its in little-endian format. The rip at 0xffffcdf4 has also been redirected to 0xffffcde8, which will be executed (since we turned off write/execute protection in our compiler flags).
Looking at our disas vuln, the next instructions will be
0x56556190 <+19>: leave→ moveesptoebpand popsfp(which we have set to0x90909090) making theebpstore0x90909090. we can confirm this in the next instruction when we look atinfo registers.0x56556191 <+20>: ret→ popripand continue execution.
Now that we have returned from vuln, since we have changed rip we can see that it is now executing from our buffer, 0xffffcde8. If you can recall, we have defined hook-stop to show us the next instruction in eip and the next 16 words from esp. Of course, the esp is in 0xffffcdf8 since the esp was incremented a while ago, so we cant really see anything of value there.
So let’s take a look at the stack by printing the next 16 words from the address of our buffer, 0xffffcde8:
And what do you know, our shell code worked! We’re deadset on a homerun and the only thing that’s stopping us is ctrl + c. Of course, we’re not stopping now. We’ll continue execution. But before that, i claimed that the ebp will be set to x90909090 a while ago. Lets take a look at our registers now.
Hell yeah. Let’s continue execution with c and look at the glorious exit code.
and just like that, snipe method worked just fine.
Matrix method
To recap:
The plan is to:
- pad
bufferandsfpwithNOPs, - redirect
ripto0xffffcdf8(where the rest of our shell code will be placed, due to the nature ofgetsallowing us to write up into higher memory addresses beyondbuffer) - store and execute our shell code at
0xffffcdf8and beyond
Since the buffer is 8 bytes and the sfp is 4 bytes, we will need 12-bytes’ worth of NOPs, our redirect to 0xffffcdf8 which is actually main's ebp.
If you recall, we already discussed this above:
mainallocates stack frame at0xffffcdf8- doesn’t really have variables, so just immediately stores values below it
- as you can see the sfp normally points to
0xffffcdf8, so that confirms that0xffffcdf8is indeedmain’sebp.
Now, for our shell code:
|
|
Our final payload should look like something like this in bytes:
\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90 \xe8\xcd\xff\xff \x31\xc0\x40\x89\xc3\xcd\x80
Without further ado, let’s rerun vuln with our payload.
I skipped over the initial things, I believe I don’t need to explain those anymore. As we can see here:
bufferandsfpare filled withNOPs- rip at
0xffffcdf4points towards 0xffffcdf8 - 0xffffcdf8 up until
0xffffcdffcontains ourexit(1)shell code.
Let’s skip to ret and single step from here just to view it more closely:
And there you go, we are already executing exit(1). Just to confirm, let’s view the next 5 instructions in the eip:
And that’s that. Lets continue and get our sweet sweet exited with code 01
Now that we have a reliable way to insert longer shell code, lets try execve("/bin/bash")
Beyond the matrix
This is just for fun, we grab shell code from shell-storm that executes execve("/bin/bash")
|
|
Before pressing the i believe button, let’s look at what the shell code does though. As usual, I don’t know why \xcd\x80 calls an interrupt, it just does.
|
|
If you look closely, we’re reversing ”/bin/sh” for the little endian format. Anyway, execve allows us to execute a program, or so it says in the man-pages:
|
|
Now lets rerun vuln with our shiny, brand-new payload:
- teal is our
bufferandsfp - red is our
rip - green is our shell code , with the orange part showing the last 4 bytes, ending with the
interruptof course.
We continue and take a look at what is going on with the eip. Since we have 12 instructions in the asm version of the shell code, lets take a look at the next 12 instructions of our eip:
That’s our execve shell code! Anyway, lets continue from here:
We can see that its executing /usr/bin/bash, because /bin/bash is just a symlink. Anyway, now we have what they call arbitrary code execution – sort of. I can’t really get this to run in the terminal, even when I disable Address space layout randomization (ASLR) for vuln. I suspect it might have something to do with having a different memory address as compared to running it in gdb, but that’s already outside the scope of this machine problem, so I believe that’s the end of this writeup.
それでわ、 じゃあね!
Changelog
- 2024-02-17T04:00:00+08:00 - first full writeup upload
- 2024-02-22T20:20:00+08:00 - full rewrite
- 2024-02-24T12:00:00+08:00 - minor edits to first section and second sections for clarity
Notes
- I haven’t gotten around to adding a “last modified” tag yet, it should be simple to add, since we already have a “date created” tag.