티스토리 뷰

반응형

두 번째 phase를 살펴보자.

 

일단, phase_2를 "(gdb) $ disas phase_2" 명령어로 어셈블리어를 추출하여보자.

0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp
  400efd:	53                   	push   %rbx
  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi
  400f05:	e8 52 05 00 00       	call   40145c <read_six_numbers>
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34>
  400f10:	e8 25 05 00 00       	call   40143a <explode_bomb>
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	call   40143a <explode_bomb>
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	ret

흠... 일단 중요한 부분은 함수 "read_six_numbers"부터 시작하는 것 같다. 앞의 부분은 스택 관련 operation이고, 실제로 400f05 instruction까지 실행하고 나면 이때부터 농간이 시작되는 것으로 추정된다. 그럼, 일단 "read_six_numbers"를 보고 오는 게 인지상정. 이 함수부터 분해하여보자.

000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp
  401460:	48 89 f2             	mov    %rsi,%rdx
  401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx
  401467:	48 8d 46 14          	lea    0x14(%rsi),%rax
  40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax
  401474:	48 89 04 24          	mov    %rax,(%rsp)
  401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9
  40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8
  401480:	be c3 25 40 00       	mov    $0x4025c3,%esi
  401485:	b8 00 00 00 00       	mov    $0x0,%eax
  40148a:	e8 61 f7 ff ff       	call   400bf0 <__isoc99_sscanf@plt>
  40148f:	83 f8 05             	cmp    $0x5,%eax
  401492:	7f 05                	jg     401499 <read_six_numbers+0x3d>
  401494:	e8 a1 ff ff ff       	call   40143a <explode_bomb>
  401499:	48 83 c4 18          	add    $0x18,%rsp
  40149d:	c3                   	ret

함수 이름 자체는 굉장히 직관적이지만, 그래도 공부 목적으로 한번 꼼꼼하게 따져보자. 우리에게 익숙한 sscanf 함수를 호출하기 전, lea 명령어와 mov 명령어로 %rsi, %rax 레지스터에 값을 넣는 모습을 확인할 수 있다. 0x401485까지 실행시킨 이후, %rsi 레지스터 안에 무엇을 넣는 지 확인하자.

(gdb) x/s $rsi
0x4025c3:       "%d %d %d %d %d %d"

아하! scanf에서 익숙한 문구가 보인다. 해석해보면 입력값은 6개의 정수라고 추측할 수 있다. 또한, sscanf를 의 반환값이 담긴 레지스터 %rax에는 입력한 정수의 개수가 담기는 것을 확인할 수 있다. 이는 여러 번의 노가다로 알아낸 사실이다.

 

0x40148f에서 [cmp $0x5 %eax]를 수행하여 jg를 통해 점프하지 않을 경우 폭탄이 터지고, 점프할 경우 0x401499로 가므로 반드시 점프를 해야한다. 즉, 입력값은 6개를 넘어야 한다. 어차피 6개보다 많은 입력을 하였을 경우 뒤의 입력은 잘리기 때문에 6개를 입력한 것과 같은 효과이다.

 

이제, read_six_numbers를 분석하였으므로 다시 원래의 함수 phase_2로 돌아오도록 하자!

 

...
  400f05:	e8 52 05 00 00       	call   40145c <read_six_numbers>
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34>
  400f10:	e8 25 05 00 00       	call   40143a <explode_bomb>
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	call   40143a <explode_bomb>
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	ret

폭탄이 터지는 곳을 보면 0x400f10, 0x400f20이다. 따라서, 이 부분은 건들지 않도록 해야한다.

 

먼저, %rsp에 담긴 값과 0x1을 비교한다. 테스트를 위하여 "10 20 40 80 160 320" 이란 글자를 넣고, x 명령어로 %rsp에 담긴 값을 확인하였다.

(gdb) x/d $rsp
0x7fffffffd8e0: 10

 

흠... %rsp에는 첫 input에 대한 메모리 주소를 가지고 있다고 유추할 수 있다. 0x1과 같아야지 폭탄이 터지지 않으므로, 번째 input 정수는 1이라 할 수 있다!

 

0x400f0e에서의 [je 400f30] 부터 살펴보면, %rbx에 *(%rsp+0x4)을 담는다. 다음으로 [jmp 400f17]을 수행하고,  %rax에 *(%rbx-0x4)를 담는다. 이후 %rax 자기 자신을 더한 후, %rbx의 메모리 주소와 %rax를 비교한다. 같지 않으면 폭탄이 터지므로 같아야 한다!

 

생각해보자. %rsp에는 첫 input에 대한 메모리 주소를 가지고 있고, 현재 정수를 input으로 받으므로 이를 저장한 버퍼는 정수의 크기인 4바이트 단위로 건너뛸 것이다. %rbx는 0x4씩 더해지므로, %rbx는 input 버퍼를 하나씩 index를 늘려가며 순회한다는 추측을 할 수 있다. 그리고 %rax에는 %rbx-0x4를 주소로 한 값을 담으므로, %rbx가 순회하는 배열의 (index-1) 값이라 할 수 있다.

 

이후 %rax는 자기 자신을 더하고 이를 index에 담긴 값과 비교하므로, 우리에게 친숙한 언어로 표현하면 arr[i-1] + arr[i-1] == arr[i] 인지를 확인하는 것이다. 이 과정을 5번 반복하며 총 6개의 input을 비교한다.

 

바꿔 말하면, 각 input은 전 input의 2배여야 한다는 의미가 된다. 어려워 보이는 코드가 단순 배열 순환의 의미를 지니고 있는 것이다.

 

우리에게 친숙한 c 코드로 변환하면 다음과 같이 쓸 수 있다.

int arr[6];

if (arr[0] != 1) {
	explode_bomb();
 }
 
 for (int i = 1; i < 6; i++) {
 	if (arr[i] != arr[i-1]*2) {
    	explode_bomb();
    }
}

 

첫 번째 input은 1로 고정되어 있으므로, 두 번째 input은 1+1 = 2, 세 번쨰 input은 2+2 = 4 ... 이런식으로 답을 구하면 다음과 같다.

 

답 : 1 2 4 8 16 32

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함