Reverse Engineering,  Series

[Bomb Lab] Phase 2

Phase 1과 같이 phase 2로 disassembly 해보면 다음과 같은 코드를 볼 수 있다.

(gdb) disassemble phase_2
Dump of assembler code for function phase_2:
   0x00005555555555cb <+0>:     endbr64
   0x00005555555555cf <+4>:     push   %rbp
   0x00005555555555d0 <+5>:     push   %rbx
   0x00005555555555d1 <+6>:     sub    $0x28,%rsp
   0x00005555555555d5 <+10>:    mov    %fs:0x28,%rax
   0x00005555555555de <+19>:    mov    %rax,0x18(%rsp)
   0x00005555555555e3 <+24>:    xor    %eax,%eax
   0x00005555555555e5 <+26>:    mov    %rsp,%rsi
   0x00005555555555e8 <+29>:    callq  0x555555555c14 <read_six_numbers>
   0x00005555555555ed <+34>:    cmpl   $0x1,(%rsp)
   0x00005555555555f1 <+38>:    jne    0x5555555555fd <phase_2+50>
   0x00005555555555f3 <+40>:    mov    %rsp,%rbx
   0x00005555555555f6 <+43>:    lea    0x14(%rsp),%rbp
   0x00005555555555fb <+48>:    jmp    0x555555555612 <phase_2+71>
   0x00005555555555fd <+50>:    callq  0x555555555be8 <explode_bomb>
   0x0000555555555602 <+55>:    jmp    0x5555555555f3 <phase_2+40>
   0x0000555555555604 <+57>:    callq  0x555555555be8 <explode_bomb>
   0x0000555555555609 <+62>:    add    $0x4,%rbx
   0x000055555555560d <+66>:    cmp    %rbp,%rbx
   0x0000555555555610 <+69>:    je     0x55555555561d <phase_2+82>
   0x0000555555555612 <+71>:    mov    (%rbx),%eax
   0x0000555555555614 <+73>:    add    %eax,%eax
   0x0000555555555616 <+75>:    cmp    %eax,0x4(%rbx)
   0x0000555555555619 <+78>:    je     0x555555555609 <phase_2+62>
   0x000055555555561b <+80>:    jmp    0x555555555604 <phase_2+57>
   0x000055555555561d <+82>:    mov    0x18(%rsp),%rax
   0x0000555555555622 <+87>:    xor    %fs:0x28,%rax
   0x000055555555562b <+96>:    jne    0x555555555634 <phase_2+105>
   0x000055555555562d <+98>:    add    $0x28,%rsp
   0x0000555555555631 <+102>:   pop    %rbx
   0x0000555555555632 <+103>:   pop    %rbp
   0x0000555555555633 <+104>:   retq
   0x0000555555555634 <+105>:   callq  0x555555555220 <__stack_chk_fail@plt>
End of assembler dump.

phase_2 function을 보면 수행 직후 여러 register 처리들을 해주고 난 뒤 <+29>: callq 0x555555555c14 <read_six_numbers>를 통해 read_six_numbers 함수로 jump 하는 것을 볼 수 있다.

(gdb) disassemble read_six_numbers
Dump of assembler code for function read_six_numbers:
   0x0000555555555c14 <+0>:     endbr64
   0x0000555555555c18 <+4>:     sub    $0x8,%rsp
   0x0000555555555c1c <+8>:     mov    %rsi,%rdx
   0x0000555555555c1f <+11>:    lea    0x4(%rsi),%rcx
   0x0000555555555c23 <+15>:    lea    0x14(%rsi),%rax
   0x0000555555555c27 <+19>:    push   %rax
   0x0000555555555c28 <+20>:    lea    0x10(%rsi),%rax
   0x0000555555555c2c <+24>:    push   %rax
   0x0000555555555c2d <+25>:    lea    0xc(%rsi),%r9
   0x0000555555555c31 <+29>:    lea    0x8(%rsi),%r8
   0x0000555555555c35 <+33>:    lea    0x16c7(%rip),%rsi        # 0x555555557303
   0x0000555555555c3c <+40>:    mov    $0x0,%eax
   0x0000555555555c41 <+45>:    callq  0x5555555552c0 <__isoc99_sscanf@plt>
   0x0000555555555c46 <+50>:    add    $0x10,%rsp
   0x0000555555555c4a <+54>:    cmp    $0x5,%eax
   0x0000555555555c4d <+57>:    jle    0x555555555c54 <read_six_numbers+64>
   0x0000555555555c4f <+59>:    add    $0x8,%rsp
   0x0000555555555c53 <+63>:    retq
   0x0000555555555c54 <+64>:    callq  0x555555555be8 <explode_bomb>
