세 가지 함수를 분석하겠습니다.

phase_4

func4

__isoc99_sscanf


__isoc99_sscanf

phase2에서 __isoc99_sscanf 함수에 알아낸 사실들을 정리하면 다음과 같습니다.

  1. 입력한 정수가 %rdi에서 저장된 문자열에서 배열형태로 변환됩니다.
  2. 첫번째 원소를 가리키는 포인터가 %rsp에 저장됩니다.
  3. 입력한 정수의 개수를 %rax에 반환합니다.

따라서 50 100 을 입력했다면 오른쪽의 상황을 가집니다.

 

 


phase_4

Dump of assembler code for function phase_4:
=> 0x0000555555401455 <+0>:	sub    $0x18,%rsp
   0x0000555555401459 <+4>:	mov    %fs:0x28,%rax
   0x0000555555401462 <+13>:	mov    %rax,0x8(%rsp)
   0x0000555555401467 <+18>:	xor    %eax,%eax
   0x0000555555401469 <+20>:	lea    0x4(%rsp),%rcx
   0x000055555540146e <+25>:	mov    %rsp,%rdx
   0x0000555555401471 <+28>:	lea    0x1975(%rip),%rsi        # 0x555555402ded
   0x0000555555401478 <+35>:	callq  0x555555400fc0 <__isoc99_sscanf@plt>
   0x000055555540147d <+40>:	cmp    $0x2,%eax
   0x0000555555401480 <+43>:	jne    0x555555401488 <phase_4+51>
   0x0000555555401482 <+45>:	cmpl   $0xe,(%rsp)
   0x0000555555401486 <+49>:	jbe    0x55555540148d <phase_4+56>
   0x0000555555401488 <+51>:	callq  0x555555401a90 <explode_bomb>
   0x000055555540148d <+56>:	mov    $0xe,%edx
   0x0000555555401492 <+61>:	mov    $0x0,%esi
   0x0000555555401497 <+66>:	mov    (%rsp),%edi
   0x000055555540149a <+69>:	callq  0x555555401421 <func4>
   0x000055555540149f <+74>:	cmp    $0x7,%eax
   0x00005555554014a2 <+77>:	jne    0x5555554014ab <phase_4+86>
   0x00005555554014a4 <+79>:	cmpl   $0x7,0x4(%rsp)
   0x00005555554014a9 <+84>:	je     0x5555554014b0 <phase_4+91>
   0x00005555554014ab <+86>:	callq  0x555555401a90 <explode_bomb>
   0x00005555554014b0 <+91>:	mov    0x8(%rsp),%rax
   0x00005555554014b5 <+96>:	xor    %fs:0x28,%rax
   0x00005555554014be <+105>:	jne    0x5555554014c5 <phase_4+112>
   0x00005555554014c0 <+107>:	add    $0x18,%rsp
   0x00005555554014c4 <+111>:	retq
   0x00005555554014c5 <+112>:	callq  0x555555400f20 <__stack_chk_fail@plt>

<+28>

__isoc99_sscanf 함수의 인자로 %rsi에 "%d %d"를 담아 전달합니다.

(gdb) x/s 0x555555402ded
0x555555402ded:	"%d %d"

<+35>

1. "%d %d"를 전달해서 함수를 호출했으므로 두 개의 정수를 입력해야한다고 판단할 수 있습니다.

2. %rsp에 정수배열의 포인터가 저장됩니다.

3. %rax에 입력한 정수의 개수가 저장됩니다.


<+40 ~ 43>

   0x000055555540147d <+40>:	cmp    $0x2,%eax
   0x0000555555401480 <+43>:	jne    0x555555401488 <phase_4+51>
   0x0000555555401488 <+51>:	callq  0x555555401a90 <explode_bomb>

 

<+40> : 입력한 정수의 개수를 2와 비교합니다.

 

<+43> : 입력한 정수의 개수가 2가 아니라면 <+51>로 점프하고 폭탄이 터지므로, 입력해야하는 정수의 개수는 2개입니다.


