Reverse Engineering,  Series

[Bomb Lab] Secret Phase

Bomb lab 과제에 내부 secret phase가 있다고 해서 찾아보니 phase_defused 함수를 분석해보면 된다고 힌트가 있었다.

(gdb) disassemble phase_defused
Dump of assembler code for function phase_defused:
   0x0000000000001da1 <+0>:     endbr64
   0x0000000000001da5 <+4>:     sub    $0x78,%rsp
   0x0000000000001da9 <+8>:     mov    %fs:0x28,%rax
   0x0000000000001db2 <+17>:    mov    %rax,0x68(%rsp)
   0x0000000000001db7 <+22>:    xor    %eax,%eax
   0x0000000000001db9 <+24>:    cmpl   $0x6,0x38d0(%rip)        # 0x5690 <num_input_strings>
   0x0000000000001dc0 <+31>:    je     0x1dd7 <phase_defused+54>
   0x0000000000001dc2 <+33>:    mov    0x68(%rsp),%rax
   0x0000000000001dc7 <+38>:    xor    %fs:0x28,%rax
   0x0000000000001dd0 <+47>:    jne    0x1e45 <phase_defused+164>
   0x0000000000001dd2 <+49>:    add    $0x78,%rsp
   0x0000000000001dd6 <+53>:    retq
   0x0000000000001dd7 <+54>:    lea    0xc(%rsp),%rcx
   0x0000000000001ddc <+59>:    lea    0x8(%rsp),%rdx
   0x0000000000001de1 <+64>:    lea    0x10(%rsp),%r8
   0x0000000000001de6 <+69>:    lea    0x156c(%rip),%rsi        # 0x3359
   0x0000000000001ded <+76>:    lea    0x399c(%rip),%rdi        # 0x5790 <input_strings+240>
   0x0000000000001df4 <+83>:    callq  0x12c0 <__isoc99_sscanf@plt>
   0x0000000000001df9 <+88>:    cmp    $0x3,%eax
   0x0000000000001dfc <+91>:    je     0x1e0c <phase_defused+107>
   0x0000000000001dfe <+93>:    lea    0x1493(%rip),%rdi        # 0x3298
   0x0000000000001e05 <+100>:   callq  0x1200 <puts@plt>
   0x0000000000001e0a <+105>:   jmp    0x1dc2 <phase_defused+33>
   0x0000000000001e0c <+107>:   lea    0x10(%rsp),%rdi
   0x0000000000001e11 <+112>:   lea    0x154a(%rip),%rsi        # 0x3362
   0x0000000000001e18 <+119>:   callq  0x1ad4 <strings_not_equal>
   0x0000000000001e1d <+124>:   test   %eax,%eax
   0x0000000000001e1f <+126>:   jne    0x1dfe <phase_defused+93>
   0x0000000000001e21 <+128>:   lea    0x1410(%rip),%rdi        # 0x3238
   0x0000000000001e28 <+135>:   callq  0x1200 <puts@plt>
   0x0000000000001e2d <+140>:   lea    0x142c(%rip),%rdi        # 0x3260
   0x0000000000001e34 <+147>:   callq  0x1200 <puts@plt>
   0x0000000000001e39 <+152>:   mov    $0x0,%eax
   0x0000000000001e3e <+157>:   callq  0x19c7 <secret_phase>
   0x0000000000001e43 <+162>:   jmp    0x1dfe <phase_defused+93>
   0x0000000000001e45 <+164>:   callq  0x1220 <__stack_chk_fail@plt>
End of assembler dump.

<+24>에서 0x38d0(%rip)의 값이 0x6일 경우 다른 곳으로 이동하는 것을 봤다. 이를 분석해보니 각 phase를 통과 할 때마다 값이 증가하는 것을 볼 수 있었기 때문에 일단 phase 6까지 clear가 필요한 것을 알 수 있었다.

그리고 <+69><+76>에서 각각 문자열을 읽어 들이는 것을 볼 수 있었는데 값을 출력해보니 다음과 같았다.

(gdb) x/s 0x555555557359
0x555555557359: "%d %d %s"
(gdb) x/s 0x555555559790
0x555555559790 <input_strings+240>:     "11 1"

