shellcode injection via ptrace()
the injection opcode isn’t really elegant or anything, but i found other articles quite difficult to follow at first because of the advanced styles they are using. the ones i’ve seen involved forking, or ‘call instruction mimicry’, or ‘original register restoration’ or a combination of all of them. i start again, with the simple ‘hello world’. no shell-spawning or stuff like that.
the rundown basically consists of :
1) attaching to a process and read its registers (esp and eip)
2) allocate space in the stack segment for the shellcode
3) point eip to the start of the allocated space
4) give control back to the program. (in most cases, but not here. i simply exit())
first comes the test program i wanna trace. it’s just an infinite for loop that prints some text on the screen. my goal is to insert instructions that will make it print another string and quit.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int i = 0;
for(;; i++) {
printf("%d sheep%c jumping over the fence\n",
i, (i<2) ? : 's');
sleep(2);
}
return 0;
}
i also made a simple shellcode that just prints a string and exits. this one is a little different though because i tried to verify a statement i made while i was still starting to learn shellcoding. i quote:well, declaring a string is a forward declaration of bytes. and we can’t have any “meaningless” bytes in our executable space imho. by meaningless, i mean bytes that aren’t opcodes.the thing is. you actually can! as long as you skip over them. a proof of concept of some sort. though kinda ugly and pointless to implement. the real purpose of the call ‘backwards’ is to provide a negative address offset to the near call instruction, so it wont have null bytes. (0xff as opposed to 0x00 which is null)
anyway, i’ve edited the former post i made to clarify this issue.
example shellcode
global _start
section .text
_start
jmp .midpayload
.payload
pop ecx
xor eax, eax
xor ebx, ebx
xor edx, edx
jmp .endpayload
.midpayload
call .payload
db 'i love stephanie', 0xa
.endpayload
mov al, 0x4
mov bl, 0x1
mov dl, 6
int byte 0x80
mov al, 0x1
xor ebx, ebx
int byte 0x80
and lastly, the program that does the tracing and injecting./* basic shellcode injector via ptrace()
*
* 04/17/06 stephanie
*/
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/user.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* actually, a size of 0x4 is already enough since i
* just pop the first value i pushed (of size dword) inside my shellcode
* before pushing another value at the stack.
* see 'call' instructions. in shellcode listing */
#define TMP_STACK_SIZE 0x8
typedef struct user_regs_struct uregstruct;
static void attach(pid_t cpid);
static void put_into_child(pid_t, void *src, unsigned long dest, int len);
char shellcode[]="\xeb\x15\x59\x31\xc0\x31\xdb\x31\xd2\x8d\x40\x04\x43\xb2\x12"
"\xcd\x80\x31\xc0\x40\x43\xcd\x80\xe8\xe6\xff\xff\xff\x69\x20"
"\x6c\x6f\x76\x65\x20\x73\x74\x65\x70\x68\x61\x6e\x69\x65\x21\x0a";
int main(int argc, char**argv)
{
pid_t cpid;
int shellcode_length;
unsigned long base;
uregstruct *ureg;
if (argc < 2) {
fprintf(stderr, "usage: %s <pid>\n", argv[0]);
return 1;
}
cpid = atoi(argv[1]);
/* attach to process */
attach(cpid);
/* structure to hold the child's register in the parent user space */
ureg = (uregstruct *)malloc(sizeof(uregstruct));
/* fill the struct we just malloc'ed */
ptrace(PTRACE_GETREGS, cpid, NULL, ureg);
/* allocate a stack friendly space for shellcode injection and tell
* our child process about it. */
shellcode_length = strlen(shellcode);
base = ureg->esp - shellcode_length-TMP_STACK_SIZE;
put_into_child(cpid, shellcode, base, shellcode_length);
/* kernel subtracts two bytes in cs:eip after the
* call to PTRACE_DETACH to restart the system call */
ureg->eip = base+2;
/* update cs:eip in the childs memory core */
ptrace(PTRACE_SETREGS, cpid, NULL, ureg);
/* detach from child */
ptrace(PTRACE_DETACH, cpid, NULL, NULL);
/* because we are responsible students of c */
free(ureg);
return 0;
}
void attach(pid_t cpid)
{
ptrace(PTRACE_ATTACH, cpid, NULL, NULL);
waitpid(cpid, NULL, WUNTRACED);
}
void put_into_child(pid_t cpid, void *src, unsigned long dest, int len)
{
int i = 0;
long word;
long *ptr = src;
while (i < len) {
word = *ptr++;
ptrace(PTRACE_POKETEXT, cpid, dest + i, word);
i += 4;
}
}
ok, as you prolly already noticed, injecting occured at shellcode_length+TMP_STACK_SIZE below the esp. the thing is, by using the call instruction, i implicitly also used the push instruction which pushes the instruction pointer into the stack and loads the instuction pointer with the address of the procedure name (argument to call instruction). so if we use instructions in our shellcode that utilizes the stack, we must also leave some space between our injected shellcode and the esp. it somehow resembles the illustration below.
— text segment —
— injected shellcode (esp - shellcode_length - TMP_SIZE) —
— space for stack of shellcode (TMP_SIZE) —
— start of stack (of the real program) (esp) —
bottom of memory
i have yet to verify this also, and it really made my life miserable, whenever ptrace is called with PTRACE_DETACH, the kernel subtracts two bytes from the eip.hence the ureg->eip = base + 2 above.