<+45 ~ 69>

   0x0000555555401482 <+45>:	cmpl   $0xe,(%rsp)
   0x0000555555401486 <+49>:	jbe    0x55555540148d <phase_4+56>
   0x0000555555401488 <+51>:	callq  0x555555401a90 <explode_bomb>
   0x000055555540148d <+56>:	mov    $0xe,%edx
   0x0000555555401492 <+61>:	mov    $0x0,%esi
   0x0000555555401497 <+66>:	mov    (%rsp),%edi
   0x000055555540149a <+69>:	callq  0x555555401421 <func4>

 

<+45> : (%rsp)는 오른쪽 그림과 같이 입력한 첫번째 정수입니다. 첫번째 정수와 14를 비교합니다.

 

<+49> : unsigned비교이므로 첫번째 정수가 0 미만, 14 초과라면 <+51>로 이동합니다.

 

<+51> : 폭탄이 터집니다. 즉 첫번째 정수는 0 이상 14 이하를 만족해야합니다.

 

<+56> : %edx에 14를 저장합니다.

 

<+61> : %esi에 0을 저장합니다.

 

<+66> : %edi에 첫번째 정수를 저장합니다.

 

<+69> : 바로 func4를 호출하는것으로 보아 %edx, %esi, %edi가 함수의 인자로 작동한다고 추측할 수 있습니다.


<+72 ~ 69>

   0x000055555540149f <+74>:	cmp    $0x7,%eax
   0x00005555554014a2 <+77>:	jne    0x5555554014ab <phase_4+86>
   0x00005555554014a4 <+79>:	cmpl   $0x7,0x4(%rsp)
   0x00005555554014a9 <+84>:	je     0x5555554014b0 <phase_4+91>
   0x00005555554014ab <+86>:	callq  0x555555401a90 <explode_bomb>
   0x00005555554014b0 <+91>:	mov    0x8(%rsp),%rax
   0x00005555554014b5 <+96>:	xor    %fs:0x28,%rax
   0x00005555554014be <+105>:	jne    0x5555554014c5 <phase_4+112>
   0x00005555554014c0 <+107>:	add    $0x18,%rsp
   0x00005555554014c4 <+111>:	retq
   0x00005555554014c5 <+112>:	callq  0x555555400f20 <__stack_chk_fail@plt>

<+74> : func4가 리턴된 후의 R[%eax]는 여전히 2(입력한 정수 개수는 항상 두개여야 함이 보장되었다)거나 func4의 리턴값 둘 중 하나일겁니다. 그 값을 7과 비교합니다.

 

<+77> : 만약 R[%eax]가 7이 아니면 <+86>으로 이동해서 폭탄이 터지므로, func4는 리턴값이 존재하는 함수임을 알 수 있습니다.

그리고 그 func4의 리턴값은 7이 나오도록 인자를 전달해야합니다.

 

<+79> : 0x4(%rsp)는 입력한 두 번째 숫자입니다. 두번째 숫자와 7을 비교합니다.

 

<+84> : 두번째 숫자가 7과 같은지 비교합니다.

 

<+86> : 두번째 숫자가 7이 아니면 폭탄이 터지므로, 두번째 숫자는 7 이어야합니다.

 

<이후> : 일반적인 함수를 마치려는 코드입니다.


func4를 분석하기 전에 우리가 문제를 해결하기위해 필요한 조건들을 정리해보겠습니다.

 

  1. 입력한 첫번째 숫자는 0 이상 14 이하를 만족해야한다.
  2. 입력한 두번째 숫자는 7 이어야 한다.
  3. %edx, %esi, %edi가 인자로 전달되며 그 값들은 다음과 같습니다. R[%edx] = 14, R[%esi] = 0, R[%edi] = 입력한 첫번째 정수
  4. 리턴값이 7이 되도록 인자를 전달해야하는데, R[%edx]와 R[%esi]는 14와 0으로 정해져있습니다. 즉 R[%edi]를 리턴값이 7이 되도록 전달해야합니다. 다시말하면 첫번째 정수를 리턴값이 7이 나오도록 입력해야합니다.

func4

