두 가지 함수를 분석하려고 합니다.
phase_2
read_six_numbers
__isoc99_sscanf@plt
역순으로 분석해보겠습니다.
__isoc99_sscanf@plt
C언어에서 sscanf는 문자열에서 다른 변수로 입력을 받아들이는 역할을 하는 함수이고, 여기서도 비슷할거라 추측합니다.
read_six_numbers
Dump of assembler code for function read_six_numbers:
0x0000555555401acc <+0>: sub $0x8,%rsp
0x0000555555401ad0 <+4>: mov %rsi,%rdx
0x0000555555401ad3 <+7>: lea 0x4(%rsi),%rcx
0x0000555555401ad7 <+11>: lea 0x14(%rsi),%rax
0x0000555555401adb <+15>: push %rax
0x0000555555401adc <+16>: lea 0x10(%rsi),%rax
0x0000555555401ae0 <+20>: push %rax
0x0000555555401ae1 <+21>: lea 0xc(%rsi),%r9
0x0000555555401ae5 <+25>: lea 0x8(%rsi),%r8
0x0000555555401ae9 <+29>: lea 0x12f1(%rip),%rsi # 0x555555402de1
0x0000555555401af0 <+36>: mov $0x0,%eax
0x0000555555401af5 <+41>: callq 0x555555400fc0 <__isoc99_sscanf@plt>
0x0000555555401afa <+46>: add $0x10,%rsp
0x0000555555401afe <+50>: cmp $0x5,%eax
0x0000555555401b01 <+53>: jle 0x555555401b08 <read_six_numbers+60>
0x0000555555401b03 <+55>: add $0x8,%rsp
0x0000555555401b07 <+59>: retq
0x0000555555401b08 <+60>: callq 0x555555401a90 <explode_bomb>
<+29>
%rsi는 인자로 자주 쓰이는 레지스터입니다.
이 레지스터에 어떤 값이 전달되는지 관찰해보았습니다.
(gdb) x/s 0x555555402de1
0x555555402de1: "%d %d %d %d %d %d"
"%d %d %d %d %d %d" 였습니다. 즉, sscanf에서 문자열에서 6개의 정수로 이루어진 정수 띄어쓰기 단위로 읽어들인다고 생각할 수 있습니다.
<+50 ~ 53>
포인트만 관찰해보면, 에서 %eax의 값이 5보다 작거나 같으면 폭탄으로 점프하기 때문에 6개 이상의 숫자를 입력해야합니다. 즉 read_six_number 함수에서 %eax를 통해 값의 개수를 반환한다고 추론할 수 있습니다.
<main>
0x00005555554011f6 <+108>: callq 0x555555400f00 <puts@plt>
0x00005555554011fb <+113>: callq 0x555555401b0d <read_line>
0x0000555555401200 <+118>: mov %rax,%rdi
0x0000555555401203 <+121>: callq 0x555555401304 <phase_2>
0x0000555555401208 <+126>: callq 0x555555401c51 <phase_defused>
<+113> : read_line 함수를 호출합니다. 한 줄 입력받는 함수라고 추론할 수 있습니다.
<+118> : %rdi 에게 값을 넘기는것을 보아 %rdi에 입력받은 문자열이 저장된다고 추론해볼 수 있습니다.
테스트를 진행해보니, 예상이 맞았습니다.
<__isoc_sscanf> <read_six_numbers>
지금까지의 분석을 통해
테스트로 얻은 결론은 이렇습니다.
- %rsi에서 전달한 문자열을 통해 __isoc99_sscanf에서 어떤 형식으로 입력을 받을지 결정할 수 있습니다. 지금 경우에는 "%d %d %d %d %d %d" 가 전달되었으므로 6개의 정수를 읽는다고 판단할 수 있습니다.
- 입력한 문자열이 __isoc99_sscanf 를 진행하면서 %rdi에서 전달되어 sscanf 함수를 통해 %rsp에 정수 배열형태로 저장됩니다.
- read_six_numbers에서 정수 배열 개수가 5개 이하라면 폭탄을 터뜨립니다.
- read_six_numbers는 반환값으로 입력한 정수의 개수를 반환한다. 더 자세히 말하면 read_six_numbers는가 %rax에 저장하지 않으므로, __isoc99_sscanf 함수가 %rax에 입력한 정수의 개수를 반환합니다.
phase_2
Dump of assembler code for function phase_2:
=> 0x0000555555401304 <+0>: push %rbp
0x0000555555401305 <+1>: push %rbx
0x0000555555401306 <+2>: sub $0x28,%rsp
0x000055555540130a <+6>: mov %fs:0x28,%rax
0x0000555555401313 <+15>: mov %rax,0x18(%rsp)
0x0000555555401318 <+20>: xor %eax,%eax
0x000055555540131a <+22>: mov %rsp,%rsi
0x000055555540131d <+25>: callq 0x555555401acc <read_six_numbers>
0x0000555555401322 <+30>: cmpl $0x1,(%rsp)
0x0000555555401326 <+34>: jne 0x555555401331 <phase_2+45>
0x0000555555401328 <+36>: mov %rsp,%rbx
0x000055555540132b <+39>: lea 0x14(%rbx),%rbp
0x000055555540132f <+43>: jmp 0x555555401341 <phase_2+61>
0x0000555555401331 <+45>: callq 0x555555401a90 <explode_bomb>
0x0000555555401336 <+50>: jmp 0x555555401328 <phase_2+36>
0x0000555555401338 <+52>: add $0x4,%rbx
0x000055555540133c <+56>: cmp %rbp,%rbx
0x000055555540133f <+59>: je 0x555555401351 <phase_2+77>
0x0000555555401341 <+61>: mov (%rbx),%eax
0x0000555555401343 <+63>: add %eax,%eax
0x0000555555401345 <+65>: cmp %eax,0x4(%rbx)
0x0000555555401348 <+68>: je 0x555555401338 <phase_2+52>
0x000055555540134a <+70>: callq 0x555555401a90 <explode_bomb>
0x000055555540134f <+75>: jmp 0x555555401338 <phase_2+52>
0x0000555555401351 <+77>: mov 0x18(%rsp),%rax
0x0000555555401356 <+82>: xor %fs:0x28,%rax
0x000055555540135f <+91>: jne 0x555555401368 <phase_2+100>
0x0000555555401361 <+93>: add $0x28,%rsp
0x0000555555401365 <+97>: pop %rbx
0x0000555555401366 <+98>: pop %rbp
0x0000555555401367 <+99>: retq
0x0000555555401368 <+100>: callq 0x555555400f20 <__stack_chk_fail@plt>
위에서 read_six_number에서 분석한 결과에 따르면,
현재 우리가 입력한 값은 오른쪽과 같은 구조를 가지게 됩니다.
(주소값은 예시)
<+30 ~ 34>
(%rsp)는 우리가 입력한 첫번째 숫자입니다.
<+30> 에서 1과 비교 후 <+34>에서 첫번째 숫자가 1이 아니라면 <+45>로 넘어가게 됩니다.
0x0000555555401322 <+30>: cmpl $0x1,(%rsp)
0x0000555555401326 <+34>: jne 0x555555401331 <phase_2+45>
<+45>로 넘어가면 폭탄이 터지므로 첫번째 숫자는 1이어야 합니다.
0x0000555555401331 <+45>: callq 0x555555401a90 <explode_bomb>
<+30 ~ 34>
여기서부터는 입력한 첫번째 숫자가 1임을 보장된 상태로 코드를 진행합니다.
<+36>: mov %rsp,%rbx
<+39>: lea 0x14(%rbx),%rbp
<+43>: jmp 0x555555401341 <phase_2+61>
<+36>에서 %rbx도 첫번째 숫자를 가리키는 포인터를 가지도록합니다.
<+39>에서 0x14를 더한 주소값을 %rbp에 저장하므로,
%rbp는 우리가 입력한 6번째 숫자를 가리키게 됩니다.
<+43>에서 <+61>로 점프합니다.
<+61 ~ 68>
<+61>: mov (%rbx),%eax
<+63>: add %eax,%eax
<+65>: cmp %eax,0x4(%rbx)
<+68>: je 0x555555401338 <phase_2+52>
<+70>: callq 0x555555401a90 <explode_bomb>
<+61> : %rax에 첫번째 숫자를 복사합니다.
<+63> : %rax의 값에 2를 곱합니다.
<+65> : 0x4(%rbx)는 %rbx가 가리키는 숫자의 다음 숫자를 의미합니다. 즉, 두번째 숫자와 비교합니다.
<+68> : 두번째 숫자와 첫번째 숫자에 2를 곱한값이 같다면 <+52>로 되돌아갑니다. 다르다면 <+70>에서 폭탄이 터집니다.
<+52 ~ 61>
0x0000555555401338 <+52>: add $0x4,%rbx
0x000055555540133c <+56>: cmp %rbp,%rbx
0x000055555540133f <+59>: je 0x555555401351 <phase_2+77>
0x0000555555401341 <+61>: mov (%rbx),%eax
<+52> : %rbx가 0x04를 더해서 다음 원소를 가리키도록 합니다.
<+56> : 마지막 원소를 가리키는 포인터와 두번째 원소를 가리키는 포인터를 비교합니다.
<+59> : 만약 마지막 원소와 주소값이 같다면 <+77>로 점프합니다.
<+61> : 아니라면 <+61>부터 %rbx가 %rbp와 같을 때까지, 즉 %rax에는 다시 %rbx에 2를 곱한값이 저장되어야하고, %rbx가 다음 원소를 가리킴이 마지막 원소에 도달할 때까지 반복합니다.
<+77 ~ 99>
0x0000555555401351 <+77>: mov 0x18(%rsp),%rax
0x0000555555401356 <+82>: xor %fs:0x28,%rax
0x000055555540135f <+91>: jne 0x555555401368 <phase_2+100>
0x0000555555401361 <+93>: add $0x28,%rsp
0x0000555555401365 <+97>: pop %rbx
0x0000555555401366 <+98>: pop %rbp
0x0000555555401367 <+99>: retq
여기까지 도달했다면, 입력해야하는 숫자가 다음과 같은 공식을 만족해야합니다.
a[i+1] = a[i] * 2 : 0 <= i <= 5, a[i] = 1
스택에서 phase_2를 pop합니다.
즉 답은 다음과 같습니다.
1 2 4 8 16 32
'CS:APP' 카테고리의 다른 글
[CS:APP] BombLab Phase6 해설 (0) | 2023.10.20 |
---|---|
[CS:APP] BombLab Phase5 해설 (0) | 2023.10.20 |
[CS:APP] BombLab Phase4 해설 (0) | 2023.10.20 |
[CS:APP] BombLab Phase3 해설 (0) | 2023.10.20 |
[CS:APP] BombLab Phase1 해설 (0) | 2023.10.19 |