twodox
twodox

Reputation: 153

Passing an array (argv) to a syscall in assembly x64

I am learning to create shellcode and having a great time. I mostly understand what to do. I can create asm code that will actually generate the shell. However, I was going to verify my ability by trying another syscall, namely cat .

I am using the method of building the stack from the registers. However, I am running into an issue where I need to pass an array to the 'argv' parameter. This is simple enough when doing a shell, I can just pass the address of the address of the /bin/sh string on the stack. But with cat I need to pass both the name of the function /bin/cat and the argument for cat ie /etc/issue.

I know that the layout for a syscall is:

rax : syscall ID
rdi : arg0
rsi : arg1
rdx : arg2
r10 : arg3
r8 : arg4
r9 : arg5

What I can't decipher is how to pass {"cat","/etc/issue"} into a single register, namely rsi.

My assembly:

global _start
section .text
_start:
;third argument
xor rdx,rdx

;second array member
xor rbx,rbx
push rbx ;null terminator for upcoming string
;push string in 2 parts
mov rbx,6374652f ;python '/etc/issue'[::-1].encode().hex()
push rbx
xor rbx,rbx
mov rbx, 0x65757373692f
push rbx

;first array member
xor rcx,rcx ;null terminator for upcoming string
add rcx,0x746163 ;python 'cat'[::-1].encode().hex()
push rcx

;first argument
xor rdi,rdi
push rdi ;null terminator for upcoming string
add rdi,7461632f6e69622f ;python '/bin/cat'[::-1].encode().hex()
push rdi
mov rdi,rsp

;execve syscall
xor rax,rax
add rax,59

;exit call
xor rdi,rdi
xor rax,rax
add rax,60

It runs but (as expected) aborts when a NULL is passed as argv.

I even tried just writing a C app that creates an array and quits and debugged that but I still didn't really understand what it was doing to create the array.

Upvotes: 1

Views: 764

Answers (1)

You're making this way more complicated than you need to. Here's all you need to do:

    jmp .afterdata
.pathname:
    db '/bin/' ; note lack of null terminator
.argv0:
    db 'cat'
.endargv0:
    db 1 ; we'll have to change the last byte to a null manually
.argv1:
    db '/etc/issue'
.endargv1:
    db 1 ; we'll have to change the last byte to a null manually
.afterdata:
    xor eax, eax ; the null terminator for argv and envp
    push rax
    mov rdx, rsp ; rdx = envp
    dec byte [rel .endargv1] ; change our 1 byte to a null byte
    lea rax, [rel .argv1]
    push rax
    dec byte [rel .endargv0] ; change our 1 byte to a null byte
    lea rax, [rel .argv0]
    push rax
    mov rsi, rsp ; rsi = argv
    lea rdi, [rel .pathname]
    xor eax, eax
    mov al, 59 ; SYS_execve
    syscall
    ; if you wanted to do an exit in case the execve fails, you could here, but for shellcode I don't see the point

You don't need to do any hex-encoding or reversing of strings by hand. You can just stick the strings you need right at the end of your shellcode, and push their addresses onto the stack with rip-relative addressing. The only hoops we jump through are making sure the data is before the instructions that use it, so there's no null bytes there, and having to add in the null terminators on the string at runtime.

Also, you generally want shellcode to be short. Notice how I point into the cat that's part of /bin/cat instead of having it an extra time, and reuse the null at the end of argv for envp.

By the way, if you want to try this as a standalone program, you'll need to pass -Wl,-N and -static to GCC, since the bytes it's modifying will be in the .text section (which is normally read-only). This won't be a problem when you're actually using it as shellcode, since it'll still be writable by whatever means you got it into memory in the first place.

Upvotes: 2

Related Questions