Blog

[CS:APP] BombLab Phase2 Walkthrough

Published 5 min read

I'm going to analyze two functions.

phase_2

read_six_numbers

__isoc99_sscanf@plt

Let's analyze them in reverse order.

__isoc99_sscanf@plt

In C, sscanf is a function that reads input from a string into other variables, and I'm guessing it's similar here.

read_six_numbers

Dump of assembler code for function read_six_numbers: 0x0000555555401acc <+0>: sub $0x8,%rsp 0x0000555555401ad0 <+4>: mov %rsi,%rdx 0x0000555555401ad3 <+7>: lea 0x4(%rsi),%rcx 0x0000555555401ad7 <+11>: lea 0x14(%rsi),%rax 0x0000555555401adb <+15>: push %rax 0x0000555555401adc <+16>: lea 0x10(%rsi),%rax 0x0000555555401ae0 <+20>: push %rax 0x0000555555401ae1 <+21>: lea 0xc(%rsi),%r9 0x0000555555401ae5 <+25>: lea 0x8(%rsi),%r8 0x0000555555401ae9 <+29>: lea 0x12f1(%rip),%rsi # 0x555555402de1 0x0000555555401af0 <+36>: mov $0x0,%eax 0x0000555555401af5 <+41>: callq 0x555555400fc0 <__isoc99_sscanf@plt> 0x0000555555401afa <+46>: add $0x10,%rsp 0x0000555555401afe <+50>: cmp $0x5,%eax 0x0000555555401b01 <+53>: jle 0x555555401b08 <read_six_numbers+60> 0x0000555555401b03 <+55>: add $0x8,%rsp 0x0000555555401b07 <+59>: retq 0x0000555555401b08 <+60>: callq 0x555555401a90 <explode_bomb>

<+29>

%rsi is a register commonly used for arguments.

I looked at what value gets passed into this register.

(gdb) x/s 0x555555402de1 0x555555402de1: "%d %d %d %d %d %d"

It was "%d %d %d %d %d %d". So we can think of sscanf as reading 6 integers from the string, separated by spaces.

<+50 ~ 53>

Looking just at the key point, if the value of %eax is less than or equal to 5 it jumps to the bomb, so we have to enter 6 or more numbers. So we can infer that the read_six_number function returns the number of values through %eax.

<main>

0x00005555554011f6 <+108>: callq 0x555555400f00 <puts@plt> 0x00005555554011fb <+113>: callq 0x555555401b0d <read_line> 0x0000555555401200 <+118>: mov %rax,%rdi 0x0000555555401203 <+121>: callq 0x555555401304 <phase_2> 0x0000555555401208 <+126>: callq 0x555555401c51 <phase_defused>

<+113> : Calls the read_line function. We can infer it's a function that reads one line of input.

<+118> : Since it passes a value to %rdi, we can infer the input string is stored in %rdi.

I ran a test, and the guess was right.

<__isoc_sscanf> <read_six_numbers>

Through the analysis so far,

the conclusions I got from testing are these.

  1. The string passed via %rsi lets __isoc99_sscanf decide what format to read the input in. In this case "%d %d %d %d %d %d" was passed, so we can tell it reads 6 integers.
  2. As __isoc99_sscanf runs, the input string is passed from %rdi and, through the sscanf function, stored at %rsp as an integer array.
  3. In read_six_numbers, if the integer array has 5 or fewer elements, the bomb explodes.
  4. read_six_numbers returns the number of integers entered as its return value. More precisely, read_six_numbers doesn't store anything in %rax, so it's the __isoc99_sscanf function that returns the number of integers entered in %rax.

phase_2

