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

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

main

 

<+148> : main함수에서 read_line의 반환값이 %rdi에 저장되는 것으로보아, 입력한 문자열이 %rdi에 저장됨을 추론할 수 있습니다.

   0x0000555555401214 <+138>:	callq  0x555555400f00 <puts@plt>
   0x0000555555401219 <+143>:	callq  0x555555401b0d <read_line>
   0x000055555540121e <+148>:	mov    %rax,%rdi
   0x0000555555401221 <+151>:	callq  0x55555540136d <phase_3>

 

테스트 결과 일치함을 확인할 수 있었습니다.

(gdb) x/s $rdi
0x555555604760 <input_strings+160>:	"50 100"

__isoc99_sscanf 분석

 

phase2에서 __isoc99_sscanf 함수에 대해 이미 알아낸 사실들이 많습니다.

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

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

 


Phase_3

Dump of assembler code for function phase_3:
   0x000055555540136d <+0>:	sub    $0x18,%rsp
   0x0000555555401371 <+4>:	mov    %fs:0x28,%rax
   0x000055555540137a <+13>:	mov    %rax,0x8(%rsp)
   0x000055555540137f <+18>:	xor    %eax,%eax
   0x0000555555401381 <+20>:	lea    0x4(%rsp),%rcx
   0x0000555555401386 <+25>:	mov    %rsp,%rdx
   0x0000555555401389 <+28>:	lea    0x1a5d(%rip),%rsi        # 0x555555402ded
   0x0000555555401390 <+35>:	callq  0x555555400fc0 <__isoc99_sscanf@plt>
   0x0000555555401395 <+40>:	cmp    $0x1,%eax
   0x0000555555401398 <+43>:	jle    0x5555554013b3 <phase_3+70>
   0x000055555540139a <+45>:	cmpl   $0x7,(%rsp)
   0x000055555540139e <+49>:	ja     0x5555554013eb <phase_3+126>
   0x00005555554013a0 <+51>:	mov    (%rsp),%eax
   0x00005555554013a3 <+54>:	lea    0x1796(%rip),%rdx        # 0x555555402b40
   0x00005555554013aa <+61>:	movslq (%rdx,%rax,4),%rax
   0x00005555554013ae <+65>:	add    %rdx,%rax
   0x00005555554013b1 <+68>:	jmpq   *%rax
   0x00005555554013b3 <+70>:	callq  0x555555401a90 <explode_bomb>
   0x00005555554013b8 <+75>:	jmp    0x55555540139a <phase_3+45>
   0x00005555554013ba <+77>:	mov    $0x8c,%eax
   0x00005555554013bf <+82>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013c1 <+84>:	mov    $0x3be,%eax
   0x00005555554013c6 <+89>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013c8 <+91>:	mov    $0x79,%eax
   0x00005555554013cd <+96>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013cf <+98>:	mov    $0x2c9,%eax
   0x00005555554013d4 <+103>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013d6 <+105>:	mov    $0x379,%eax
   0x00005555554013db <+110>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013dd <+112>:	mov    $0x347,%eax
   0x00005555554013e2 <+117>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013e4 <+119>:	mov    $0x39a,%eax
   0x00005555554013e9 <+124>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013eb <+126>:	callq  0x555555401a90 <explode_bomb>
   0x00005555554013f0 <+131>:	mov    $0x0,%eax
   0x00005555554013f5 <+136>:	jmp    0x5555554013fc <phase_3+143>
   0x00005555554013f7 <+138>:	mov    $0x123,%eax
   0x00005555554013fc <+143>:	cmp    %eax,0x4(%rsp)
   0x0000555555401400 <+147>:	je     0x555555401407 <phase_3+154>
   0x0000555555401402 <+149>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401407 <+154>:	mov    0x8(%rsp),%rax
   0x000055555540140c <+159>:	xor    %fs:0x28,%rax
   0x0000555555401415 <+168>:	jne    0x55555540141c <phase_3+175>
   0x0000555555401417 <+170>:	add    $0x18,%rsp
   0x000055555540141b <+174>:	retq
   0x000055555540141c <+175>:	callq  0x555555400f20 <__stack_chk_fail@plt>

 