위 결과를 토대로 phase 4의 입력에 추가로 문자열을 넣어줘야 하는 것으로 보였다. 일단 임시로 "hello"라는 변수를 넣고 진행해보니 <phase_defused+107>로 이동했고, 여기서 <+112>를 수행해서 나온 $rsi의 위치를 읽어보니 다음과 같은 문자열이 존재했다.

(gdb) x/s 0x555555557362
0x555555557362: "DrEvil"

이렇게 입력 하면 우리가 원하는 secret_phase에 들어 갈 수 있다.

(gdb) disassemble secret_phase
Dump of assembler code for function secret_phase:
   0x00000000000019c7 <+0>:     endbr64
   0x00000000000019cb <+4>:     push   %rbx
   0x00000000000019cc <+5>:     callq  0x1c59 <read_line>
   0x00000000000019d1 <+10>:    mov    %rax,%rdi
   0x00000000000019d4 <+13>:    mov    $0xa,%edx
   0x00000000000019d9 <+18>:    mov    $0x0,%esi
   0x00000000000019de <+23>:    callq  0x12a0 <strtol@plt>
   0x00000000000019e3 <+28>:    mov    %rax,%rbx
   0x00000000000019e6 <+31>:    lea    -0x1(%rax),%eax
   0x00000000000019e9 <+34>:    cmp    $0x3e8,%eax
   0x00000000000019ee <+39>:    ja     0x1a16 <secret_phase+79>
   0x00000000000019f0 <+41>:    mov    %ebx,%esi
   0x00000000000019f2 <+43>:    lea    0x3727(%rip),%rdi        # 0x5120 <n1>
   0x00000000000019f9 <+50>:    callq  0x1986 <fun7>
   0x00000000000019fe <+55>:    cmp    $0x1,%eax
   0x0000000000001a01 <+58>:    jne    0x1a1d <secret_phase+86>
   0x0000000000001a03 <+60>:    lea    0x176e(%rip),%rdi        # 0x3178
   0x0000000000001a0a <+67>:    callq  0x1200 <puts@plt>
   0x0000000000001a0f <+72>:    callq  0x1da1 <phase_defused>
   0x0000000000001a14 <+77>:    pop    %rbx
   0x0000000000001a15 <+78>:    retq
   0x0000000000001a16 <+79>:    callq  0x1be8 <explode_bomb>
   0x0000000000001a1b <+84>:    jmp    0x19f0 <secret_phase+41>
   0x0000000000001a1d <+86>:    callq  0x1be8 <explode_bomb>
   0x0000000000001a22 <+91>:    jmp    0x1a03 <secret_phase+60>
End of assembler dump.

우선 <read_line> 함수를 통해 우리의 입력을 받는 것을 $rax register의 값을 해당 함수 수행 직후 출력해 확인했다. 참고로 테스트 목적으로 7 이라는 숫자를 입력했다.

(gdb) x/s $rax
0x555555559880 <input_strings+480>:     "7"

그리고 <strtol@plt> (string to long)를 거치고 나니 $rax에 입력한 문자열이 정수 값으로 변환됐다. 그리고 <secret_phase+34>를 보면 0x3e8 (1000)보다 $eax (우리가 입력한 값)가 큰 경우 폭탄이 터지도록 되어있다. 따라서 정답은 1000보다 작은 정수다. 그리고 <fun7>로 이동하게 된다. 우선 나오고 나서 결과를 보면 $eax0x1이 아닌 경우 폭탄이 터지도록 되어있다.

