[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>
로 이동하게 된다. 우선 나오고 나서 결과를 보면 $eax
가 0x1
이 아닌 경우 폭탄이 터지도록 되어있다.
(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의 모습이다. fun7
함수 내부를 수행하다보니 $rdi
의 값이 내부의 값을 통해서 업데이트가 되는 모습을 <fun7+31>
에서 볼 수 있었다. 따라서 $rdi 값이 바뀔 때마다 출력하면서 다음 표와 같이 정리해봤다.
Name | Memory address | Value | Address 1 (+8) | Address 2 (+16) |
---|---|---|---|---|
n1 | 0x555555559120 | 0x24 | 0x555555559140 | 0x555555559160 |
n21 | 0x555555559140 | 0x8 | 0x5555555591c0 | 0x555555559180 |
n22 | 0x555555559160 | 0x32 | 0x5555555591a0 | 0x5555555591e0 |
n31 | 0x5555555591c0 | 0x6 | 0x555555559030 | 0x555555559090 |
n32 | 0x555555559180 | 0x16 | 0x5555555590b0 | 0x555555559070 |
n33 | 0x5555555591a0 | 0x2d | 0x555555559010 | 0x5555555590d0 |
n34 | 0x5555555591e0 | 0x6b | 0x555555559050 | 0x5555555590f0 |
n41 | 0x555555559030 | 0x1 | 0x0 | 0x0 |
n42 | 0x555555559090 | 0x7 | 0x0 | 0x0 |
n43 | 0x5555555590b0 | 0x14 | 0x0 | 0x0 |
n44 | 0x555555559070 | 0x23 | 0x0 | 0x0 |
n45 | 0x555555559010 | 0x28 | 0x0 | 0x0 |
n46 | 0x5555555590d0 | 0x2f | 0x0 | 0x0 |
n47 | 0x555555559050 | 0x63 | 0x0 | 0x0 |
n48 | 0x5555555590f0 | 0x3e9 | 0x0 | 0x0 |
<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
를 넣으면 최종 폭탄 해체가 완료된다.