End of assembler dump.

함수 이름을 통해 6개의 숫자를 입력받는 것을 유추 할 수 있다.

read_six_numbers 수행 직후 stack 및 register information

위 그림은 read_six_numbers 수행 직후 register 및 stack 정보를 나타낸다. 그리고 명령어를 보면 $rsi register에 저장된 주소값을 기준으로 offset을 주어 $rcx, $rax, $r9, $r8, stack에 값을 전달하는 것을 볼 수 있다.

read_six_numbers+33 까지 수행 직후 stack 및 register information

그리고 <+33>까지 수행한 결과는 위와 같다. 이제 <+45>에서 어떤 function call이 일어나고 난 뒤 <+57>에서 conditional branch instruction을 수행하고나면 jle (jump less or equal)가 맞을 경우 <+64>로 이동해 explode_bomb를 호출해서 폭탄이 터지는 것이 예상된다. 그렇다면 $rsi에 저장된 메모리에 저장된 값을 보면 다음과 같다.

(gdb) x/xw $rsi
0x555555557303: 0x25206425
0x555555557307: 0x64252064
0x55555555730b: 0x20642520
0x55555555730f: 0x25206425
0x555555557313: 0x72450064

이렇게 보면 잘 이해가 가지 않지만 string format으로 표시하면 이해하기 쉽다.

(gdb) x/s $rsi
0x555555557303: "%d %d %d %d %d %d"

Hexa format으로 된 것을 다시 보면 x86은 big endian 기준으로 동작하기에 0x25, 0x64, 0x20이 상위 address부터 쓰여진 것을 볼 수 있다. 이는 %d 를 의미하는 ASCII code value다.

만약 임시로 input을 1 2 3 4 5 6으로 넣고 돌렸을 때 <+45> 수행 직후 $rax의 값은 6으로 나왔다. 따라서 __isoc99_sscanf함수를 수행하고 나면 argument 수를 return 해주고 해당 결과 ($eax)를 0x5와 비교해서 5개 이하면 fail 하는 동작을 나타낸다고 분석된다.

read_six_numbers 수행 직후 register 및 stack information

이제 read_six_numbers 수행 직후 register 및 stack information을 보면 어느정도 감이 온다. 아까 read_six_numbers 수행 직후 stack의 가장 높은 address (0x7fffffffe398)에 저장된 값은 return address이였고 그 위 주소에 우리가 입력한 인자들이 저장되어 있었다.

<phase_2+38> 명령어를 분석해보면 첫 인자의 값이 0x1이여야 하며 <phase_2+71>를 통해 $eax에 첫 인자의 값인 0x1을 저장한다. <phase_2+73>에서 본인 자신을 더하는, 즉 x+x 연산을 수행하고 <phase_2+75>에서 $rbx (0x1이 저장된 stack address)를 기준으로 다음 인자 값과 비교한다. 만약 동일한 경우 <phase_2+62>로 이동해 $rbx의 값, 즉 stack address를 하나 올려 반복적으로 함수의 인자를 읽어 드리도록 동작한다.

따라서 위 폭탄을 해결하기 위한 방법은 1부터 2를 곱하는 값을 6번 입력해주면 된다. 답은 1 2 4 8 16 32로 예상되며 입력한 결과는 다음과 같다.

$ ./bomb solution.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!

Leave a Reply

Your email address will not be published. Required fields are marked *