Dump of assembler code for function func4:
   0x0000555555401421 <+0>:	push   %rbx
   0x0000555555401422 <+1>:	mov    %edx,%eax
   0x0000555555401424 <+3>:	sub    %esi,%eax
   0x0000555555401426 <+5>:	mov    %eax,%ebx
   0x0000555555401428 <+7>:	shr    $0x1f,%ebx
   0x000055555540142b <+10>:	add    %eax,%ebx
   0x000055555540142d <+12>:	sar    %ebx
   0x000055555540142f <+14>:	add    %esi,%ebx
   0x0000555555401431 <+16>:	cmp    %edi,%ebx
   0x0000555555401433 <+18>:	jg     0x55555540143d <func4+28>
   0x0000555555401435 <+20>:	cmp    %edi,%ebx
   0x0000555555401437 <+22>:	jl     0x555555401449 <func4+40>
   0x0000555555401439 <+24>:	mov    %ebx,%eax
   0x000055555540143b <+26>:	pop    %rbx
   0x000055555540143c <+27>:	retq
   0x000055555540143d <+28>:	lea    -0x1(%rbx),%edx
   0x0000555555401440 <+31>:	callq  0x555555401421 <func4>
   0x0000555555401445 <+36>:	add    %eax,%ebx
   0x0000555555401447 <+38>:	jmp    0x555555401439 <func4+24>
   0x0000555555401449 <+40>:	lea    0x1(%rbx),%esi
   0x000055555540144c <+43>:	callq  0x555555401421 <func4>
   0x0000555555401451 <+48>:	add    %eax,%ebx
   0x0000555555401453 <+50>:	jmp    0x555555401439 <func4+24>

 

입력한 첫번째 정수를 a(0 <= a <= 14)라고 가정하면,

전달되는 인자는 R[%edx] = 14R[%esi] = 0R[%edi] = a 입니다. 

우리는 a값을 통해서 리턴값이 7이 되도록 해야합니다.

 


<+1 ~ 5>

   0x0000555555401422 <+1>:	mov    %edx,%eax
   0x0000555555401424 <+3>:	sub    %esi,%eax
   0x0000555555401426 <+5>:	mov    %eax,%ebx


<+7 ~ 14>

   0x0000555555401428 <+7>:	shr    $0x1f,%ebx
   0x000055555540142b <+10>:	add    %eax,%ebx
   0x000055555540142d <+12>:	sar    %ebx
   0x000055555540142f <+14>:	add    %esi,%ebx

 

 

 

<+7> : %ebx에 31번 오른쪽으로 shift합니다. 이는 %ebx의 부호를 추출한다고 이해할 수 있습니다.

 

<+10> : %ebx에 %eax를 더합니다.

 

<+12> : %ebx를 부호를 고려한 채 오른쪽으로 한번 shift합니다. 이는 %ebx를 2로 나눈것과 같습니다.

 

<+14> : %ebx에 %edi에 더합니다.

여기까지 계산한 식을 정리하면 오른쪽과 같습니다.

 

 

 


<+16 ~ 27>

   0x0000555555401431 <+16>:	cmp    %edi,%ebx
   0x0000555555401433 <+18>:	jg     0x55555540143d <func4+28>
   0x0000555555401435 <+20>:	cmp    %edi,%ebx
   0x0000555555401437 <+22>:	jl     0x555555401449 <func4+40>
   0x0000555555401439 <+24>:	mov    %ebx,%eax
   0x000055555540143b <+26>:	pop    %rbx
   0x000055555540143c <+27>:	retq

<+16> : R[%ebx]와 R[%edi]를 비교합니다.

 

<+18> : 만약 %ebx가 더 크면 <+28>로 점프합니다.

 

<+20> : 다시 비교합니다.

 

<+22> : 만약 %ebx가 더 작으면 <+40>으로 점프합니다.

 

<+24> : 크지도 않고 작지도 않으니, R[%ebx] == R[%edi] 입니다.  그러면 %ebx를 %eax에 넣어 return합니다.


<+28 ~ 50>

   0x000055555540143d <+28>:	lea    -0x1(%rbx),%edx
   0x0000555555401440 <+31>:	callq  0x555555401421 <func4>
   0x0000555555401445 <+36>:	add    %eax,%ebx
   0x0000555555401447 <+38>:	jmp    0x555555401439 <func4+24>
   0x0000555555401449 <+40>:	lea    0x1(%rbx),%esi
   0x000055555540144c <+43>:	callq  0x555555401421 <func4>
   0x0000555555401451 <+48>:	add    %eax,%ebx
   0x0000555555401453 <+50>:	jmp    0x555555401439 <func4+24>