<+40 ~ 49>

   0x0000555555401395 <+40>:	cmp    $0x1,%eax
   0x0000555555401398 <+43>:	jle    0x5555554013b3 <phase_3+70>
   0x000055555540139a <+45>:	cmpl   $0x7,(%rsp)
   0x000055555540139e <+49>:	ja     0x5555554013eb <phase_3+126>
   0x00005555554013b3 <+70>:	callq  0x555555401a90 <explode_bomb>
   0x00005555554013eb <+126>:	callq  0x555555401a90 <explode_bomb>

<+40> : 현재 R[%eax]는 입력한 숫자의 개수입니다. 입력한 숫자의 개수를 1과 비교합니다.

 

<+43> : 만약 1 이하라면 <+70>으로 점프합니다. <+70>은 폭탄이므로, 입력하는 숫자의 개수는 2개 이상이어야 합니다. (50을 입력했다고 가정했으므로 폭탄이 터집니다.)

 

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

 

<+49> : 7보다 크거나 0보다 작으면(ja는 unsigned 연산이므로) <+126>으로 점프합니다. 폭탄이므로 첫번째 숫자는 0이상, 7이하 여야합니다.


여기서부터는 0 291을 입력했다고 가정합니다.

 

<+51 ~ 68>

 

   0x00005555554013a0 <+51>:	mov    (%rsp),%eax
   0x00005555554013a3 <+54>:	lea    0x1796(%rip),%rdx        # 0x555555402b40
   0x00005555554013aa <+61>:	movslq (%rdx,%rax,4),%rax
   0x00005555554013ae <+65>:	add    %rdx,%rax
   0x00005555554013b1 <+68>:	jmpq   *%rax

 

<+51> : %eax에 입력한 첫번째 숫자를 저장합니다.

 

<+54> : <+54> 코드를 진행할 때의 R[%rip]는 <+61>의 주소, 0x00005555554013aa입니다. (%rip, PC는 다음 실행할 명령어의 주소를 가리킨다.)

즉, R[%rdx] = 0x1796 + 0x00005555554013aa = 0x555555402b40 가 됩니다.

 

<+61> : %rax는 입력한 첫번째 숫자를 의미하는데, 0이상 7이하의 숫자를 가지는 숫자에 4를 곱해서 %rdx에 더한값이 가리키는 주소의 값을 %rax에 저장합니다. R[%rdx] + 4 * R[%rax]의 참조 값은 gdb test로 알아낼 수 있었습니다.

 

첫번째 숫자 R[%rdx] + 4 * R[%rax] %rax = M[R[%rdx] + 4 * R[%rax]]
Hex, Dec
0 0x555555402b40 0xffffffffffffe8b7, -5961
1 0x555555402b44 0xffffffffffffe87a, -6022
2 0x555555402b48 0xffffffffffffe881, -6015
3 0x555555402b5c 0xffffffffffffe888, -6008
4 0x555555402b60 0xffffffffffffe88f, -6001
5 0x555555402b64 0xffffffffffffe896, -5994
6 0x555555402b68 0xffffffffffffe89d, -5987
7 0x555555402b7c 0xffffffffffffe8a4, -5980

 

<+65> : %rax에 %rdx를 한번 더 더합니다. ( R[%rdx] is 0x555555402b40, 93824990849856(demical)  )

 

<+68> : %rax가 가진 주소값으로 점프합니다. %rax 숫자별로 점프하는 곳을 표시하고 정리하면 밑과 같습니다.

 

 

첫번째 숫자 %rax
Hex, Dec
jump <+ ? >
0 0x5555554013F7, 93824990843895 <+138>
1 0x5555554013BA, 93824990843834 <+77>
2 0x5555554013C1, 93824990843841 <+84>
3 0x5555554013C8, 93824990843848 <+119>
4 0x5555554013CF, 93824990843855 <+98>
5 0x5555554013D6, 93824990843862 <+105>
6 0x5555554013DD, 93824990843869 <+112>
7 0x5555554013E4, 93824990843876 <+119>

 


