ZLA
AST

string-lower

Assembly Crash Course
Post Image
February 2, 2026
Read Time: 5 min

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
  1. call pushes 0x102a, the address of the next instruction, onto the stack.
  2. call jumps to 0x400000, the value stored in rax.

The “ret” instruction is the opposite of “call”.

ret pops 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   0x0000102a

Here, ret will jump to 0x102a.

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

foo is provided at 0x403000. foo takes a single argument as a value and returns a value.

All functions (foo and str_lower) must follow the Linux amd64 calling convention (also known as System V AMD64 ABI): System V AMD64 ABI ⤴

Therefore, your function str_lower should look for src_addr in rdi and place the function return in rax.

An important note is that src_addr is an address in memory (where the string is located) and [src_addr] refers to the byte that exists at src_addr.

Therefore, the function foo accepts 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):

  1. Save src_addr (current value of rdi) somewhere. In this case, on the stack.
  2. Save [src_addr] in a register as we’ll need to place this into rdi as foos argument
  3. Clear out rdi and move [src_addr] into dil (lower byte of rdi, as foo only accepts one byte)
  4. Call foo. Whatever is in rdi (dil in this case) is passed to foo
  5. When foo returns, the output will be stored in rax (al in this case), in accordance to the System V ABI
  6. Retrieve src_addr from the stack and put it back into rdi, giving us access to [src_addr] again
  7. Move our result from al into [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