1

I'm trying to understand the basics of stack smashing attacks but I'm finding that the code seg faults when jumping to the overwritten return address. To isolate the issue, I wrote a small snippet of code exhibiting the same behaviour, using a single NOP instruction stored in the data segment of memory:

char nop[] = "\x90";
int main(){
   ((void(*)(void))nop)();
}

Which I'm then compiling as follows:

gcc -fno-stack-protector -z execstack nop.c -g -o nop

Executing the code step-wise from the function call in GDB gives the same results:

foo@bar:~/path/to/file$ gdb -q nop
/home/foo/.gdbinit:1: Error in sourced command file:
Ambiguous set command "dis intel": disable-randomization, disassemble-next-line, disassembler-options, disassembly-flavor...
Reading symbols from nop...
(gdb) list
1   char nop[] = "\x90";
2   int main(){ 
3      ((void(*)(void))nop)();
4   }
(gdb) break 3
Breakpoint 1 at 0x1131: file nop.c, line 3.
(gdb) run
Starting program: /home/foo/path/to/file/nop 

Breakpoint 1, main () at nop.c:3
3      ((void(*)(void))nop)();
(gdb) x/xi $rip
=> 0x555555555131 <main+8>: lea    0x2ed8(%rip),%rax        # 0x555555558010 <nop>
(gdb) step

Program received signal SIGSEGV, Segmentation fault.
0x0000555555558010 in nop ()
(gdb) x/xi $rip
=> 0x555555558010 <nop>:    nop

In similar questions, the issue seems to stem from the data segment being non-executable, which appears to be the case here too. Most answers seem to suggest using the execstack flag to disable this though, which clearly isn't working here.

Any help would be much appreciated!


Edit 1: Quick verification that the execstack flag is being set:

foo@bar:~/path/to/file$ execstack nop
X nop

Edit 2: Rerunning GDB using stepi rather than step suggests that the seg-fault happens the instruction after the code jumps to the NOP:

foo@bar:~/path/to/file$ gdb -q nop
/home/foo/.gdbinit:1: Error in sourced command file:
Ambiguous set command "dis intel": disable-randomization, disassemble-next-line, disassembler-options, disassembly-flavor...
Reading symbols from nop...
(gdb) break 3
Breakpoint 1 at 0x1131: file nop.c, line 3.
(gdb) run
Starting program: /home/foo/path/to/file/nop 

Breakpoint 1, main () at nop.c:3
3      ((void(*)(void))nop)();
(gdb) stepi
0x0000555555555138  3      ((void(*)(void))nop)();
(gdb) x/xi $rip
=> 0x555555555138 <main+15>:    callq  *%rax
(gdb) stepi
0x0000555555558010 in nop ()
(gdb) x/xi $rip
=> 0x555555558010 <nop>:    nop
(gdb) stepi