R[%ebx] > R[%edi]

  • <+28> :  R[%edx] = R[%rbx] - 1 을 진행합니다.
  • <+31> : 코드의 처음으로 돌아가서 재귀호출합니다.

R[%ebx] > R[%edi]

  • <+40> : R[%esi] = R[%rbx] + 1 을 진행합니다.
  • <+34> : 코드의 처음으로 돌아가서 재귀호출합니다.

func4 결론

코드를 잘 보시면 이분탐색을 진행하는 코드임을 알 수 있습니다.

검색 범위는 0부터 14까지인데, %esi는 최소값을 담당하고 %edx를 담당합니다.

검색 대상은 우리가 입력한 첫번째 숫자인 %edi입니다.

 

그리고 %edx와 %esi의 중간 인덱스를 %ebx에 저장해서 그 값을 Target인 %edi와 비교해서 분기합니다.결론적으로 리턴값은 검색을 성공했을 때의 %ebx 입니다.

 

리턴값이 7이어야한다는건, 검색을 성공했을 때의 인덱스가 7이어야한다는겁니다.

또한 검색을 성공했다는건 R[%ebx] == R[%edi] 임을 의미하므로, 검색 대상인 첫번째 숫자가 7이어야함을 의미합니다.

 

인덱스와 그 값이 완전히 똑같은 배열에서 왜 이분탐색을 돌리는지는 의문이지만,

결론적으로 첫번째 숫자가 7, 두번째 숫자가 7이어야하므로 답은 다음과 같습니다.

7 7

'CS:APP' 카테고리의 다른 글

[CS:APP] BombLab Phase6 해설  (0) 2023.10.20
[CS:APP] BombLab Phase5 해설  (0) 2023.10.20
[CS:APP] BombLab Phase3 해설  (0) 2023.10.20
[CS:APP] BombLab Phase2 해설  (0) 2023.10.19
[CS:APP] BombLab Phase1 해설  (0) 2023.10.19

두 가지 함수를 분석하려고 합니다.

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>

 

지금까지의 분석을 통해

테스트로 얻은 결론은 이렇습니다.

 

  1. %rsi에서 전달한 문자열을 통해 __isoc99_sscanf에서 어떤 형식으로 입력을 받을지 결정할 수 있습니다. 지금 경우에는 "%d %d %d %d %d %d" 가 전달되었으므로 6개의 정수를 읽는다고 판단할 수 있습니다.
  2. 입력한 문자열이 __isoc99_sscanf 를 진행하면서 %rdi에서 전달되어 sscanf 함수를 통해 %rsp에 정수 배열형태로 저장됩니다.
  3. read_six_numbers에서 정수 배열 개수가 5개 이하라면 폭탄을 터뜨립니다.
  4. 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

세 가지 함수를 분석하려고 합니다.

phase_1

strings_no_equal

string_length

 


string_length

Dump of assembler code for function string_length:
   0x000000000000176f <+0>:	cmpb   $0x0,(%rdi)
   0x0000000000001772 <+3>:	je     0x1786 <string_length+23>
   0x0000000000001774 <+5>:	mov    %rdi,%rdx
   0x0000000000001777 <+8>:	add    $0x1,%rdx
   0x000000000000177b <+12>:	mov    %edx,%eax
   0x000000000000177d <+14>:	sub    %edi,%eax
   0x000000000000177f <+16>:	cmpb   $0x0,(%rdx)
   0x0000000000001782 <+19>:	jne    0x1777 <string_length+8>
   0x0000000000001784 <+21>:	repz retq
   0x0000000000001786 <+23>:	mov    $0x0,%eax
   0x000000000000178b <+28>:	retq

 

string_length는 문자열 길이를 반환하는 함수라고 과감히 예상할 수 있습니다.

그리고 바로 %rdi를 사용하는것으로 보아, %rdi에 문자열이 전달되었다고 생각할 수 있습니다.