<77 ~ 138>

 

   0x00005555554013ba <+77>: mov    $0x8c,%eax
   0x00005555554013bf <+82>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013c1 <+84>: mov    $0x3be,%eax
   0x00005555554013c6 <+89>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013c8 <+91>: mov    $0x79,%eax
   0x00005555554013cd <+96>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013cf <+98>: mov    $0x2c9,%eax
   0x00005555554013d4 <+103>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013d6 <+105>: mov    $0x379,%eax
   0x00005555554013db <+110>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013dd <+112>: mov    $0x347,%eax
   0x00005555554013e2 <+117>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013e4 <+119>: mov    $0x39a,%eax
   0x00005555554013e9 <+124>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013eb <+126>: callq  0x555555401a90 <explode_bomb>
   0x00005555554013f0 <+131>: mov    $0x0,%eax
   0x00005555554013f5 <+136>: jmp    0x5555554013fc <phase_3+143>
   0x00005555554013f7 <+138>: mov    $0x123,%eax

 

첫번째 숫자에 따라 점프해서 %eax에 값을 저장하고 있습니다.

각 숫자별로 정리하면 다음과 같습니다.

 

첫번째 숫자 jump <+ ? > Demical : R[%eax]
0 <+138> 291
1 <+77> 140
2 <+84> 958
3 <+119> 922
4 <+98> 713
5 <+105> 889
6 <+112> 839
7 <+119> 922

<143 ~ 174>

 

   0x00005555554013fc <+143>:	cmp    %eax,0x4(%rsp)
   0x0000555555401400 <+147>:	je     0x555555401407 <phase_3+154>
   0x0000555555401402 <+149>:	callq  0x555555401a90 <explode_bomb>
   0x0000555555401407 <+154>:	mov    0x8(%rsp),%rax
   0x000055555540140c <+159>:	xor    %fs:0x28,%rax
   0x0000555555401415 <+168>:	jne    0x55555540141c <phase_3+175>
   0x0000555555401417 <+170>:	add    $0x18,%rsp
   0x000055555540141b <+174>:	retq

 

<+143> : 0x4(%rsp) 는 우리가 입력한 두번째 숫자를 의미합니다. 두번째 숫자와 R[%eax]를 비교하고 있습니다.

 

<+147> : 만약 위에서 정리한 R[%eax]와 두번째 숫자가 같다면 <+154>로 점프합니다.

 

<+149> : 만약 두번째 숫자가 R[%eax]와 다르다면, 폭탄이 터지게 되므로 두번째 숫자와 R[%eax]가 같아야함이 밝혀졌습니다.


즉 답은 다음과 같습니다.

 

첫번째 숫자 두번째 숫자
0 291
1 140
2 958
3 922
4 713
5 889
6 839
7 922

 

'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 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

문제점:

UIImageView에서 scaleToFit 설정을 준 후, constraint를 통해 width가 화면 뷰에 맞도록 고정시켰지만,

UIImageView의 높이가 UIImageView에 저장된 UIImage에 맞게 줄어들지 않아 남는 여백이 생깁니다.

 

해결법:

 

여백을 없애는 기능 자체는 아직 없는 것으로 보입니다.

직접 사진의 비율을 계산해서 width를 원하는 값으로 고정시켜, 도출된 height 값으로 높이를 설정해야 합니다.

(물론 height를 고정시키고, width를 도출시킬 수 도 있습니다.)

다시 말하면,

 

(1) UIImage의 비율을 구해서

(2) 구한 비율값을 바탕으로 설정할 height를 도출하고

(3) AutoLayout height constraint에 넣는 방식으로 해결할 수 있습니다.

 

 

(1) image의 비율을 저장. ( 비율 = 가로 ÷ 세로 ) (반대로 나눠도 되고..)

UIImageView의 저장된 image(UIImageView에 저장될 image)의 비율을 ratio 변수에 저장합니다.

let ratio: CGFloat = image.size.width / image.size.height

 

(2) UIImageView의 width와 비율로 height 도출

UIImageView를 넣는 상황을 다시 생각해 보면,

우리가 원하는 width값을 저장하면 그에 맞게 height값이 원본 이미지 크기에 맞게 설정되도록 하려는 겁니다.

그래서, 우리는 width와 ratio를 알고 있습니다.

