Lolcat Clone in x64 Assembly

Making rainbows happen in your terminal
Reading time: about 6 minutes

Hey there! In this post, I will be coding a clone of the lolcat program. Lolcat is basically cat with colors. It copies its standard input to its standard output, but modifies the content so that it’s colorful. Here’s a screenshot of lolcat.

Please note that this article is a bit long. You can go here for screenshots and the final code.

The original lolcat is written in Ruby, but our rewrite will be in x64 assembly. So let’s begin by creating a makefile in our project directory.

all:
    nasm -f elf64 -o lolcat.o lolcat.asm
    ld -o lolcat lolcat.o

clean:
    rm -f lolcat.o lolcat

test:
    make all
    echo "Testing the lolcat ^_^" | ./lolcat

We will be using nasm as our assembler. The -f elf64 part means that we want a 64-bit ELF binary. After we write our code, we can run make to compile it or make test to test it. That said, let’s get to writing the code.

In this article I will write the assembly code and give an approximate translation to C under it

section .text
    global _start

_start:
    nop
void main() {

}

In assembly, all of our executable code lives in the .text section. We make _start global, because that’s where the assembler will look for the entry point of our program.

The exit function

This program basically does nothing. The nop instruction does not do anything, so this program should exit cleanly right? In reality, if we were to run this program we would be greeted with a Segmentation fault. That is because we need to use the exit syscall. Normally the C standard library does this for us but since we aren’t using it, we need to do it ourselves. So let’s create an exit function that we will call at the end of _start.

exit:
    mov rax, 60
    mov rdi, 0
    syscall
void exit() {
    exit_syscall(0); // Exits with return code 0
}

This code calls the 60th system call with the argument 0, which is basically exit(0); in C. Now our start function should look like this.

_start:
    call exit
void main() {
    exit();
}

If we ran this now, we would get the expected result of nothing happening.

Putting characters on the screen

But doing nothing isn’t very exciting now, is it? Let’s do something more interesting and print something to the console. First of all, we are going to need a character buffer. This is the equivalent of a char in C. We will use this to read and write characters. This will go to a special section called .data. This is where our strings and static variables will live.

section .data
    char_buffer db 0

This is similar to unsigned char char_buffer = 0; in C. To print a character, we will use the 1st system call, which is write.

put_char:
    mov [char_buffer], rdi
    mov rax, 1
    mov rdi, 1
    mov rsi, char_buffer
    mov rdx, 1
    syscall
    ret
void put_char(char character) {
    // The write syscall takes a character buffer and a length
    // Since our function prints only 1 character, that length is always 1
    syscall_write(STDOUT, &character, 1);
}

This function gets the first argument, puts it into char_buffer and calls write(STDOUT, char_buffer, 1);.

By modifying our _start function to this, we can print Test.

_start:
    mov rdi, 'T'
    call put_char
    mov rdi, 'e'
    call put_char
    mov rdi, 's'
    call put_char
    mov rdi, 't'
    call put_char
    call exit
void main() {
    put_char('T');
    put_char('e');
    put_char('s');
    put_char('t');
    exit();
}

Reading characters

In order to have a working cat, we need to read characters and write them back. Right now we can only write characters. Let’s create a function to read characters as well. We will use system call 0 for this, which is read.

read_char:
    mov rax, 0
    mov rdi, 0
    mov rsi, char_buffer
    mov rdx, 1
    syscall
    mov rax, [char_buffer]
    ret
char read_char() {
    char *char_buffer; //*
    // Read one character from the standard input and put it into char_buffer.
    syscall_read(STDIN, &char_buffer, 1);
    return char_buffer;
}

I don’t know if you noticed this, but this function doesn’t do any error handling. We can make it return NULL when there are no more characters to read.

read_char:
    mov rax, 0
    mov rdi, 0
    mov rsi, char_buffer
    mov rdx, 1
    syscall
    cmp eax, 0
    jg .success
    mov rax, 0
    ret
    .success:
        mov rax, [char_buffer]
        ret