전달될 때는 첫 element의 주소가 저장됩니다.

 

 

 


<+0 ~ 3> 

 

(%rdi) - 0x0을 계산하고 결과값이 0과 같으면 23번 줄로 점프합니다.

다시말하면, (%rdi)는 현재 배열에서 첫번째 value고 이 value가 '\0'임을 체크합니다.

만약 '\0'이면 <+23>으로 점프해서 eax에 0을 저장하고 리턴하고, 아니라면 다음으로 진행합니다.

   0x000000000000176f <+0>:	cmpb   $0x0,(%rdi)
   0x0000000000001772 <+3>:	je     0x1786 <string_length+23>
   0x0000000000001786 <+23>:	mov    $0x0,%eax
   0x000000000000178b <+28>:	retq

<+5 ~ 14>

 

%rdx에 %rdi값을 저장했습니다. 즉 %rdx도 첫번째 원소를 가리키는 포인터값을 지니게 됩니다.

그 후 1을 더하므로 다음 원소를 가리키게 되고, %eax가 %rdx의 포인터값의 일부를 가지도록 합니다.

그 후 %eax에서 %edi를 빼므로 %rdx와 %rdi의 인덱스 차이를 가지게 됩니다.

 

   0x0000000000001774 <+5>:	mov    %rdi,%rdx
   0x0000000000001777 <+8>:	add    $0x1,%rdx
   0x000000000000177b <+12>:	mov    %edx,%eax
   0x000000000000177d <+14>:	sub    %edi,%eax

<+16 ~ 19>

 

%rdx가 가리키는 주소값의 내용, 즉 두번째 원소가 null인지 비교합니다. (아까 했던 과정임)

'\0'이 아니라면 <+8>로 점프합니다.

   0x000000000000177f <+16>:	cmpb   $0x0,(%rdx)
   0x0000000000001782 <+19>:	jne    0x1777 <string_length+8>

 

 

결론

여기서부터 %rdx를 1더하고

%eax는 %rdx에서 %rdi의 간격차를 계산한 값을 가지게 되는데

이를 %rdx가 '\0'이 될때까지 반복하므로 %eax가 결국에는 %rdi에 전달받은 문자열이 길이를 가지게 됨을 알 수 있습니다.

 

 

 

 


string_no_equal

Dump of assembler code for function strings_not_equal:
   0x000000000000178c <+0>:	push   %r12
   0x000000000000178e <+2>:	push   %rbp
   0x000000000000178f <+3>:	push   %rbx
   0x0000000000001790 <+4>:	mov    %rdi,%rbx
   0x0000000000001793 <+7>:	mov    %rsi,%rbp
   0x0000000000001796 <+10>:	callq  0x176f <string_length>
   0x000000000000179b <+15>:	mov    %eax,%r12d
   0x000000000000179e <+18>:	mov    %rbp,%rdi
   0x00000000000017a1 <+21>:	callq  0x176f <string_length>
   0x00000000000017a6 <+26>:	mov    $0x1,%edx
   0x00000000000017ab <+31>:	cmp    %eax,%r12d
   0x00000000000017ae <+34>:	je     0x17b7 <strings_not_equal+43>
   0x00000000000017b0 <+36>:	mov    %edx,%eax
   0x00000000000017b2 <+38>:	pop    %rbx
   0x00000000000017b3 <+39>:	pop    %rbp
   0x00000000000017b4 <+40>:	pop    %r12
   0x00000000000017b6 <+42>:	retq
   0x00000000000017b7 <+43>:	movzbl (%rbx),%eax
   0x00000000000017ba <+46>:	test   %al,%al
   0x00000000000017bc <+48>:	je     0x17e5 <strings_not_equal+89>
   0x00000000000017be <+50>:	cmp    0x0(%rbp),%al
   0x00000000000017c1 <+53>:	jne    0x17ec <strings_not_equal+96>
   0x00000000000017c3 <+55>:	add    $0x1,%rbx
   0x00000000000017c7 <+59>:	add    $0x1,%rbp
   0x00000000000017cb <+63>:	movzbl (%rbx),%eax
   0x00000000000017ce <+66>:	test   %al,%al
   0x00000000000017d0 <+68>:	je     0x17de <strings_not_equal+82>
   0x00000000000017d2 <+70>:	cmp    %al,0x0(%rbp)
   0x00000000000017d5 <+73>:	je     0x17c3 <strings_not_equal+55>
   0x00000000000017d7 <+75>:	mov    $0x1,%edx
   0x00000000000017dc <+80>:	jmp    0x17b0 <strings_not_equal+36>
   0x00000000000017de <+82>:	mov    $0x0,%edx
   0x00000000000017e3 <+87>:	jmp    0x17b0 <strings_not_equal+36>
   0x00000000000017e5 <+89>:	mov    $0x0,%edx
   0x00000000000017ea <+94>:	jmp    0x17b0 <strings_not_equal+36>
   0x00000000000017ec <+96>:	mov    $0x1,%edx
   0x00000000000017f1 <+101>:	jmp    0x17b0 <strings_not_equal+36>

 

