I'm reading the famous "Smash the stack and profit" paper, and to reiterate, the whole point of the idea is to put some binary code in a buffer, overflows it to rewrite the return address (RET) and go from there.
In the middle of paper there is a picture for this:
bottom of DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF top of
memory 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF memory
buffer sfp ret a b c
<------ [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]
^|^ ^| |
|||_____________||____________| (1)
(2) ||_____________||
|______________| (3)
top of bottom of
stack stack
And there is a code block with calculated offsets (because we have to manually count the bytes for JMP and CALL):
------------------------------------------------------------------------------
jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2b # 5 bytes
.string \"/bin/sh\" # 8 bytes
------------------------------------------------------------------------------
And then it says:
Looks good. To make sure it works correctly we must compile it and run it. But there is a problem. Our code modifies itself, but most operating system mark code pages read-only.
I got confused here and feel that I actually don't fully understand the idea. Here are my two questions:
Q1: Let's go back to the original idea (put binary code into buffer and rewrites RET to fire that binary code). I know that code lives in text segment, which is independent of stack and heap. So, even if we are able to rewrite the RET, how come the OS permits code run from stack? After all the binary code still lives in a buffer on stack.
Q2: Where does the code rewrite itself? My understanding is that, the code simply rewrites part of the stack:
- Step 1:
popl
top of stack toesi
, but since this "code" is in the buffer, it is also sitting on top of the stack, so basically this means, I'm going to pass the base address of the string/buffer toesi
- Step 2: and then move a bunch of data to an offset of that address. The string/buffer is definitely larger than
0x8
or evenoxc
bytes, so that means the string itself got rewritten.
Is my understanding correct? I still don't see how this piece of code work, but I'll drill deeper whence the two questions are cleared.