Strcpy Using Stdcall And Int 0x80 Legacy Calling Convention
strcpy using stdcall and int 0x80 legacy calling convention
Intro:
I will be making a binary that
- Implements
strcpy
andstrlen
without a libc - Allocates a single page of dynamic memory
- Copies a string into that memory
See Quick and Dirty Assembly and Pure Binary in Assembly
STDCALL Calling conventions
The calling convention for STDCALL is:
- Arguments are passed R to L onto the stack (e.g., first argument is pushed last)
- The callee cleans the stack
- preserve ebx, esi, edi, ebp, esp
Legacy int 0x80 calling conventions
https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux
syscall | |
---|---|
argument 1 | ebx |
argument 2 | ecx |
argument 3 | edx |
argument 4 | esi |
argument 5 | edi |
argument 6 | ebp |
syscall number | eax |
NOTE: BSD systems also allow the int 0x80 call with pushing values on the stack, and use the above as an alternate calling convention. http://www.int80h.org/bsdasm/#system-calls
Getting memory with mmap2 syscall
void *mmap2(unsigned long addr, unsigned long length,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoffset)
Both the flags
and prot
parameters take constants in C to make our lives easier. We’ll have to look through the Linux source code to find them.
; https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/mman-common.h#L23
%define PROT_READ 0x1
%define PROT_WRITE 0x2
%define MAP_ANONYMOUS 0x20
; https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/mman.h#L17
%define MAP_PRIVATE 0x2
Now we plug our parameters into the appropriate registers and call int 0x80
mov ebx, 0
mov ecx, 4096 ; length < page length (4k) results in a page being allocated anyway
mov edx, PROT_READ
or edx, PROT_WRITE ; R/W permissions
mov esi, MAP_ANONYMOUS
or esi, MAP_PRIVATE ; private and not file backed (just allocate memory, don't make it point to a file)
mov edi, -1 ; no fd
mov ebp, 0x0 ; no offset
mov eax, 192 ; syscall 192 is mmap2, 90 is mmap but fails b/c it wants an argument struct
; https://stackoverflow.com/questions/59923709/problem-trying-to-call-mmap-in-32-bit-i386-assembly#comment105970761_59923709
int 0x80
Calling strcpy
The man page for strcpy
gives use the following function definition
char *strcpy(char *restrict dest, const char *src);
The destination will be the freshly mapped memory. After the call to mmap
the address of that page of memory is in eax
.
To make life easier, I defined a string in the data section for the src
parameter.
section .data
str1: db 'this is only a test', 0
Once again, we plug the parameters into the appropriate registers but this time we use call
. Since only 2 parameters are needed, the others are ignored.
push str1
push eax ; eax is address from mmap
call strcpy
Detour into 32 bit function basics
Prologue
The beginning of a function contains a prologue that does the following
- Save off the previous base pointer (required)
- Set the stack pointer equal to the current base pointer (required)
- Subtract N bytes from the stack pointer for any local variables (if needed)
- Save any preserved registers that this function clobbers (if needed)
; prologue example
push ebp
mov ebp, esp
sub esp, 0xC
push ebx
The stack after the prologue
Using the ebp
as a reference we can access the arguments, saved EIP, and any local variables. Remember the stack grows downward (subtract = using stack space)
; more args here if needed
ebp + 0xC <- argument 2
ebp + 0x8 <- argument 1
ebp + 0x4 <- Saved EIP
ebp + 0x0 <- Current function's base pointer
ebp - 0x4 <- local var 1
ebp - 0x8 <- local var 2
; more local vars here if needed
ebp - N*(0x4) <- local var N & <-- ESP
Epilogue
The end of a function contains a epilogue that reverses the prologue
- Reset any preserved registers (if needed)
- Add N bytes back to stack to “clean” local vars (if needed)
- Set stack pointer back to base pointer (required)
- Reset previous base pointer (required)
; epilogue example from above example
pop ebx
add esp, 0xC
mov esp, ebp
pop ebp
strcpy part 1 - Determining how many bytes to copy
In order to copy the string, we need to know exactly how many bytes the source string is. Calling strlen
will give us that length. We have to also clean the stack afterward.
Note:
STDCALL functions are name-decorated with a leading underscore, followed by an @, and then the number (in bytes) of arguments passed on the stack. This number will always be a multiple of 4, on a 32-bit aligned machine. https://en.wikibooks.org/wiki/X86_Disassembly/Calling_Conventions#STDCALL
_strcpy@8:
push ebp
mov ebp, esp ; End of prologue, no local vars or clobbered regs
xor eax,eax
mov edx, [ebp + 0xC] ; param 2 src
push edx
call strlen
add esp, 0x4
strlen
The man page for strlen
gives us the following function definition
size_t strlen(const char *s);
My implementation for strlen
does the following:
- Clear the
eax
register, to represent the null character\0
- Copy the maximum number of bytes into
edx
andecx
(4k, and this is cheating) - Search until we find a byte in the string that matches
eax
- Subtract
ecx
fromedx
to get the number of bytes - Return 4 since the CALLEE cleans the stack
_strlen@4:
push ebp
mov ebp, esp
push ecx
push edi
cld
xor eax,eax
mov ecx, 0x1000
mov edx, ecx
mov edi, [ebp + 0x8]
b:
repne scasb
je strlen_done
strlen_done:
std
sub edx, ecx
mov eax, edx
pop edi
pop ecx
mov esp, ebp
pop ebp
ret 4
The funky line repne scasb
is shorthand for:
- Compare the contents of the
al
register with the byte at pointed at inrsi
, and then increment or decrement the pointer atesi
(scasb) - Repeat while those bytes are not equal (repne) and
ecx
is not 0
strcpy part 2 - The copy loop
Now that we have the length, we can make a loop that copies the required bytes over.
mov ecx, eax
mov edx, ecx
mov edi, [ebp + 0x8] ; param 1 dst
mov esi, [ebp + 0xC] ; param 2 src
cld
rep movsb
std
mov esp, ebp
pop ebp
ret 8
The line rep movsb
is shorthand for:
- move the byte pointed at by
esi
into the byte pointed at byedi
(movsb) - Repeat while
ecx
is not 0 (rep)
Since I have two 4 byte parameters, I need to issue a ret 8
The final code can be seen https://github.com/mulpdev/practice/blob/master/asm-strcpy-calling-conventions/strcpy-cdecl.asm