Problem
This problem comes from pwn.college: Assembly Crash Course ⤴.
We will be testing your code multiple times in this level with dynamic values! This means we will be running your code in a variety of random ways to verify that the logic is robust enough to survive normal use.
In this level, you will be working with functions! This will involve manipulating the instruction pointer (rip), as well as doing harder tasks than normal. You may be asked to use the stack to store values or call functions that we provide you.
In previous levels, you implemented a while loop to count the number of consecutive non-zero bytes in a contiguous region of memory.
In this level, you will be provided with a contiguous region of memory again and will loop over each performing a conditional operation till a zero byte is reached. All of which will be contained in a function!
A function is a callable segment of code that does not destroy control flow.
Functions use the instructions “call” and “ret”.
The “call” instruction pushes the memory address of the next instruction onto the stack and then jumps to the value stored in the first argument.
Let’s use the following instructions as an example:
0x1021 mov rax, 0x400000 0x1028 call rax 0x102a mov [rsi], rax
callpushes0x102a, the address of the next instruction, onto the stack.calljumps to0x400000, the value stored inrax.The “ret” instruction is the opposite of “call”.
retpops the top value off of the stack and jumps to it.Let’s use the following instructions and stack as an example:
Stack ADDR VALUE 0x103f mov rax, rdx RSP + 0x8 0xdeadbeef 0x1042 ret RSP + 0x0 0x0000102aHere,
retwill jump to0x102a.Please implement the following logic:
str_lower(src_addr): i = 0 if src_addr != 0: while [src_addr] != 0x00: if [src_addr] <= 0x5a: [src_addr] = foo([src_addr]) i += 1 src_addr += 1 return i
foois provided at0x403000.footakes a single argument as a value and returns a value.All functions (
fooandstr_lower) must follow the Linux amd64 calling convention (also known as System V AMD64 ABI): System V AMD64 ABI ⤴Therefore, your function
str_lowershould look forsrc_addrinrdiand place the function return inrax.An important note is that
src_addris an address in memory (where the string is located) and[src_addr]refers to the byte that exists atsrc_addr.Therefore, the function
fooaccepts a byte as its first argument and returns a byte.
Solution
First, let’s understand what this function is supposed to do. str_lower is meant to turn all characters in a string to lowercase.
It does this by repeatedly calling foo on each byte (one character) of the string, foo being a function that lowercases a single character.
This process continues until we hit a null character (0x00), which indicates the end of the string.
Now, according to the System V ABI, parameters are passed to a function in the order of rdi, rsi, rdx, rcx, r8, and r9, with any extra parameters being placed on the stack.
The thing is, we have two functions: str_lower and foo. Both accept just one parameter, which means both use rdi! We’ll have to do some juggling to make sure everything works correctly.
Here’s the general plan (focusing on the important part):
- Save
src_addr(current value ofrdi) somewhere. In this case, on the stack. - Save
[src_addr]in a register as we’ll need to place this intordiasfoos argument - Clear out
rdiand move[src_addr]intodil(lower byte ofrdi, asfooonly accepts one byte) - Call
foo. Whatever is inrdi(dilin this case) is passed tofoo - When
fooreturns, the output will be stored inrax(alin this case), in accordance to the System V ABI - Retrieve
src_addrfrom the stack and put it back intordi, giving us access to[src_addr]again - Move our result from
alinto[src_addr], replacing the old character with the lowercase one
The code to implement str_lower is below
.intel_syntax noprefix
.global _start
_start:
mov r11, 0x403000
str_lower:
# i = 0
xor rbx, rbx
# If src_addr == 0, jump to end
test rdi, rdi
jz end
while:
# If [src_addr] == 0x00, break while loop
cmp byte ptr [rdi], 0x00
jz end
# If [src_addr] > 0x5a, go to next_loop
cmp byte ptr [rdi], 0x5a
ja next_loop
# Call foo, then increment i (rbx)
push rdi # Save src_addr
mov r12b, byte ptr [rdi] # Save [src_addr]
xor rdi, rdi # Clear rdi
mov dil, r12b # Put [src_addr] as foo's argument
call r11 # Call foo (return value is in rax)
pop rdi # Retrieve src_addr
mov byte ptr [rdi], al # Put result of foo into [src_addr]
add rbx, 1 # i += 1
next_loop:
add rdi, 1
jmp while
end:
# return i
mov rax, rbx
ret