(gdb) disassemble fun7
Dump of assembler code for function fun7:
   0x0000555555555986 <+0>:     endbr64
   0x000055555555598a <+4>:     test   %rdi,%rdi
   0x000055555555598d <+7>:     je     0x5555555559c1 <fun7+59>
   0x000055555555598f <+9>:     sub    $0x8,%rsp
   0x0000555555555993 <+13>:    mov    (%rdi),%edx
   0x0000555555555995 <+15>:    cmp    %esi,%edx
   0x0000555555555997 <+17>:    jg     0x5555555559a5 <fun7+31>
   0x0000555555555999 <+19>:    mov    $0x0,%eax
   0x000055555555599e <+24>:    jne    0x5555555559b2 <fun7+44>
   0x00005555555559a0 <+26>:    add    $0x8,%rsp
   0x00005555555559a4 <+30>:    retq
   0x00005555555559a5 <+31>:    mov    0x8(%rdi),%rdi
   0x00005555555559a9 <+35>:    callq  0x555555555986 <fun7>
   0x00005555555559ae <+40>:    add    %eax,%eax
   0x00005555555559b0 <+42>:    jmp    0x5555555559a0 <fun7+26>
   0x00005555555559b2 <+44>:    mov    0x10(%rdi),%rdi
   0x00005555555559b6 <+48>:    callq  0x555555555986 <fun7>
   0x00005555555559bb <+53>:    lea    0x1(%rax,%rax,1),%eax
   0x00005555555559bf <+57>:    jmp    0x5555555559a0 <fun7+26>
   0x00005555555559c1 <+59>:    mov    $0xffffffff,%eax
   0x00005555555559c6 <+64>:    retq
End of assembler dump.
<fun7> 수행 직전 register 및 stack information

위 그림은 <fun7> 수행 직전의 register 및 stack의 모습이다. fun7 함수 내부를 수행하다보니 $rdi의 값이 내부의 값을 통해서 업데이트가 되는 모습을 <fun7+31>에서 볼 수 있었다. 따라서 $rdi 값이 바뀔 때마다 출력하면서 다음 표와 같이 정리해봤다.

NameMemory addressValueAddress 1 (+8)Address 2 (+16)
n10x5555555591200x240x5555555591400x555555559160
n210x5555555591400x80x5555555591c00x555555559180
n220x5555555591600x320x5555555591a00x5555555591e0
n310x5555555591c00x60x5555555590300x555555559090
n320x5555555591800x160x5555555590b00x555555559070
n330x5555555591a00x2d0x5555555590100x5555555590d0
n340x5555555591e00x6b0x5555555590500x5555555590f0
n410x5555555590300x10x00x0
n420x5555555590900x70x00x0
n430x5555555590b00x140x00x0
n440x5555555590700x230x00x0
n450x5555555590100x280x00x0
n460x5555555590d00x2f0x00x0
n470x5555555590500x630x00x0
n480x5555555590f00x3e90x00x0

<fun7> 함수를 분석해보자면 <fun7+15>에서 값을 비교해서 $rdi에 저장된 값보다 작은 경우 address 1로, 작은 경우 address 2로 이동하는 것을 <fun7+31>, <fun7+44>에서 볼 수 있다. 이는 자료 구조에서 binary tree와 같이 어떤 값보다 작은 경우 왼쪽, 큰 경우 오른쪽으로 이동하는 모습과 매우 유사하다. 자료구조를 그림으로 그려보면 다음과 같다.

테스트로 넣은 7의 경우 fun7의 return은 4가 나왔다. n42까지 갔을 때 4가 나왔다는 의미인데 정확하게 규칙을 아직 모르기 때문에 다른 값을 넣어보기로 한다. 입력으로 0x6b (107)를 넣은 결과 3이 나왔다. 여기서 우리는 내부에서 $eax를 만들어 주는 두 명령어를 분석해보면 알 수 있다.

<+40>:    add    %eax,%eax ; left node, x + x
<+53>:    lea    0x1(%rax,%rax,1),%eax ; right node, 0x1 + x + x * 1

$eax의 초기값은 0으로, 목표 node에서 다시 root로 이동하면서 위 연산을 각각 수행하게 되는데, 만약 left node의 경우는 2 * x, right node의 경우는 0x1 + x + x를 recursive하게 수행하게 된다. 만약 입력으로 0x16을 넣게 되면 left node, right node를 지났기 때문에 1, 2의 결과를 내 최종적으로 2가 $eax에 저장된다. 또한 0x6b는 right node를 두번 거치기 때문에 1, 3의 결과를 내 최종적으로는 3이 된다. 우리는 최종 값으로 1이 필요하기 때문에 0x32를 넣으면 최종 폭탄 해체가 완료된다.

Leave a Reply

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