[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 수행 직후 register 및 stack 정보를 나타낸다. 그리고 명령어를 보면 $rsi
register에 저장된 주소값을 기준으로 offset을 주어 $rcx
, $rax
, $r9
, $r8
, stack에 값을 전달하는 것을 볼 수 있다.
그리고 <+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
수행 직후 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!