string_no_eqaul, 문자열 두 개 받아서 서로 다른지 비교하는 기능을 가졌을 것으로 보입니다.


 

 

<+4 ~ 7>

 

인자 전달로 자주쓰이는 %rdi, %rsi를 %rbx, %rbp로 전달했습니다.

이는 callee-saved register인 레지스터에 저장해서 caller-saved register인 %rdi, %rsi가 string_no_eqaul 함수에서 변하지 않도록 하기위함을 알 수 있습니다.

이제 %rbx, %rbp가 각각 문자열의 첫 원소를 가리키는 포인터를 가집니다.

 

 

 

 

 


<+10 ~ 31>

 

위의 분석해서 알 수 있듯이 string_length 함수는 %rdi를 인자로 전달받는 함수입니다.

즉, 위 문자열에서 "abcdefg" 문자열이 전달되고 %eax에 7이 반환되고 이를 %r12d에 저장합니다.

 

그리고 <+18> 에서 %rbp의 값을 %rdi에 전달합니다. 이는 아래 문자열을 string_length의 인자로 전달하기 위함임을 알 수 있습니다.

함수리 리턴되면 %eax에 7이 전달될겁니다.


<+26>

 

%edx 에 1을 저장하고 있습니다.( 어떤 역할인지는 밑에서 설명합니다 )

 


 

<+31 ~ 34>

 

%r12d과 %eax 둘이 같은지 비교합니다.

다시말하면, 첫번째 문자열의 길이와 두번째 문자열의 길이를 비교합니다.

- 만약 같다면 <+43>으로 점프합니다.

- 만약 다르면, 두 문자열이 서로 다름이 판정되었으므로 점프하지 않고 <+36 ~ 42>에서 %edx를 %eax에 저장해 반환함으로써 결과적으로 1을 반환합니다.

-> 즉 %edx는 문자열이 다른지 같은지를 알려주는 반환값의 역할을 하는 레지스터입니다.

 

 

 


<+43 ~ 48>

 

여기부터는 두 문자열의 길이가 같음을 보장한 후 진행하게됩니다. (다른경우 이미 리턴됐으므로)

 

(%rbx)는 첫번째 문자열에서 가리키고 있는 값입니다. 이 경우에는 a입니다. 그리고 a를 %eax에 저장합니다.

그리고 저장한 %eax는 1바이트 값이므로 %al이 '\0'인지 아닌지 체크합니다.

 

- '\0' 인경우 : 만약 '\0'이라면 두 문자열이 모두 비었음을 의미하고, 길이가 같으므로 두 문자열이 같습니다. 후에 <+89>로 이동해서 %edx를 0으로 만들어 <+36>으로 이동해 0을 반환하게 됩니다.

 

- '\0' 이 아닌경우 : 두 문자열이 모두 빈 문자열이 아님을 알 수 있습니다. 이제부터는 각 문자를 순서대로 하나씩 비교해봐야합니다.

우선 <+50 ~ 53>에서 첫번째 원소를 비교합니다.

   0x00000000000017be <+50>:	cmp    0x0(%rbp),%al
   0x00000000000017c1 <+53>:	jne    0x17ec <strings_not_equal+96>