Dump of assembler code for function phase_2: => 0x0000555555401304 <+0>: push %rbp 0x0000555555401305 <+1>: push %rbx 0x0000555555401306 <+2>: sub $0x28,%rsp 0x000055555540130a <+6>: mov %fs:0x28,%rax 0x0000555555401313 <+15>: mov %rax,0x18(%rsp) 0x0000555555401318 <+20>: xor %eax,%eax 0x000055555540131a <+22>: mov %rsp,%rsi 0x000055555540131d <+25>: callq 0x555555401acc <read_six_numbers> 0x0000555555401322 <+30>: cmpl $0x1,(%rsp) 0x0000555555401326 <+34>: jne 0x555555401331 <phase_2+45> 0x0000555555401328 <+36>: mov %rsp,%rbx 0x000055555540132b <+39>: lea 0x14(%rbx),%rbp 0x000055555540132f <+43>: jmp 0x555555401341 <phase_2+61> 0x0000555555401331 <+45>: callq 0x555555401a90 <explode_bomb> 0x0000555555401336 <+50>: jmp 0x555555401328 <phase_2+36> 0x0000555555401338 <+52>: add $0x4,%rbx 0x000055555540133c <+56>: cmp %rbp,%rbx 0x000055555540133f <+59>: je 0x555555401351 <phase_2+77> 0x0000555555401341 <+61>: mov (%rbx),%eax 0x0000555555401343 <+63>: add %eax,%eax 0x0000555555401345 <+65>: cmp %eax,0x4(%rbx) 0x0000555555401348 <+68>: je 0x555555401338 <phase_2+52> 0x000055555540134a <+70>: callq 0x555555401a90 <explode_bomb> 0x000055555540134f <+75>: jmp 0x555555401338 <phase_2+52> 0x0000555555401351 <+77>: mov 0x18(%rsp),%rax 0x0000555555401356 <+82>: xor %fs:0x28,%rax 0x000055555540135f <+91>: jne 0x555555401368 <phase_2+100> 0x0000555555401361 <+93>: add $0x28,%rsp 0x0000555555401365 <+97>: pop %rbx 0x0000555555401366 <+98>: pop %rbp 0x0000555555401367 <+99>: retq 0x0000555555401368 <+100>: callq 0x555555400f20 <__stack_chk_fail@plt>

According to what we found analyzing read_six_number above,

the values we entered have the structure shown on the right.

(addresses are examples)

<+30 ~ 34>

(%rsp) is the first number we entered.

At <+30> it compares it with 1, and at <+34> if the first number isn't 1 it goes to <+45>.

0x0000555555401322 <+30>: cmpl $0x1,(%rsp) 0x0000555555401326 <+34>: jne 0x555555401331 <phase_2+45>

If it goes to <+45> the bomb explodes, so the first number has to be 1.

0x0000555555401331 <+45>: callq 0x555555401a90 <explode_bomb>

<+30 ~ 34>

From here on, the code runs with the first number we entered guaranteed to be 1.

<+36>: mov %rsp,%rbx <+39>: lea 0x14(%rbx),%rbp <+43>: jmp 0x555555401341 <phase_2+61>

At <+36> it makes %rbx also hold a pointer to the first number.

At <+39> it stores the address plus 0x14 in %rbp,

so %rbp points to the 6th number we entered.

At <+43> it jumps to <+61>.

<+61 ~ 68>

<+61>: mov (%rbx),%eax <+63>: add %eax,%eax <+65>: cmp %eax,0x4(%rbx) <+68>: je 0x555555401338 <phase_2+52> <+70>: callq 0x555555401a90 <explode_bomb>

<+61> : Copies the first number into %rax.

<+63> : Multiplies the value in %rax by 2.

<+65> : 0x4(%rbx) means the number after the one %rbx points to. So it compares with the second number.

<+68> : If the second number equals the first number times 2, it goes back to <+52>. If not, the bomb explodes at <+70>.

<+52 ~ 61>

0x0000555555401338 <+52>: add $0x4,%rbx 0x000055555540133c <+56>: cmp %rbp,%rbx 0x000055555540133f <+59>: je 0x555555401351 <phase_2+77> 0x0000555555401341 <+61>: mov (%rbx),%eax

<+52> : Adds 0x04 to %rbx so it points to the next element.

<+56> : Compares the pointer to the last element with the pointer to the second element.

<+59> : If the address equals the last element's, it jumps to <+77>.

<+61> : Otherwise, from <+61> until %rbx equals %rbp, that is, %rax again has to hold %rbx times 2, and it repeats with %rbx pointing to the next element until it reaches the last element.

<+77 ~ 99>

0x0000555555401351 <+77>: mov 0x18(%rsp),%rax 0x0000555555401356 <+82>: xor %fs:0x28,%rax 0x000055555540135f <+91>: jne 0x555555401368 <phase_2+100> 0x0000555555401361 <+93>: add $0x28,%rsp 0x0000555555401365 <+97>: pop %rbx 0x0000555555401366 <+98>: pop %rbp 0x0000555555401367 <+99>: retq

If you've made it this far, the numbers you enter have to satisfy the following formula.

a[i+1] = a[i] * 2 : 0 <= i <= 5, a[i] = 1

It pops phase_2 off the stack.

So the answer is the following.

1 2 4 8 16 32

Comments

Leaving a comment stores your nickname (or GitHub profile info). Privacy policy

GitHub