[CSAPP] 2. Bomb lab (Assembly Reversing & GDB Tool) - Phase 3
세 번째 phase를 살펴보자. 가보자 가보자!
phase_3 함수를 어셈블리어로 추출하여보자.
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff call 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a <phase_3+0x27>
400f65: e8 d0 04 00 00 call 40143a <explode_bomb>
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad <phase_3+0x6a>
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmp *0x402470(,%rax,8)
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
400fad: e8 88 04 00 00 call 40143a <explode_bomb>
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9 <phase_3+0x86>
400fc4: e8 71 04 00 00 call 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 ret
길이가 길어진 거 같은 느낌이 들지만, 천천히 해보자.
400f5b에서 sscanf 함수를 호출하고, 이후 %rax 레지스터와 상수 0x1을 비교한다. AT&T 기준이므로 만약 %rax가 1보다 클 경우, 400f6a로 점프하게 된다. 폭탄이 터지지 않으려면 jg가 true가 되어야 하고, 또한 phase_2에서 sscanf을 호출 한 뒤에 %rax에는 인수의 개수가 담긴다고 하였으므로, 최소한 두 개의 인수를 받는다는 것을 유추할 수 있다.
따라서, 2개의 인수를 넣어보자. 테스트를 위하여 "100 200"를 넣어주었다.
이후 상수 0x7과 *[%rsp+0x8]을 비교하여, 오른쪽의 값이 더 크면 점프한다는 사실을 알 수 있다. 점프를 할 경우 폭탄이 터지기 때문에, 적어도 %rsp+0x8의 값은 7보다 작은 값을 지녀야 한다.
따라서, 일단 %rsp-8안에는 어떤 값이 들어가 있는지 확인하여보자. x 명령어를 이용하여 직접 확인해보면 다음과 같다.
(gdb) x/d $rsp+8
0x7fffffffdf28: 100
아하! %rsp+8에는 첫 번째 input 정수가 들어가 있다는 것을 추측할 수 있다! 그러면, %rsp+4에는? 스택에 값이 push되는 원리를 생각해보면 두 번째 input 정수가 들어가 있을 확률이 매우 크다. 직접 찍어보자!
(gdb) x/d $rsp+4
0x7fffffffdf24: 0
흠... 우리가 넣은 "200"이라는 정수 대신 다른 값이 들어가 있는 것을 확인할 수 있다. 따라서, 일단은 잠깐 넘기고, 계속하여 실행하여보자. 400f6f이 true일 경우 폭탄이 터지는 지점으로 jmp하므로, jg가 false가 나야한다. 즉, 첫 번째 input은 7보다 작은 숫자여야 한다.
그럼, 테스트를 위하여 "1 345"라는 값을 넣어보도록 하자.
우리가 분석하지 않은 지점부터 계속하여 실행하자. %rax 레지스터 안에 첫 번째 input 값을 옮긴 다음, jmpq를 통해 이상한 위치로 jmp한다. 도착해보니 400fb9에 도착하였고, %rax 안에 0x137 = 311이란 값이 들어간다. 이후, *(%rsp+0xc)와 %rax의 값이 비교하여, 같지 않을 경우 폭탄이 터지는 것을 볼 수 있다. 그럼, 먼저 *(%rsp+0xc)에 어떤 값이 들어가는지 부터 살펴보아야 한다.
(gdb) x/d $rsp+12
0x7fffffffdf2c: 200
아하! 우리가 넣은 두 번째 input은 *(%rsp+0xc)에 위치해 있는 것까지 확인하였다.
정리하자면, 첫 번째 input은 1과 비교, 두 번째 input은 %rax = 311과 비교하여, 하나라도 다르면 폭탄이 터진다고 할 수 있다.
답은 쉽게 구할 수 있었지만, 아직 완전한 이해를 하지 않았으므로 미심쩍은 부분을 파헤쳐보자.
위의 설명에서 뭉게면서 넘어간 부분은 해당 instruction이다.
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
분석해보면, (0x402470 + %rax * 8)의 메모리 주소로 점프하라는 내용이다. 그럼, 0x402470에서 앞뒤로 담긴 내용을 살펴보면 다음과 같다.
(gdb) x/16gx 0x402470
0x402470: 0x0000000000400f7c 0x0000000000400fb9
0x402480: 0x0000000000400f83 0x0000000000400f8a
0x402490: 0x0000000000400f91 0x0000000000400f98
0x4024a0: 0x0000000000400f9f 0x0000000000400fa6
0x4024b0 <array.3449>: 0x737265697564616d 0x6c796276746f666e
0x4024c0: 0x7420756f79206f53 0x756f79206b6e6968
0x4024d0: 0x6f7473206e616320 0x6f62206568742070
0x4024e0: 0x206874697720626d 0x202c632d6c727463
여러 개의 메모리 주소가 나와있다. 예를 들어, %rax에 1이 담겨있을 경우 0x402478의 값인 0x400fb9로 점프하고, %rax에 2가 담겨 있을 경우 0x402480의 값인 0x400f83으로 점프한다. 이러한 점프 테이블을 가지고 있는, 우리에게 친숙한 문법이 있지 않나?
그렇다. Phase 3는 switch문으로 구현되어 있는 함수라 할 수 있다. 우리에게 친숙한 코드로 변환하면 다음과 같이 변환할 수 있다.
unsigned int input1, input2;
unsigned int res;
sscanf("%d %d", &input1, &input2);
if (input1 > 7) {
explode_bomb();
}
else {
switch(input1) {
case 0:
res = 0xcf; // 207
break;
case 1:
res = 0x137; // 311
break;
case 2:
res = 0x2c3; // 707
break;
case 3:
res = 0x100; // 256
break;
case 4:
res = 0x185; // 389
break;
case 5:
res = 0xce; // 206
break;
case 6:
res = 0x2aa; // 682
break;
case 7:
res = 0x147; // 327
break;
}
if (input2 == res) {
return;
} else {
explode_bomb();
}
}
답은 해당 input1과 input2를 잘 대응시켜 입력한다면, 총 7가지의 답이 존재할 수 있다.
답 : 0 207 / 1 311 / 2 707 / 3 256 / 4 389 / 5 206 / 6 682 / 7 327
아직까진 할만한가?