먼저 main 함수를 살펴보자.
0000000000001489 <main>:
1489: f3 0f 1e fa endbr64
148d: 53 push %rbx
148e: 83 ff 01 cmp $0x1,%edi
1491: 0f 84 f8 00 00 00 je 158f <main+0x106>
1497: 48 89 f3 mov %rsi,%rbx
149a: 83 ff 02 cmp $0x2,%edi
149d: 0f 85 21 01 00 00 jne 15c4 <main+0x13b>
14a3: 48 8b 7e 08 mov 0x8(%rsi),%rdi
14a7: 48 8d 35 e2 20 00 00 lea 0x20e2(%rip),%rsi # 3590 <array.3473+0x410>
14ae: e8 5d fe ff ff callq 1310 <fopen@plt>
14b3: 48 89 05 f6 41 00 00 mov %rax,0x41f6(%rip) # 56b0 <infile>
14ba: 48 85 c0 test %rax,%rax
14bd: 0f 84 df 00 00 00 je 15a2 <main+0x119>
14c3: e8 48 06 00 00 callq 1b10 <initialize_bomb>
14c8: 48 8d 3d b9 1b 00 00 lea 0x1bb9(%rip),%rdi # 3088 <_IO_stdin_used+0x88>
14cf: e8 5c fd ff ff callq 1230 <puts@plt>
초반부에는 폭탄을 설치하기 위한 준비와, 폭탄을 설치하는 것으로 보인다. initialize_bomb이 폭탄을 설치하는 것으로 추정된다. 그리고 이후부터 각 phase마다 다음과 같은 코드가 반복된다.
14d4: 48 8d 3d ed 1b 00 00 lea 0x1bed(%rip),%rdi # 30c8 <_IO_stdin_used+0xc8>
14db: e8 50 fd ff ff callq 1230 <puts@plt>
14e0: e8 42 09 00 00 callq 1e27 <read_line>
14e5: 48 89 c7 mov %rax,%rdi
14e8: e8 fa 00 00 00 callq 15e7 <phase_1>
14ed: e8 7d 0a 00 00 callq 1f6f <phase_defused>
puts@plt와 read_line 함수가 사용자로부터 문자열을 입력받는 것으로 추정되며, 그 값이 %rdi에 저장되어 phase함수의 argument로 전달되는 것을 알 수 있다. 즉, 사용자의 답안이 바로 %rdi이고, 각 phase함수는 정답과 %rdi를 비교할 것이다. phase 함수가 종료되면 phase_defused함수가 호출된다.
다음으로 phase_1함수를 확인한다.
00000000000015e7 <phase_1>:
15e7: f3 0f 1e fa endbr64
15eb: 48 83 ec 08 sub $0x8,%rsp
15ef: 48 8d 35 56 1b 00 00 lea 0x1b56(%rip),%rsi # 314c <_IO_stdin_used+0x14c>
15f6: e8 b5 04 00 00 callq 1ab0 <strings_not_equal>
15fb: 85 c0 test %eax,%eax
15fd: 75 05 jne 1604 <phase_1+0x1d>
15ff: 48 83 c4 08 add $0x8,%rsp
1603: c3 retq
1604: e8 97 07 00 00 callq 1da0 <explode_bomb>
1609: eb f4 jmp 15ff <phase_1+0x18>
가장 먼저 눈에 띄는 것은 explode_bomb 함수이다. 폭탄이 터지는 코드로 추정. explode_bomb함수를 call하지 않도록 해야 한다.
jne 인스트럭션은 ZF가 0이 아닐 때 jump한다. 그리고 test 인스트럭션은 &연산이므로, 이 코드에서는 %eax & %eax가 0, 즉 %eax가 0x00000000일때 jne가 실행되지 않고 함수가 종료되어, phase_defused함수가 호출된다. 만약 %eax가 0이 아니라면 jne 인스트럭션에 의해 1604로 jump해 폭탄이 터지고, jmp 인스트럭션이 15ff로 이동해 함수가 종료된다.
%eax는 strings_not_equal의 return value이다. 따라서 strings_not_equal 함수를 확인한다.
0000000000001ab0 <strings_not_equal>:
1ab0: f3 0f 1e fa endbr64
1ab4: 41 54 push %r12
1ab6: 55 push %rbp
1ab7: 53 push %rbx
1ab8: 48 89 fb mov %rdi,%rbx
1abb: 48 89 f5 mov %rsi,%rbp
1abe: e8 cc ff ff ff callq 1a8f <string_length>
1ac3: 41 89 c4 mov %eax,%r12d
1ac6: 48 89 ef mov %rbp,%rdi
1ac9: e8 c1 ff ff ff callq 1a8f <string_length>
1ace: 89 c2 mov %eax,%edx
1ad0: b8 01 00 00 00 mov $0x1,%eax
1ad5: 41 39 d4 cmp %edx,%r12d
1ad8: 75 31 jne 1b0b <strings_not_equal+0x5b>
1ada: 0f b6 13 movzbl (%rbx),%edx
1add: 84 d2 test %dl,%dl
1adf: 74 1e je 1aff <strings_not_equal+0x4f>
1ae1: b8 00 00 00 00 mov $0x0,%eax
1ae6: 38 54 05 00 cmp %dl,0x0(%rbp,%rax,1)
1aea: 75 1a jne 1b06 <strings_not_equal+0x56>
1aec: 48 83 c0 01 add $0x1,%rax
1af0: 0f b6 14 03 movzbl (%rbx,%rax,1),%edx
1af4: 84 d2 test %dl,%dl
1af6: 75 ee jne 1ae6 <strings_not_equal+0x36>
1af8: b8 00 00 00 00 mov $0x0,%eax
1afd: eb 0c jmp 1b0b <strings_not_equal+0x5b>
1aff: b8 00 00 00 00 mov $0x0,%eax
1b04: eb 05 jmp 1b0b <strings_not_equal+0x5b>
1b06: b8 01 00 00 00 mov $0x1,%eax
1b0b: 5b pop %rbx
1b0c: 5d pop %rbp
1b0d: 41 5c pop %r12
1b0f: c3 retq
이 코드를 자세히 분석하지는 않고, 0이 return되는 경우만 확인해보자. return value는 %rax이고, 1825의 retq에서 return 후 종료된다. 코드를 살펴보면 결국 문자열이 일치하지 않으면 %eax에는 0x1이 저장되어 return되고, 일치하면 0x0이 저장되어 return된다. 결국 정답 문자열인 %rsi를 찾아야한다. 다시 phase_1으로 돌아가자.
00000000000015e7 <phase_1>:
15e7: f3 0f 1e fa endbr64
15eb: 48 83 ec 08 sub $0x8,%rsp
15ef: 48 8d 35 56 1b 00 00 lea 0x1b56(%rip),%rsi # 314c <_IO_stdin_used+0x14c>
15f6: e8 b5 04 00 00 callq 1ab0 <strings_not_equal>
15fb: 85 c0 test %eax,%eax
15fd: 75 05 jne 1604 <phase_1+0x1d>
15ff: 48 83 c4 08 add $0x8,%rsp
1603: c3 retq
1604: e8 97 07 00 00 callq 1da0 <explode_bomb>
1609: eb f4 jmp 15ff <phase_1+0x18>
%rsi에는 (%rip의 주소) + 0x1b56이 저장된다. %rip는 다음 명령어의 주소를 의미한다. 즉 %rsi는 다음 명령어인 15f6으로부터 0x1b56만큼 떨어진 주소를 의미한다. 0x15f6 + 0x1b56 = 0x314c, 따라서 %rsi는 0x315c이고 주석에 이 값이 표시되어있다. 읽기 전용(read-only) 데이터 .rodata 파일을 확인해본다.
3130 6f742074 68617420 6f6e652e 20205472 ot that one. Tr
3140 79207468 6973206f 6e652e00 576f7721 y this one..Wow!
3150 20427261 7a696c20 69732062 69672e00 Brazil is big..
3160 c1e5ffff 6be5ffff 8be5ffff 92e5ffff ....k...........
0x314c는 3140행의 12번째 바이트이므로 79 20 74 68 69 73 20 6f 6e 65 2e 00 57 6f 77 21, 314c에 해당하는 부분은 57로, Wow! Brazil is big.이 들어있다. ASCII 코드로 2e는 마침표(.)를, 00은 NULL 문자(\0)을 의미하므로 \0을 기준으로 저장된 문자열이 어디까지인지 구분된다.
gdb를 이용해서도 확인해본다. 이미 사용자의 입력이 %rdi, 정답이 %rsi임을 알고 있으니 이를 확인하면 된다.
<user_id>@studentLab:~/bomb4$ gdb ./bomb
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./bomb...done.
(gdb)
gdb 명령어로 디버깅을 시작한다. 그리고 다음과 같이 브레이크 포인트를 설정한다.
(gdb) break phase_1
Breakpoint 4 at 0x5555555555e7
(gdb) break explode_bomb
Breakpoint 5 at 0x555555555da0
(gdb) break strings_not_equal
Breakpoint 6 at 0x555555555ab0
(gdb) info breakpoint
Num Type Disp Enb Address What
4 breakpoint keep y 0x00005555555555e7 <phase_1>
5 breakpoint keep y 0x0000555555555da0 <explode_bomb>
6 breakpoint keep y 0x0000555555555ab0 <strings_not_equal>
phase_1과 strings_not_equal 함수의 시작에 breakpoint를 걸어주었다. 그리고 폭탄이 터지면 점수가 1/2로 감점되므로 explode_bomb에도 breakpoint를 걸어 터지지 않도록 해준다. run 명령어로 프로그램을 실행한다.
(gdb) run
Starting program: /home/origami0352/bomb4/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
프로그램이 실행되고 사용자의 입력을 기다린다. answer을 입력해본다.
(gdb) run
Starting program: /home/origami0352/bomb4/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
answer
Breakpoint 4, 0x00005555555555e7 in phase_1 ()
(gdb) si
0x00005555555555eb in phase_1 ()
(gdb) si
0x00005555555555ef in phase_1 ()
(gdb) si
0x00005555555555f6 in phase_1 ()
(gdb) si
Breakpoint 6, 0x0000555555555ab0 in strings_not_equal ()
phase_1함수가 실행하고 멈춘다. si 명령어를 입력해 한줄씩 실행하다보면 strings_not_equal 함수가 호출된다. 여기서 현재 register 정보를 확인한다. 우리가 관심 있는 것은 사용자의 입력을 저장하는 %rdi와 phase_1의 정답 문자열을 가리키는 %rsi이다.
Breakpoint 6, 0x0000555555555ab0 in strings_not_equal ()
(gdb) info register
rax 0x5555555596c0 93824992253632
rbx 0x0 0
rcx 0x6 6
rdx 0x5555555596c0 93824992253632
rsi 0x55555555714c 93824992244044
rdi 0x5555555596c0 93824992253632
rbp 0x555555556c00 0x555555556c00 <__libc_csu_init>
rsp 0x7fffffffe448 0x7fffffffe448
r8 0x55555555bb27 93824992262951
r9 0x7ffff7fe9540 140737354044736
r10 0x55555555a010 93824992256016
r11 0x246 582
r12 0x5555555553a0 93824992236448
r13 0x7fffffffe540 140737488348480
r14 0x0 0
r15 0x0 0
rip 0x555555555ab0 0x555555555ab0 <strings_not_equal>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
---Type <return> to continue, or q <return> to quit---
각 레지스터의 값을 확인할 수 있다. %rdi와 %rsi가 가리키는 문자열이 무엇인지 확인해본다.
(gdb) x/s $rdi
0x5555555596c0 <input_strings>: "answer"
(gdb) x/s $rsi
0x55555555714c: "Wow! Brazil is big."
(gdb)
예상대로 %rdi는 사용자의 입력이고, %rsi의 문자열은 "Wow! Brazil is big."이다. 이는 .rodata에서 확인한 것과 동일하다.
만약 정답이 "Wow! Brazil is big."이 맞다면 strings_not_equal함수는 0을 반환할 것이다. 다음과 같이 strings_not_equal함수의 마지막 부분에 breakpoint를 걸고 확인한다.
(gdb) info breakpoint
Num Type Disp Enb Address What
4 breakpoint keep y 0x00005555555555e7 <phase_1>
breakpoint already hit 1 time
5 breakpoint keep y 0x0000555555555da0 <explode_bomb>
6 breakpoint keep y 0x0000555555555ab0 <strings_not_equal>
breakpoint already hit 1 time
(gdb) break *0x0000555555555b0f
Breakpoint 7 at 0x555555555b0f
(gdb) info breakpoint
Num Type Disp Enb Address What
4 breakpoint keep y 0x00005555555555e7 <phase_1>
breakpoint already hit 1 time
5 breakpoint keep y 0x0000555555555da0 <explode_bomb>
6 breakpoint keep y 0x0000555555555ab0 <strings_not_equal>
breakpoint already hit 1 time
7 breakpoint keep y 0x0000555555555b0f <strings_not_equal+95>
(gdb)
continue 명령을 사용해 다음 breakpoint로 이동한다. 그리고 return값인 %rax를 확인한다.
Breakpoint 7, 0x0000555555555b0f in strings_not_equal ()
(gdb) info register rax
rax 0x1 1
(gdb)
입력한 문자열인 "answer"은 정답 "Wow! Brazil is big."과 다르므로 현재 %rax의 값은 1이다. kill 명령어로 이전 실행을 종료하고 다시 정답을 입력한다.
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) run
Starting program: /home/origami0352/bomb4/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Wow! Brazil is big.
Breakpoint 4, 0x00005555555555e7 in phase_1 ()
(gdb) continue
Continuing.
Breakpoint 6, 0x0000555555555ab0 in strings_not_equal ()
(gdb) x/s $rdi
0x5555555596c0 <input_strings>: "Wow! Brazil is big."
(gdb) x/s $rsi
0x55555555714c: "Wow! Brazil is big."
(gdb) continue
Continuing.
Breakpoint 7, 0x0000555555555b0f in strings_not_equal ()
(gdb) info register rax
rax 0x0 0
(gdb)
정답을 입력한 결과 %rax의 값이 0이 되었다. breakpoint를 제거하고 다시 실행해본다. 단, explode_bomb은 항상 남겨둔다.
(gdb) info breakpoint
Num Type Disp Enb Address What
4 breakpoint keep y 0x00005555555555e7 <phase_1>
breakpoint already hit 1 time
5 breakpoint keep y 0x0000555555555da0 <explode_bomb>
6 breakpoint keep y 0x0000555555555ab0 <strings_not_equal>
breakpoint already hit 1 time
7 breakpoint keep y 0x0000555555555b0f <strings_not_equal+95>
breakpoint already hit 1 time
(gdb) delete 4
(gdb) delete 6
(gdb) delete 7
(gdb) info breakpoint
Num Type Disp Enb Address What
5 breakpoint keep y 0x0000555555555da0 <explode_bomb>
(gdb) run
Starting program: /home/origami0352/bomb4/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Wow! Brazil is big.
Phase 1 defused. How about the next one?
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7af2031 in __GI___libc_read (fd=0, buf=0x55555555bb20, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:27
27 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) quit
phase_1 완료. Ctrl + C로 종료하고 gdb 세션을 종료한다.
'ComputerSystemProgramming' 카테고리의 다른 글
[컴퓨터시스템프로그래밍] 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 |
[컴퓨터시스템프로그래밍] CS:APP Bomb lab: Defusing a Binary Bomb (3) phase_2 (0) | 2024.11.09 |
[컴퓨터시스템프로그래밍] CS:APP Bomb lab: Defusing a Binary Bomb (1) 준비 (0) | 2024.11.07 |
[컴퓨터시스템프로그래밍] CS:APP Data lab bits.c Solution (3) | 2024.10.12 |