Q1. What is the address at which the program starts executing (i.e., the address which corresponds to _start)?
When launching gdb and entering `starti`,
the program starts executing at the address 0x0000000000400078.
The first instruction to execute is ldr x24, [sp].
----------------------------------------------------------------------------------
Q2. Identify all functions in task2. We will define a function as a target of a bl (or blr) instruction. For each function, identify the full range of addresses this function resides at (first address and last address), as well as what this function does.
Function 1 at 0x4001b0:
This function begins at address 0x4001b0 and the last address is at 0x4001f4.
This function essentially branches to many other functions to perform actions on the password, and returning the result here to act on.
For example, this function calls a returning function to count the password characters, and does the compare check here after.
The same is done for checking the password for permitted characters and other conditions.
Those functions will return a register return code, which is then checked here and acted upon (branching or returning somewhere).
Function 2 at 0x40014c:
This function begins at address 0x40014c and the last address is at 0x40015c.
This function iterates through the password characters and returns the number of characters (Condition 1)
Function 3 at 0x400194:
This function begins at address 0x400194 and the last address is at 0x4001ac.
The function iterates through the characters and sums the length with the square of each character's hex value (Condition 4)
Function 4 at 0x400118:
This function begins at address 0x400118 and the last address is at 0x400148.
This function iterates through the characters and checks that it is a permitted character from A-Z or a-z (Condition 2)
Function 5 at 0x400160:
This function begins at address 0x400160 and the last address is at 0x400190.
This function iterates through the characters and checks that the password is a palindrome (Condition 3)
----------------------------------------------------------------------------------
Q3. List all of the conditions the correct password must satisfy. This must be precise; for example, if a check requires that the first letter is W, it's not enough to say that the first letter is checked, you must also specify that this must be W.
1st Condition:
- The password must be 22 characters long
- This instruction: `cmp x29, #0x16` compares the length of the password with 22 (converted to decimal)
- If this condition is not met, the `b.ne 0x4001f8` instruction directs the program to return FAILED
2nd Condition:
- The password must contain characters from A-Z and a-z ONLY
- Reviewing the following snippet, each individual password character is checked
- The subtraction of #0x41 disallows any characters with hex values below 0x41 (where A is 0x41)
- The subtraction of #0x19 indicates that any character from A-Z is appropriate for the password
- The subtraction of #0x7 indicates the 7 characters after A-Z, [ to `, are not permitted
- The subtraction of #0x1a indicates that any character from a-z is appropriate for the password
mov x29, #0x1 // #1 // JUMP13: move number 1 into reg x29
ldrb w25, [x24], #1 // SKIP3: load register byte password (one letter at a time #1) to w25
cbz w25, 0x400148 // if zero, skip to SKIP1 -> DONE
subs w25, w25, #0x41 // subtract number 65 from loaded register byte
b.mi 0x400144 // b.first // branch if negative to SKIP2 -> FAILED
subs w25, w25, #0x19 // subtract number 25 from loaded register byte
b.ls 0x40011c // b.plast // branch if unsigned less than or equal to SKIP3 -> PASSED
subs w25, w25, #0x7 // subtract number 7 from loaded register byte
b.mi 0x400144 // b.first // branch if negative to SKIP2 -> FAILED
subs w25, w25, #0x1a // subtract 26 from loaded register byte
b.mi 0x40011c // b.first // branch if negative to SKIP3 -> PASSED
mov x29, xzr // sp // SKIP2: move sp into reg x29 //FAIL OUTCOME
ret // SKIP1: return to JUMP13 trigger
3rd Condition:
- The password must be a palindrome
- Reviewing the following snippet, the individual password characters are checked (first with last, second with second to last, etc)
- The first character (loaded in w25) is compared for equality with the last character (loaded in w26), else directs program to FAILED
cmp x28, x24 // SKIP6: compare x28 and x24
b.ls 0x400190 // b.plast // branch if unsigned less than or equal to SKIP5
ldrb w25, [x24], #1 // load register byte password (one letter at a time #1) to w25
ldrb w26, [x28, #-1]! // load register byte password (one letter at a time #1) to w26 WTFFFF
cmp w25, w26 // compare w25 and w26
b.eq 0x400174 // b.none // if equal, jump to SKIP6
mov x29, #0x0 // #0 // move 0 into x29
ret // SKIP5: return from JUMP20 trigger
4th Condition:
- The password length (0x16) summed with the squares of each character's hex value must contain b8 in its last 8 bits
- Ex. An appropriate sum could be something like 0x234acb8, 0xb8, or 0xffcb8
- Reviewing the following snippet, the first iteration adds the password length to the square of the first character's hex value
- Subsequent iterations will add the square of each following character to the previous sum
- At last, it will mask all irrelevant characters and compare to ensure the last 8 bits are 0xb8
- This condition was easily handled after filtering from the first 3 conditions, through a desmos calculation and hex-dec conversion
ldrb w25, [x24], #1 // load register byte password (one letter at a time #1) to w25
madd w29, w25, w25, w29 // multiply w25 with w25 and add w29, save in w29
cbnz w25, 0x4001a0 // keep looping through letters until zero-terminated
ret
...
and x29, x29, #0xff // ands to mask x29 reg to keep last 8 bits
cmp x29, #0xb8 // compares x29 with number 184
b.ne 0x4001f8 // b.any // branch to 0x4001f8 if not equal - JUMP10
----------------------------------------------------------------------------------
Q4. Determine as much as you can about the calling convention being used. How is the return address passed? How are function arguments passed? Where is the frame pointer stored? Which register do you know from the code must be caller-saved? Which must be callee-saved?
All functions containing a `ret` statement know where to return to as they are products of a branch with link (bl) statement.
There are multiple instances in the program where the x30(lr) register is changed in order to vary where the function returns.
The frame pointer reg (fp) was not directly referenced, however, its purpose was fullfilled in examples as such:
400160: a9bf63fe stp x30, x24, [sp, #-16]!
400168: a8c163fe ldp x30, x24, [sp], #16
This stp and ldp "pair" occur in various locations in different function calls, allocating and restoring space on the stack.
In this example, the frame pointer stored in x30(lr) is (pre-indexed!) stored in the stack,
and later loaded back into x30(lr) so that the function knows where to return when `ret` is reached.
For reference, the sp is "where local data is" and the fp is "where the last local data is".
Registers x24, w25, w26, x27, x28, x29, x30(lr), and xzr(sp) were primarily modified across functions and passed as arguments to others.
These were the callee-saved registers and were used to store long-lived values preserved across calls, the function arguments.
They contained values referring to the password and its length, individual characters, and other values related to checking conditions.
Registers x0, x1, x2, ..., x8, ... were used to hold temporary values not relevant across function calls.
These were the caller-saved registers and where used to store temporary values not to be preserved across calls.
They contained values related to making syscall writes and exits, and outputting the correct message to the terminal.
----------------------------------------------------------------------------------