1

So after changing the entry point via the e_entry field I managed to execute my shellcode before returning control to the original entry point. Here's how I did it:

 // write string and jump to OEP, patch address at 23
    unsigned char shellcode[] = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\xeb"
                      "\x16\x5e\xb0\x01\x40\xb7\x01\xb2\x09\x0f"
                      "\x05\x48\xb8\x41\x41\x41\x41\x41\x41\x41"
                      "\xff\xe0\xe8\xe5\xff\xff\xff\x68\x69\x6a"
                      "\x61\x63\x6b\x65\x64\x0a";

So as soon as I parse the ELF header I patch the shellcode:

uint64_t oep = ehdr->e_entry;
memcpy(&opcode[23], &oep, 8);

Everything works, the shellcode executes and then execution resumes where it should. The problem is that after the target's main function finishes it segfaults(To make things simple I just made a program that prints a string).

So I used gdb and IDA to see what's going on. Just to make things clear the flow of excution is as follows:

  • execution starts at the shellcode
  • shellcode jumps to _start
  • _start calls libc_start_main
  • main executes and returns to libc_start_main

Now, after main returns IDA shows the following:

libc_start_main:
 ext:000000000040244B                 lea     rax, [rsp+108h+var_98]
.text:0000000000402450                 mov     fs:300h, rax
.text:0000000000402459                 mov     rdx, cs:environ
.text:0000000000402460                 mov     edi, [rsp+108h+var_FC]
.text:0000000000402464                 mov     rsi, [rsp+108h+var_F8]
.text:0000000000402469                 mov     rax, [rsp+108h+mainaddr]
.text:000000000040246E                 call    rax             ; jump to main
.text:0000000000402470                 mov     edi, eax
.text:0000000000402472
.text:0000000000402472 loc_402472:                             ; CODE XREF: __libc_start_main+4AF↓j
.text:0000000000402472                 call    exit
.text:0000000000402477 ; -----------------------------------------------------------

The exit function ends up calling run_exit_handlers which appears to be the real culprit, as it faults with the following instruction:

mov     rdx, [rax+18h]
mov     rdi, [rax+20h]
mov     qword ptr [rax+10h], 0
mov     esi, ebp
ror     rdx, 11h
xor     rdx, fs:30h
call    rdx             ; faults
jmp     loc_40823A

For some reason rdx has the value of 9, which is not allowed and hence causes the segmentation fault.

I searched for information on run_exit_handlers but didn't find anything meaningful.

So my question is: Why is this happening? All my shellcode does is write a string and jump to the OEP, it shouldn't really affect anything else.

  • 1
    what is the program that you run your shellcode from? I think it might be difficult without checking the code in the debugger to find out what there's a value of 9, so if possible provide some working program that can be debugged – Paweł Łukasik Jan 26 at 16:58
  • @PawełŁukasik it's a simple ELF_EXEC,it justs puts a string out. It's a one line program. – Trey Jan 26 at 17:04
  • 1
    ok, I'll try to prepare sth myself when I find the time and see if I can reproduce your results – Paweł Łukasik Jan 26 at 17:39
  • 1
    Are you sure that you pasted the right shellcode here? Everything before e0 e8 seems OK, but the rest disassembles to something strange like in eax, 0xff. By the way, I think you meant memcpy(&shellcode... instead of opcode. – bart1e Jan 27 at 19:31
  • 1
    Can you share an example patched binary? – sudhackar Jan 28 at 6:34
1
+50

You problem is that your shellcode uses RDX by setting dl to 9 for the sake of sys_write syscall. You are not setting this variable back to it's initial value of 0 and unfortunately this is being passed to _start where it's being treated as a pointer to function that will be executed as a finalizer. That's why you're getting a segfault since CPU try to execute code from address 0x9.

You should save the registers before you execute your shellcode and restore them before returning to the entry point. But for the sake of this example it will work if you would just clear (r)dx before jumping back.

Modified shellcode with added \x48\x31\xd2 for xor rdx,rdx and changed offsets at shellcode[10] and shellcode[37] to adjust for additional bytes in it (modified them manually - if you have the source code - you can do that in code and compile)

unsigned char shellcode[] = 
              "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\xeb"
              "\x19\x5e\xb0\x01\x40\xb7\x01\xb2\x09\x0f"
              "\x05\x48\xb8\x41\x41\x41\x41\x41\x41\x41\xff\x48\x31\xd2"               
              "\xff\xe0\xe8\xe2\xff\xff\xff\x68\x69\x6a"
              "\x61\x63\x6b\x65\x64\x0a"; 

A bit more explanation why RDX is problematic here. After your shellcode finishes its job it executes _start which has those two lines as in the beginning:

  00400a30 31  ed          XOR       EBP ,EBP
  00400a32 49  89  d1      MOV       R9 ,RDX

so whatever will be in RDX will be assigned to R9. Later the __libc_start_main is called and if you check its' signature (for example here) you can see it takes 7 arguments. The are passed via registers and according to Linux ABI the 6th is passed as a R9 register and the 6th parameter in _libc_start_main is callback for finalizers. That's why value in RDX matters as it's being passed to R9 and in the end ends up as a callback address.

|improve this answer|||||
  • I still don't get it why the value in RDX is being called as function. Is this specific to RDX? Where can I read more about this behavior? – Trey Jan 30 at 22:10
  • 1
    see my updated answer – Paweł Łukasik Jan 30 at 22:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.