pwnable.kr: leg writeup

📅 Nov 2025 🏆 5 points 📂 Toddler's Bottle
ARM assembly reverse engineering PC register

Challenge Description

We get the following hint:

Daddy told me I should study ARM architecture.
But I know Intel architecture and it should be similar.
Why bother to study ARM?

Download : http://pwnable.kr/bin/leg.c
Download : http://pwnable.kr/bin/leg.asm

ssh leg@pwnable.kr -p2222 (pw:guest)

Connect using:

ssh leg@pwnable.kr -p2222

We get a BusyBox machine with the following information:

/ $ uname -a
Linux (none) 3.11.4 #5 Sat Oct 12 00:15:00 EDT 2013 armv5tejl GNU/Linux
/ $ ls
bin      dev      flag     linuxrc  root     sys
boot     etc      leg      proc     sbin     usr
/ $ whoami
busy

We are given the source code for leg, along with its disassembly.

Source Code Analysis

#include <stdio.h>
#include <fcntl.h>
int key1(){
    asm("mov r3, pc\n");
}
int key2(){
    asm(
    "push	{r6}\n"
    "add	r6, pc, $1\n"
    "bx	r6\n"
    ".code   16\n"
    "mov	r3, pc\n"
    "add	r3, $0x4\n"
    "push	{r3}\n"
    "pop	{pc}\n"
    ".code	32\n"
    "pop	{r6}\n"
    );
}
int key3(){
    asm("mov r3, lr\n");
}
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d", &key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}

The goal is to determine the return values of key1, key2, and key3, and provide their sum as input.

General ARM notes

On ARM, the return value of a function is stored in the r0 register. Therefore, for each function we need to track what value ends up in r0.

Analyzing key1()

Disassembly:

...
0x00008cdc <+8>:  mov r3, pc
0x00008ce0 <+12>: mov r0, r3
...

Here, r0 = r3, and r3 is set to the value of pc. In ARM state, pc is the current instruction address + 8. The mov r3, pc instruction is at 0x00008cdc, so:

pc = 0x00008cdc + 8 = 0x00008ce4

Therefore, key1() = 0x00008ce4.

Analyzing key2()

Relevant instructions:

...
0x00008d04 <+20>:	mov	 r3, pc
0x00008d06 <+22>:	adds r3, #4
0x00008d10 <+32>:	mov	 r0, r3

This part runs in Thumb mode, which we can see because the instruction at 0x00008d06 is at 0x00008d04 + 2, indicating 16-bit instructions. In Thumb mode, pc is the current instruction address + 4.

So:

pc = 0x00008d04 + 4 = 0x00008d08
r3 = 0x00008d08 + 4 = 0x00008d0c

Thus key2() = 0x00008d0c.

Analyzing key3()

Disassembly:

0x00008d28 <+8>:  mov r3, lr
0x00008d2c <+12>: mov r0, r3

Here, r0 = r3, and r3 is loaded from lr. On ARM, lr contains the return address of the function. From main, the call to key3 returns to:

   0x00008d80 <+68>:	mov	r3, r0

So key3() = 0x00008d80.

Solution

Adding it all together:

0x00008ce4
+0x00008d0c
+0x00008d80
------------
 0x0001a770 = 108400

Let's try:

/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
daddy_has_lot_of_ARM_muscl3

Success! 🎉