%al은 첫번째 문자열의 첫번째값을 지니고, 0x0(%rbp)는 두번째 문자열의 첫번째 값을 가집니다.

<53>에서 두 문자열이 다르면 <+96> 으로 이동해서

   0x00000000000017ec <+96>:	mov    $0x1,%edx
   0x00000000000017f1 <+101>:	jmp    0x17b0 <strings_not_equal+36>

%edx에 1(두 문자열이 다름)을 저장하고 <+36>으로 점프해서 1을 반환합니다.


<+55 ~ 73>

 

%rbx, %rbp에 각각 1을 더해서 다음 원소를 가리키도록합니다.

다시 %eax에는 첫번째 문자열에서 %rbx가 가리키는 값을 가지도록하므로,

앞으로 나오는 %al은 첫번째 문자열의 원소입니다. (%al은 %eax의 일부입니다)

그리고 오른쪽의 상태를 가집니다.

 

이제 <+66>에서 %al이 '\0'인지 아닌지 체크합니다

 

'\0'인경우 : 두 문자열이 길이가 같지만, 가리키는 첫번째 문자열의 원소가 '\0'이므로 가리키는 두번째 문자열의 원소 또한 '\0'입니다. 즉 두 문자열이 같습니다. 때문에 <+82>로 넘어가서 0을 리턴합니다.

'\0'이 아닌경우 : 가리키는 두 원소가 같은지 체크해야합니다. 만약 다르다면 두 문자열이 다르므로 <+75>로 가서 1을 리턴합니다.

   0x00000000000017d7 <+75>:	mov    $0x1,%edx
   0x00000000000017dc <+80>:	jmp    0x17b0 <strings_not_equal+36>

만약 같다면 <+55>로 돌아가서 <+55 ~ 73> 과정을 반복해서 문자열의 같은지 여부를 %rax에 리턴하게 됩니다.

<+55>:	add    $0x1,%rbx
<+59>:	add    $0x1,%rbp
<+63>:	movzbl (%rbx),%eax
<+66>:	test   %al,%al
<+68>:	je     0x17de <strings_not_equal+82>
<+70>:	cmp    %al,0x0(%rbp)
<+73>:	je     0x17c3 <strings_not_equal+55>

두 문자열이 같은경우에 마지막 순간은 아래와 같은 상태가 됩니다.


phase_1

Dump of assembler code for function phase_1:
=> 0x00005555554012e4 <+0>:	sub    $0x8,%rsp
   0x00005555554012e8 <+4>:	lea    0x17e1(%rip),%rsi        # 0x555555402ad0
   0x00005555554012ef <+11>:	callq  0x55555540178c <strings_not_equal>
   0x00005555554012f4 <+16>:	test   %eax,%eax
   0x00005555554012f6 <+18>:	jne    0x5555554012fd <phase_1+25>
   0x00005555554012f8 <+20>:	add    $0x8,%rsp
   0x00005555554012fc <+24>:	retq
   0x00005555554012fd <+25>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401302 <+30>:	jmp    0x5555554012f8 <phase_1+20>

위에서 관찰했듯이 strings_not_eqaul 함수는 %rdi, %rsi를 매개변수로 가집니다.


<+4 ~ 18>

 

<+4>를 보면 0x17e1(%rip)를 %rsi에 저장하고 있습니다.

<+11>에서 strings_not_eqaul을 호출해서 %eax에 두 문자열이 같은지 다른지를 판별한 값이 들어갑니다.

<+16 ~ 18>에서 %eax가 1이면 <+25>로 가서 폭탄이 터지므로 두 문자열이 같도록해서 <+24>에서 정상적으로 phase_1이 리턴되도록 해야합니다.

즉, strings_not_eqaul의 %rdi, %rsi 중 하나는 입력한 값이고, 하나는 답이므로 <+11>전까지 진행해서 값을 꺼내보면 됩니다.

abcd를 입력해서 값을 꺼내보면,

 

%rdi에 입력한 값이 들어갔으므로, %rsi에 답이 있다고 추론할 수 있습니다.

즉, 답은 다음과 같습니다.

When I get angry, Mr. Bigglesworth gets upset.

 

'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 Phase2 해설  (0) 2023.10.19

+ Recent posts