저는 width를 100으로 설정하겠습니다.

 

imageView.snp.makeConstraints { make in
    make.width.equalTo(100)
}

 

ratio = width / height 이므로

-> height = width / ratio 가 되겠죠.

 

공식 그대로 제약조건에 추가해 줍니다.

 

imageView.snp.makeConstraints { make in
    make.height.equalTo(imageView.snp.width).dividedBy(ratio)
}

 

해결!

문제상황

struct BlueRectangle: View {
    @State var blueRectSize = CGSize(width: 20, height: 40)

    var body: some View {
        VStack {
            Rectangle()
                .frame(width: blueRectSize.width, height: blueRectSize.height)
                .foregroundStyle(.blue)

            BlackRectangle()
                .frame(height: 60)
        }
    }
}
struct BlackRectangle: View {
    var body: some View {
        HStack {
            Rectangle()
            Rectangle()
            Rectangle()
            Rectangle()
        }
    }
}

BlueRectangle 1개와 BlackRectangle 3개로 구성되어있다.

여기서 BlackRectangle의 사이즈는 동적으로 결정되었다.

이 상황에서 BlueRectangle 객체의 사이즈를 BlackRectangle 한 개의 사이즈과 똑같이 만들고 싶다.

따라서 이 장에서는 다음을 해결해야한다.

  • BlackRectangle 한 개의 사이즈를 구한다.
  • 구한 사이즈를 상위뷰로 전달한다.

BlackRectangle Size 구하기

GeometryReader를 이용해서 구할 수 있다.

뷰를 GeometryReader로 감싸면 GeometryProxy 타입의 변수를 통해 Size를 알 수 있다.

ForEach structure에 감싸져 있던 Rectangle을 GeometryReader로 감싸서,

overlay로 width값을 확인해 보자.

GeometryReader { proxy in
    Rectangle()
        .overlay {
            Text("\(proxy.size.width)")
                .foregroundStyle(.white)
        }  
}

 

이제 오른쪽 화면을 보면 BlackRectangle이 101.5의 width값을 가짐을 확인했다.

즉, GeometryReader를 통해 뷰의 Size를 구할 수 있음을 알았다.

이제 사이즈를 값을 분리된 상위뷰인 BlueRectangle에 전달해보자.

상위뷰로 데이터 전달하기

PreferenceKey 프로토콜를 통해 데이터를 상위뷰로 전달할 수 있다.

먼저, PreferenceKey를 준수하는 struct를 하나 정의한다.

struct GeometryPrefereneKey: PreferenceKey {
    typealias Value = CGSize

    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

preference modifier를 통해 데이터를 송신시키자.

GeometryReader { proxy in
      Rectangle()
          .overlay {
              Text("\(proxy.size.width)")
                   .foregroundStyle(.white)
          }
          .preference(key: GeometryPrefereneKey.self, value: proxy.size)
}

이제 onPreferenceChange modifier를 통해 데이터를 수신할 수 있다.

onPreferenceChange는 blueRectangle 변수에 값을 넣을 수 있는 뷰에 적당히 넣자. 나는 VStack을 선택했다.

그리고 수신된 데이터를 blueRectSize에 저장하면 문제가 해결된다.

(추가로 blueRectangle의 width값의 확인을 위해 overlay로 화면에 width를 렌더링했다)

VStack {
       Rectangle()
            .frame(width: blueRectSize.width, height: blueRectSize.height)
            .foregroundStyle(.blue)
            .overlay {
                Text("\(blueRectSize.width)")
            }

       BlackRectangle()
           .frame(height: 60)
}
.onPreferenceChange(GeometryPrefereneKey.self) { blackRectangleSize in
    blueRectSize = blackRectangleSize
}

대충 순서를 보면 다음과 같다 :

GeometryReader를 통해 Size 찾기

-> BlackRectangle에서 데이터 송신

-> BlueRectangle에서 데이터 수신

-> 수신된 데이터를 blueRectSize에 저장

-> @State에 의해 뷰 reRendering

-> blueRectangle의 사이즈 변경됨

'SwiftUI' 카테고리의 다른 글

[SwiftUI] ForEach and ID  (0) 2023.11.06

+ Recent posts