QUOTE(Rude @ 2008-02-13 09:52)

Det man skriver över är de andra registren. Exempel på register är EAX,EDX,ESP,EIP osv. Det du vill komma åt och ändra är som du redan vet EIP(Extended Instruction Pointer).
Nej, det är inte så det går till. Du kan aldrig skriva över själva registren. Du vill skriva över den minnesregion av stacken som håller det
sparade värdet av eip. När ett C-program (t.ex.) anropar en funktion görs detta på IA-32 med instruktionen call som lägger adressen till nästa instruktion i följden på stacken och hoppar till den adress som är given som operand. När funktionen sen är färdig körs instruktionen ret som tar ett dword (i 32-bitars protected mode) som ligger överst på stacken, behandlar det som en adress och sätter eip till det. Skriver man då över denna minnesregion på stacken med sin egen adress kan man manupulera programflödet. Det man oftast vill då är att få eip att sättas till en adress någonstans i sin nop-sled, så att den sedan kan börja köra payloaden. Har du en buffer på 512 byte och de sparade värdena av ebp och eip som följer kan du använda 512+4 byte till en nop-sled och shell code. Därpå följer det värde du vill ha på instruktionspekaren.
Stack overflows kan förhindras ganska lätt om man verkligen vill, ASLR är ett exempel som gör det nästan omöjligt att veta var ens buffer finns i processens minnesrymd. Att göra stacken till en ren data-sektion är en annan, på så sätt kan man inte sätta eip till stacken. Man kan dock fortfarande skriva över det sparade värdet av eip.
EDIT: att logiskt separera kod och data (med hårdvarumodeller som Harvardmodellen) är också möjligt. Där kommer säkerheten som en bieffekt. Att ha separata stackar för returvärden och dataparametrar (som Forth) är ett annat, Forth går till och med ett steg längre och tillåter inte buffrar att läggas på stacken (det är inte en "ren" lösning egentligen, det bryter lite mot abstraktionen att ha en stack öht.) utan där läggs de istället i "data space", vilket mer eller mindre motsvarar arbetsminnet. Forth VM har faktiskt implementeras i hårdvara ett antal gånger, senaste är väl
SEAForth, fast jag vet inte riktigt hur deras lösning är. I mina ögon påminner det mer om en FPGA än en CPU som sådan. Fast processordesign är inte min starka sida.
EDIT2:
ang.
QUOTE
Och hur genererar man shellcoden föresten? är det själva payloaden först i asm
som man gör om till shellcode eller?
Ja, det är nog det vanligaste sättet. Man skriver den kod man vill köra i assembler, kompilerar den till maskinkod och sedan använder man antingen maskinkoden direkt eller packar om den till ett C-fält eller liknande.
ex:
CODE
BITS 32
global _start
section .text
_start:
push dword 0x72617265
push dword 0x67657220
push dword 0x73657274
push dword 0x73657773
xor eax, eax
mov al, 4
xor ebx, ebx
inc ebx
mov ecx, esp
xor edx, edx
mov dl, 16
int 0x80
xor eax,eax
inc eax
xor ebx, ebx
int 0x80
Detta är en giltig shellcode utan nullbytes (man vill inte ha nullbytes om man jobbar mot att utnyttja stränghanteringsfunktioner som strcpy och liknande).
Vi kompilerar den till rå maskinkod med nasm:
QUOTE
nasm -fbin -o sc shellcode.asm
Sen kan man packa om det till en C-array för att underlätta skapandet av en exploit. Detta kan man antingen göra med en hex-editor, eller så skriver man ett program som gör det. Något i stil med:
CODE
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
int ch;
if (argc != 2) {
fprintf(stderr, "bin2arr - convert a flat binary file to a C array\n"
"by swestres\nusage: %s <file>", argv[0]);
return 1;
}
fp = fopen(argv[1], "rb");
if (!fp) {
fprintf(stderr, "error: couldn't open %s for reading\n", argv[1]);
return 1;
}
printf("char shellcode[] = \"");
while ((ch = fgetc(fp)) != EOF) {
printf("\\x%02X", ch);
}
printf("\";\n");
fclose(fp);
return 0;
}
QUOTE
$ gcc -o bin2arr bin2arr.c
$ ./bin2arr sc
char shellcode[] = "\x68\x65\x72\x61\x72\x68\x20\x72\x65\x67\x68\x74\x72\x65\x73\x68\x73\x77\x65\x73\x31\xC0\xB0\x04\x31\xDB\x43\x89\xE1\x31\xD2\xB2\x10\xCD\x80\x31\xC0\x40\x31\xDB\xCD\x80";