secret_phase로 돌입한다. disassemble된 코드를 보면 secret_phase라는 함수를 찾을 수 있다. 이 secret_phase를 진입하는 방법을 찾기 위해 검색해보면, phase_defused 함수에서 secret_phase를 호출하는 부분이 있다.
0000000000001f6f <phase_defused>:
1f6f: f3 0f 1e fa endbr64
1f73: 48 83 ec 78 sub $0x78,%rsp
1f77: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
1f7e: 00 00
1f80: 48 89 44 24 68 mov %rax,0x68(%rsp)
1f85: 31 c0 xor %eax,%eax
1f87: bf 01 00 00 00 mov $0x1,%edi
1f8c: e8 1c fd ff ff callq 1cad <send_msg>
1f91: 83 3d 14 37 00 00 06 cmpl $0x6,0x3714(%rip) # 56ac <num_input_strings>
1f98: 74 19 je 1fb3 <phase_defused+0x44>
우선 phase_defused 함수를 보면, 1f91 줄에 num_input_strings라는 주석이 보인다. 정확히 알 수는 없지만, 6과 비교하는 점과 main 함수에서 각 phase를 통과했을 때 phase_defused 함수를 호출하는 것으로 보아 6개의 phase를 통과했을 때를 의미하지 않을까 싶다. jump 위치 1fb3을 보자.
0000000000001f6f <phase_defused>:
# ...
1fb3: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
1fb8: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
1fbd: 4c 8d 44 24 10 lea 0x10(%rsp),%r8
1fc2: 48 8d 35 8e 14 00 00 lea 0x148e(%rip),%rsi # 3457 <array.3473+0x2d7>
1fc9: 48 8d 3d e0 37 00 00 lea 0x37e0(%rip),%rdi # 57b0 <input_strings+0xf0>
1fd0: b8 00 00 00 00 mov $0x0,%eax
1fd5: e8 16 f3 ff ff callq 12f0 <__isoc99_sscanf@plt>
1fda: 83 f8 03 cmp $0x3,%eax
1fdd: 74 1a je 1ff9 <phase_defused+0x8a>
# ...
이전 phase들과 비슷한 코드가 보인다. 3개의 문자열을 읽어내는 것으로 보인다. sscanf 함수와 인자를 다시 살펴보자.
int sscanf(const char* str, const char* format, ...)
sscanf 함수는 str 문자열에서 format(형식 문자열)이 지정하는 바에 따라 읽어 읽어들인 항목의 개수를 반환한다. 그리고 아래와 같이 레지스터가 배정된다.
1st argument : %rdi (사용자의 입력 문자열)
2nd argument: %rsi (형식 문자열. 0x3457)
즉, 0x3457에 지정된 형식 문자열을 format으로, 사용자의 입력에서 추출한다는 것을 알 수 있다.
3440 723a2049 6e707574 206c696e 6520746f r: Input line to
3450 6f206c6f 6e670025 64202564 20257300 o long.%d %d %s.
3460 44724576 696c0073 74756465 6e744c61 DrEvil.studentLa
.rodata 파일에서 0x3457에 해당하는 것은 %d %d %s이다. 1ff9를 보자.
0000000000001f6f <phase_defused>:
# ...
1ff9: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
1ffe: 48 8d 35 5b 14 00 00 lea 0x145b(%rip),%rsi # 3460 <array.3473+0x2e0>
2005: e8 a6 fa ff ff callq 1ab0 <strings_not_equal>
200a: 85 c0 test %eax,%eax
200c: 75 d1 jne 1fdf <phase_defused+0x70>
200e: 48 8d 3d a3 12 00 00 lea 0x12a3(%rip),%rdi # 32b8 <array.3473+0x138>
2015: e8 16 f2 ff ff callq 1230 <puts@plt>
201a: 48 8d 3d bf 12 00 00 lea 0x12bf(%rip),%rdi # 32e0 <array.3473+0x160>
2021: e8 0a f2 ff ff callq 1230 <puts@plt>
2026: b8 00 00 00 00 mov $0x0,%eax
202b: e8 73 f9 ff ff callq 19a3 <secret_phase>
# ...
strings_not_equal 함수를 호출하고 있다. phase_1에서 확인바와 같이 문자열이 일치하면 strings_not_equal 함수는 0을 반환할 것이고, jne 인스트럭션을 패스한다. 그 결과 쭉 진행하여 secret_phase로 진입함을 알 수 있다.
그럼 어떤 문자열을 비교하는가를 보니, %rsi(0x3460)을 비교한다. 앞서 확인한 .rodata 파일에서 0x3460은 "DrEvil"이다.
문제는 이 문자열을 어디서 입력해야 하는가인데, 전부 뜯어볼 수도 있지만 약간 때려맞춰보자. 문자열을 추출하는 형식 포맷은 %d %d %s였다. 그리고 지난 phase들 중 %d %d는 phase_3과 phase_4에서 입력하였다. 그렇다면 둘 중 한 곳이지 않을까 하며 시도해보자.
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!
Halfway there!
So you got that one. Try this one.
Good work! On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
phase_3의 정답 뒤에 입력했을 때는 아무 변화가 없었던 반면, phase_4에 입력하자 secret_phase가 열렸다. 이제 secret_phase 함수를 확인해보자
00000000000019a3 <secret_phase>:
19a3: f3 0f 1e fa endbr64
19a7: 53 push %rbx
19a8: e8 7a 04 00 00 callq 1e27 <read_line>
19ad: 48 89 c7 mov %rax,%rdi
19b0: ba 0a 00 00 00 mov $0xa,%edx
19b5: be 00 00 00 00 mov $0x0,%esi
19ba: e8 11 f9 ff ff callq 12d0 <strtol@plt>
# ...
문자열을 입력받은 후 strtol 함수를 호출하고 있다.
long strtol(const char *p_str, char **p_end_pos, int radix)
strtol 함수는 숫자 형식의 문자열을 숫자로 변환해 반환한다. 즉, %rdi는 사용자가 입력한 문자열이며, 숫자를 입력받는다.
00000000000019a3 <secret_phase>:
# ...
19ba: e8 11 f9 ff ff callq 12d0 <strtol@plt>
19bf: 48 89 c3 mov %rax,%rbx
19c2: 8d 40 ff lea -0x1(%rax),%eax
19c5: 3d e8 03 00 00 cmp $0x3e8,%eax
19ca: 77 26 /--- ja 19f2 <secret_phase+0x4f>
19cc: 89 de | mov %ebx,%esi
19ce: 48 8d 3d 7b 37 00 00 | lea 0x377b(%rip),%rdi # 5150 <n1>
19d5: e8 88 ff ff ff | callq 1962 <fun7>
19da: 83 f8 07 | cmp $0x7,%eax
19dd: 75 1a /--|--- jne 19f9 <secret_phase+0x56>
19df: 48 8d 3d da 17 00 00 | | lea 0x17da(%rip),%rdi # 31c0 <array.3473+0x40>
19e6: e8 45 f8 ff ff | | callq 1230 <puts@plt>
19eb: e8 7f 05 00 00 | | callq 1f6f <phase_defused>
19f0: 5b | | pop %rbx
19f1: c3 | | retq
19f2: e8 a9 03 00 00 | \--> callq 1da0 <explode_bomb>
19f7: eb d3 | jmp 19cc <secret_phase+0x29>
19f9: e8 a2 03 00 00 \-----> callq 1da0 <explode_bomb>
19fe: eb df jmp 19df <secret_phase+0x3c>
%rbx는 사용자가 입력한 숫자이다. 이를 num이라 하면, num > 0x3e8(1000) + 1 이면 폭발한다. 따라서 num <= 1001이어야 한다.
%esi에 %ebx(num)을 저장하고 %rdi에 0x5150을 저장하고 있다. 그리고 fun7 함수를 호출하는데, return 값이 7이어야 한다. fun7함수를 살펴보자.
0000000000001962 <fun7>:
1962: f3 0f 1e fa endbr64
1966: 48 85 ff test %rdi,%rdi
1969: 74 32 /--- je 199d <fun7+0x3b>
196b: 48 83 ec 08 | sub $0x8,%rsp
196f: 8b 17 | mov (%rdi),%edx
# ...
%rdi가 0이면 -1을 반환한다. %rdi는 포인터이므로 NULL 포인터면 -1을반환한다. %edx에 (%rdi)를 저장한다. %rdi는 0x5150이므로 .data에서 0x5150의 값 4byte를 읽으면
5150 24000000 00000000 70510000 00000000 $.......pQ......
%edx는 0x24(36)이다.
0000000000001962 <fun7>:
# ...
1971: 39 f2 | cmp %esi,%edx
1973: 7f 0c /--|--- jg 1981 <fun7+0x1f>
1975: b8 00 00 00 00 | | mov $0x0,%eax
197a: 75 12 /--|--|--- jne 198e <fun7+0x2c>
197c: 48 83 c4 08 /--|--|--|--> add $0x8,%rsp
1980: c3 | | | | retq
1981: 48 8b 7f 08 | | \--|--> mov 0x8(%rdi),%rdi
1985: e8 d8 ff ff ff | | | callq 1962 <fun7>
198a: 01 c0 | | | add %eax,%eax
198c: eb ee +--|-----|--- jmp 197c <fun7+0x1a>
198e: 48 8b 7f 10 | \-----|--> mov 0x10(%rdi),%rdi
1992: e8 cb ff ff ff | | callq 1962 <fun7>
1997: 8d 44 00 01 | | lea 0x1(%rax,%rax,1),%eax
199b: eb df \--------|--- jmp 197c <fun7+0x1a>
199d: b8 ff ff ff ff \--> mov $0xffffffff,%eax
19a2: c3 retq
%edx와 %esi의 값을 비교해서 num < 36이면 1981로 jump한다. %rdi에 (%rdi + 0x8)을 저장하고 fun7을 recursion, %eax를 2배한다. num > 36이면 198e로 jump하여 %rdi + 0x10을 저장하고 recursion, %eax는 2배 + 1을 해준다. num == 36이면 %eax에 0을 저장하고 fun7을 recursion한다. 구조를 보아하니, phase_4에서 한번 다루었던 이진탐색트리이다.
.data 파일에서 각 노드의 값을 다음과 같이 읽을 수 있다.
5160 90510000 00000000 00000000 00000000 .Q..............
5170 08000000 00000000 f0510000 00000000 .........Q......
5180 b0510000 00000000 00000000 00000000 .Q..............
5190 32000000 00000000 d0510000 00000000 2........Q......
51a0 10520000 00000000 00000000 00000000 .R..............
51b0 16000000 00000000 b0500000 00000000 .........P......
51c0 70500000 00000000 00000000 00000000 pP..............
51d0 2d000000 00000000 10500000 00000000 -........P......
51e0 d0500000 00000000 00000000 00000000 .P..............
51f0 06000000 00000000 30500000 00000000 ........0P......
5200 90500000 00000000 00000000 00000000 .P..............
0x5150 -> 0x24(36)
0x5158 -> *0x5170 -> 0x08(8)
0x5160 -> *0x5190 -> 0x32(50)
이렇게 읽은 값은 다음과 같은 이진탐색트리가 된다.
탐색 결과 return값이 7이 되어야 하고, 오른쪽 탐색시 2배 + 1, 왼쪽 탐색 시 2배, 그리고 num은 1001 이하이다.
return 값이 7이 되려면, 오른쪽 노드를 3번 탐색해야 한다. (0 -> 1 -> 3 -> 7) 따라서 secret_phase의 정답은 1001이다.
secret_phase 클리어. 과제 끝
'ComputerSystemProgramming' 카테고리의 다른 글
[컴퓨터시스템프로그래밍] CS:APP Cache Lab csim.c (2) solution (0) | 2025.01.05 |
---|---|
[컴퓨터시스템프로그래밍] CS:APP Bomb lab: Defusing a Binary Bomb (7) phase_6 (0) | 2024.11.11 |
[컴퓨터시스템프로그래밍] CS:APP Bomb lab: Defusing a Binary Bomb (6) phase_5 (0) | 2024.11.11 |
[컴퓨터시스템프로그래밍] CS:APP Bomb lab: Defusing a Binary Bomb (5) phase_4 (0) | 2024.11.11 |
[컴퓨터시스템프로그래밍] CS:APP Bomb lab: Defusing a Binary Bomb (4) phase_3 (0) | 2024.11.09 |