Search code examples
x86nasmsystem-callssend

nasm x86: send system call interpreting payload to send as NULL


I am attempting to write some shell code which will connect to a listener on port 31337 on localhost and send the effective user id of the program for learning purposes.

To make debugging easier, I constructed the following code and assembled with nasm:

BITS 32

section .data

section .bss

section .text
    global _start:

_start:

    ; s = socket(2, 1, 0)
    push BYTE 0x66 ; socketcall is syscall #102 (0x66).
    pop eax
    cdq ; Zero out edx for use as a null DWORD later.
    xor ebx, ebx ; ebx is the type of socketcall.
    inc ebx ; 1 = SYS_SOCKET = socket()
    push edx ; Build arg array: { protocol = 0,
    push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,
    push BYTE 0x2 ; AF_INET = 2 }
    mov ecx, esp ; ecx = ptr to argument array
    int 0x80 ; After syscall, eax has socket file descriptor.
    xchg esi, eax ; Save socket FD in esi for later.

    ; connect(s, [2, 31337, <IP address>], 16)
    push BYTE 0x66 ; socketcall (syscall #102)
    pop eax
    inc ebx ; ebx = 2 (needed for AF_INET)
    push DWORD 0x0100007f ; Build sockaddr struct: IP address = 127.0.0.1
    push WORD 0x697a ; (in reverse order) PORT = 31337
    push WORD bx ; AF_INET = 2
    mov ecx, esp ; ecx = server struct pointer
    push BYTE 16 ; argv: { sizeof(server struct) = 16,
    push ecx ; server struct pointer,
    push esi ; socket file descriptor }
    mov ecx, esp ; ecx = argument array
    inc ebx ; ebx = 3 = SYS_CONNECT = connect()
    int 0x80

    ; geteuid(void)
    push BYTE 0x31 ; call for geteuid (syscall #49)
    pop eax
    int 0x80 ; eax = effective user id
    mov edi, eax ; store euid for later

    ; send(3, euid, 8, 0)
    push BYTE 0x66 ; socketcall (syscall #102)
    pop eax
    xor edx, edx ; creating zero for flags
    push edx 
    push BYTE 8 ; size of data to transmit
    push edi ; euid
    push esi ; file descriptor
    mov ebx, 9 ; ebx = 9 = SYS_SEND = send()
    mov ecx, esp ; argument array
    int 0x80

    ; exit(1)       
    push BYTE 1 ; call for exit
    pop eax
    xor ebx, ebx
    int 0x80

When I run this code, a socket is successfully created and a connection is established to a server I have listening on port 31337. However, my user ID is not sent. When I ran strace, I received the following output:

execve("./connect_back", ["./connect_back"], [/* 18 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(31337), 
sin_addr=inet_addr("127.0.0.1")}, 16) = 0
geteuid()                               = 0
send(3, NULL, 8, 0)                     = -1 EFAULT (Bad address)
_exit(0)                                = ?
+++ exited with 0 +++

It appears that my euid is not being used as an argument. However, when I run gdb on the binary, the program appears to correctly set up the arguments for the send call:

gdb debug

I am new to nasm so I apologize if this is a silly syntax problem. Thank you for your help!


Solution

  • TL;DR: The value NULL you see in STRACE for send is because you are running STRACE as root user and the UID for root on Linux distros is generally 0. In the debugger you see 0x3e8 because you ran the debugger as the unprivileged user with UID=1000 (which is 0x3e8).

    sys_send requires a pointer to data to send, not data. The value 0x0000 and 0x03e8 are being treated as memory addresses even though they are not memory addresses. Both addresses are to memory we don't have read privileges for, thus the result is a -EFAULT (bad address) in the strace output for send


    You are passing the value of UID to send, not a pointer to the data. send takes a pointer to data, not the data itself. This code pushes UID on the stack and then uses the stack address as the pointer to the UID. That pointer to the UID is used to call send:

    BITS 32
    
    section .data
    
    section .bss
    
    section .text
        global _start:
    
    _start:
    
        ; s = socket(2, 1, 0)
        push BYTE 0x66 ; socketcall is syscall #102 (0x66).
        pop eax
        cdq ; Zero out edx for use as a null DWORD later.
        xor ebx, ebx ; ebx is the type of socketcall.
        inc ebx ; 1 = SYS_SOCKET = socket()
        push edx ; Build arg array: { protocol = 0,
        push BYTE 0x1 ; (in reverse) SOCK_STREAM = 1,
        push BYTE 0x2 ; AF_INET = 2 }
        mov ecx, esp ; ecx = ptr to argument array
        int 0x80 ; After syscall, eax has socket file descriptor.
        xchg esi, eax ; Save socket FD in esi for later.
    
        ; connect(s, [2, 31337, <IP address>], 16)
        push BYTE 0x66 ; socketcall (syscall #102)
        pop eax
        inc ebx ; ebx = 2 (needed for AF_INET)
        push DWORD 0x0100007f ; Build sockaddr struct: IP address = 127.0.0.1
        push WORD 0x697a ; (in reverse order) PORT = 31337
        push WORD bx ; AF_INET = 2
        mov ecx, esp ; ecx = server struct pointer
        push BYTE 16 ; argv: { sizeof(server struct) = 16,
        push ecx ; server struct pointer,
        push esi ; socket file descriptor }
        mov ecx, esp ; ecx = argument array
        inc ebx ; ebx = 3 = SYS_CONNECT = connect()
        int 0x80
    
        ; geteuid(void)
        push BYTE 0x31 ; call for geteuid (syscall #49)
        pop eax
        int 0x80 ; eax = effective user id
        push eax     ; Put EAX on the stack
        mov edi, esp ; Get the address (on stack) of the UID
    
        ; send(3, euid, 8, 0)
        push BYTE 0x66 ; socketcall (syscall #102)
        pop eax
        xor edx, edx ; creating zero for flags
        push edx
        push BYTE 4 ; size of data to transmit
        push edi ; euid
        push esi ; file descriptor
        mov ebx, 9 ; ebx = 9 = SYS_SEND = send()
        mov ecx, esp ; argument array
        int 0x80
    
        ; exit(1)
        push BYTE 1 ; call for exit
        pop eax
        xor ebx, ebx
        int 0x80
    

    I send 4 bytes of data (32-bit integer) rather than 8 bytes. The receiver should receive exactly 4 bytes containing the binary value of the UID.

    If you want to send the UID as a printable string then you would have to convert the UID to a string and pass the address of the string to send.