char read_char() {
    char *char_buffer; //*
    int result = syscall_read(STDIN, &char_buffer, 1);
    if (result > 0) {
        return char_buffer;
    }else {
        return 0
    }
}

We have a cat

With those additions, we should be able to make a regular, non-colored cat now. Let’s change our start function to this.

_start:
    call read_char
    mov rdi, rax
    cmp rdi, 0
    je .end
    call put_char
    jmp _start
    .end:
        call exit
void main() {
    while (true) {
        char input_character = read_char();
        if (input_character != 0) {
            put_char(input_character);
        }else {
            break;
        }
    }
    exit();
}

Bring in the rainbows

So how can we change the color of the text we’re printing? With ANSI Codes of course. This means we can print ESC[31m to turn our text red or ESC[32;1m to make it bright green. ESC is 27 in decimal.

We will make a function that takes an integer and changes colors to 1,2,3,4,5,6,7 repeating. The reason we don’t want to print black is because we don’t want to have invisible characters on a black terminal.

set_color:
    push rdi
    mov rdi, 27 ;escape
    call put_char
    mov rdi, '['
    call put_char
    mov rdi, '3'
    call put_char
    pop rdi
    mov rax, rdi
    mov rdi, 6
    div rdi
    mov rdi, '0'
    inc rdx
    add rdi, rdx
    call put_char
    mov rdi, ';'
    call put_char
    mov rdi, '1'
    call put_char
    mov rdi, 'm'
    call put_char
    ret
void set_color(int i) {
    put_char(27); // ANSI Escape
    put_char('[');
    put_char('3');

    char color = (i % 6) + 1; // Between 1-7
    put_char('0' + color); // Turn integer to ASCII char

    put_char(';');
    put_char('1'); // Make the colors bright
    put_char('m');
}

Now we can modify our _start function once again in order to use this new color function. We will keep an integer value and increment it every time we print a character. This will change the color of the printed text in a repeated fashion.

_start:
    mov r12, 0
    .loop:
    call read_char
    mov rdi, rax
    cmp rdi, 0
    je .end
    call put_char
    mov rdi, r12
    call set_color
    inc r12
    jmp .loop
    .end:
        mov rdi, 0
        call exit
void main() {
    int color = 0;
    while (true) {
        char input_character = read_char();
        if (input_character != 0) {
            put_char(input_character);
            set_color(i);
            i++;
        }else {
            break;
        }
    }
    exit();
}

Conclusion

And like that, our lolcat is complete. Time to test it. Here are some screenshots.

  • [cowsay lolcat](/img/articles/lolcat-clone-in-x64-assembly/screenshot.png)
  • [hexdump lolcat](/img/articles/lolcat-clone-in-x64-assembly/screenshot_2.png)

You can find the whole asm file here.


Thanks for sticking till the end. That was quite a long article but I hope you liked it.

The following pages link here

Citation

If you find this work useful, please cite it as:
@article{yaltirakli201607lolcatcloneinx64assembly,
  title   = "Lolcat Clone in x64 Assembly",
  author  = "Yaltirakli, Gokberk",
  journal = "gkbrk.com",
  year    = "2016",
  url     = "https://www.gkbrk.com/2016/07/lolcat-clone-in-x64-assembly/"
}
Not using BibTeX? Click here for more citation styles.
IEEE Citation
Gokberk Yaltirakli, "Lolcat Clone in x64 Assembly", July, 2016. [Online]. Available: https://www.gkbrk.com/2016/07/lolcat-clone-in-x64-assembly/. [Accessed Dec. 17, 2024].
APA Style
Yaltirakli, G. (2016, July 27). Lolcat Clone in x64 Assembly. https://www.gkbrk.com/2016/07/lolcat-clone-in-x64-assembly/
Bluebook Style
Gokberk Yaltirakli, Lolcat Clone in x64 Assembly, GKBRK.COM (Jul. 27, 2016), https://www.gkbrk.com/2016/07/lolcat-clone-in-x64-assembly/

Comments

Comment by Golemwire
2019-05-18 at 20:55
Spam probability: 1.071%

Nice article! However, the cowsay & hexdump links are broken. :(

© 2024 Gokberk Yaltirakli