Program received signal SIGSEGV, Segmentation fault.
0x0000555555558010 in nop ()
(gdb) x/xi $rip
=> 0x555555558010 <nop>:    nop

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Neroph
  • 41
  • 4
  • 1
    Your OS may simply be not allowing the execstack to take effect. Look in `/proc//maps` to check memory protections (if you are using linux). – Jester Dec 30 '21 at 16:17
  • Thanks for the response. What would the easiest way of running and immediately pausing the process be, so I can find the pid and check the maps? – Neroph Dec 30 '21 at 16:25
  • 1
    gdb? :) You can have a look at the maps while it's stopped at the segfault. gdb itself has `info proc mappings` but that does not seem to include protections. – Jester Dec 30 '21 at 16:38
  • Thanks. I'm a little new to gdb and code / os details this low level! I think I've found the cause of the issue, which was a combination of the code being located in the data segment of memory, rather than the stack, and no return after the nop causing the processor to run wild in memory afterwards. Checking the maps seems to verify that the execstack flag is fine: `7ffffffde000-7ffffffff000 rwxp 00000000 00:00 0 [stack]` – Neroph Dec 30 '21 at 16:57
  • Your array is global, not on the stack. `gcc -z execstack` these days only applies to the stack. See [How to get c code to execute hex machine code?](https://stackoverflow.com/a/55893781) / [Unexpected exec permission from mmap when assembly files included in the project](https://stackoverflow.com/posts/comments/122825610) – Peter Cordes Dec 30 '21 at 22:06

1 Answers1

3

Just solved the problem, which was a combination of two issues:

  1. I assumed that the execstack flag would make all non-text segments of memory executable, however it seems this is not the case. To fix this, I made the raw hex code a local variable:
int main(){
   char nop[] = "\x90";
   ((void(*)(void))nop)();
}
  1. The next issue seems to have been that, with no return instruction, the code continues executing after the nop until it hits a bad area of memory. To fix this, I wrote a simple function performing the same task...
void nop() {
   return;
}

int main(){
   nop();
}

Extracted the hex from the disassembled code...

nop:
   \xf3\x0f\x1e\xfa  endbr64
   \x55              push %rbp
   \x48\x89\xe5      mov %rsp,%rbp
   \x90              nop
   \x5d              pop %rbp
   \xc3              retq

Then used these bytes as the basis for my character array:

int main(){
   char nop[] = "\xf3\x0f\x1e\xfa\x55\x48\x89\xe5\x90\x5d\xc3";
   ((void(*)(void))nop)();
}

This led to a successful execution:

foo@bar:~/path/to/file$ gdb -q nop
/home/foo/.gdbinit:1: Error in sourced command file:
Ambiguous set command "dis intel": disable-randomization, disassemble-next-line, disassembler-options, disassembly-flavor...
Reading symbols from nop...
(gdb) list
warning: Source file is more recent than executable.
1   int main(){
2      char nop[] = "\xf3\x0f\x1e\xfa\x55\x48\x89\xe5\x90\x5d\xc3";
3      ((void(*)(void))nop)();
4   }
(gdb) break 3
Breakpoint 1 at 0x114a: file nop.c, line 3.
(gdb) run
Starting program: /home/foo/path/to/file/nop 

Breakpoint 1, main () at nop.c:3
3      ((void(*)(void))nop)();
(gdb) x/xi $rip
=> 0x55555555514a <main+33>:    lea    -0xc(%rbp),%rax
(gdb) stepi
0x000055555555514e  3      ((void(*)(void))nop)();
(gdb) x/xi $rip
=> 0x55555555514e <main+37>:    callq  *%rax
(gdb) stepi
0x00007fffffffe204 in ?? ()
(gdb) x/xi $rip
=> 0x7fffffffe204:  endbr64 
(gdb) stepi
0x00007fffffffe208 in ?? ()
(gdb) x/xi $rip
=> 0x7fffffffe208:  push   %rbp
(gdb) stepi
0x00007fffffffe209 in ?? ()
(gdb) x/xi $rip
=> 0x7fffffffe209:  mov    %rsp,%rbp
(gdb) stepi
0x00007fffffffe20c in ?? ()
(gdb) x/xi $rip
=> 0x7fffffffe20c:  nop
(gdb) stepi
0x00007fffffffe20d in ?? ()
(gdb) x/xi $rip
=> 0x7fffffffe20d:  pop    %rbp
(gdb) stepi
0x00007fffffffe20e in ?? ()
(gdb) x/xi $rip
=> 0x7fffffffe20e:  retq   
(gdb) stepi
0x0000555555555150 in main () at nop.c:3
3      ((void(*)(void))nop)();
(gdb) x/xi $rip
=> 0x555555555150 <main+39>:    mov    $0x0,%eax
(gdb) continue
Continuing.
[Inferior 1 (process 68215) exited normally]
Neroph
  • 41
  • 4
  • `-z execstack` *used to* make everything executable, you're not crazy for thinking / remembering that. But it doesn't anymore. See [How to get c code to execute hex machine code?](https://stackoverflow.com/a/55893781) – Peter Cordes Dec 30 '21 at 21:53