Layer7 - 리버싱 4차시 과제
IDA를 쓰면 10초컷인 문제들이긴 한데 그럼 너무 날먹이니까 IDA안쓰고 gdb만으로 분석했다. (사실 수업때 gdb로 동적분석하는거만 알려주길래 IDA를 쓰면 안될것같았다.)
prob1

까자마자 바로 보인다. scanf에서 rbp-0xc위치에 입력 받고 그게 0xdeadbeef와 같은지 비교해준다. 다를경우엔 jne로 main+93으로 뛰고 같을경우엔 안뛴다. 안뛸 경우에는 flag_generator함수가 호출되는데 이 함수의 return값을 puts로 그대로 출력해주는거 보니까 얘가 플래그를 만들어주는 함수임을 알 수 있다.
0xdeadbeef를 10진수로 바꾸면 3735928559가 되는데 이걸 입력해보면

플래그가 나오긴 한다. 여기서 끝내면 노잼이니까 flag_generator함수를 핸드레이 해봤다.

생각보다 분량 별거 없다. 어셈으로 이정도 분량이면 C언어로는 엄청 짧게 구현되어 있을것이다.
아무튼 분석을 해보면 처음에 0x21짜리 malloc을 하나 해준다. 할당받은 힙 주소를 rbp-0x8에 넣고 다시 rax로 가져와준다. 그리고 나서는
*rax = 0x1614581d0a160e23;
*(rax+8) = 0x8300a1d0e301a5f;
*(rax+16) = 0xe301b0e300b5f5f;
*(rax+24) = 0x12165e0d020a5a5a;
*(rax+32) = 0;
위와같이 값을 할당해준 후 rbp-0xc위치에 0을 넣어준다.

그리고 flag_generator+132로 뛰어주는데 뛰고 나면 rbp-0xc랑 0x1f랑 비교한다. rbp-0xc에 들어있는 값이 작거나 같을 경우 flag_generator+94로 뛰어준다. flag_generator+94에서는 rbp-0xc의 값을 rdx로 가져오고 rbp-0x8의 값을 rax로 가져온다. 그 후에 rax에 rdx를 더해주고 다시 똑같은 과정을 거쳐서 rdx에도 똑같은 값을 넣어준다. 그리고 rdx가 가리키는 주소에 있는 값을 가져와서 edx에 넣어주고 0x6f랑 xor한뒤에 rdx의 하위 1byte인 dl을 rax가 가리키는 주소에 넣어준다. 그 후에 rbp-0xc에 들어있는 값을 1증가시키고 다시 0x1f랑 비교한다. 즉 0x1f만큼 루틴을 반복한다는걸 알 수 있다. 전형적인 for문의 형태이다. rbp-0xc의 값은 for문에서 사용되는 카운트 변수라는것을 알 수 있고 rbp-0x8에 들어간건 할당받은 힙주소니까 아까 할당한 값에서 인덱스를 하나하나 늘려가면서 0x6f랑 xor하면 플래그가 나온다는 사실을 알 수 있다.
#include <stdio.h>
#include <stdlib.h>
char *flag_generator(){
long long *s = malloc(0x21);
s[0] = 0x1614581d0a160e23;
s[1] = 0x8300a1d0e301a5f;
s[2] = 0xe301b0e300b5f5f;
s[3] = 0x12165e0d020a5a5a;
s[4] = 0;
for(int i = 0; i <= 0x1f; i++)
*((char *)s+i) ^= 0x6f;
return s;
}
int main(void){
puts(flag_generator());
return 0;
}
C언어로 핸드레이 해보면 이렇게 짤 수 있고

잘 나온다. python으로도 짜보면
import struct
s = [0 for i in range(0x21)]
s[0] = struct.pack('<Q', 0x1614581d0a160e23)
s[8] = struct.pack('<Q', 0x8300a1d0e301a5f)
s[16] = struct.pack('<Q', 0xe301b0e300b5f5f)
s[24] = struct.pack('<Q', 0x12165e0d020a5a5a)
s[32] = struct.pack('<Q', 0)
s = b''.join([s[i] for i in range(0, 33, 8)])
for i in range(0x20):
print(chr(s[i]^0x6f), end='')

잘 나온다.
prob2
0x000000000040075b <+0>: push rbp
0x000000000040075c <+1>: mov rbp,rsp
0x000000000040075f <+4>: sub rsp,0x20
0x0000000000400763 <+8>: mov rax,QWORD PTR fs:0x28
0x000000000040076c <+17>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400770 <+21>: xor eax,eax
0x0000000000400772 <+23>: mov edi,0xfeedc0de
0x0000000000400777 <+28>: call 0x400580 <srand@plt>
0x000000000040077c <+33>: mov DWORD PTR [rbp-0x10],0x0
0x0000000000400783 <+40>: jmp 0x4007ca <main+111>
0x0000000000400785 <+42>: mov edi,0x4008d4
0x000000000040078a <+47>: call 0x400550 <puts@plt>
0x000000000040078f <+52>: call 0x4005b0 <rand@plt>
0x0000000000400794 <+57>: mov DWORD PTR [rbp-0xc],eax
0x0000000000400797 <+60>: lea rax,[rbp-0x14]
0x000000000040079b <+64>: mov rsi,rax
0x000000000040079e <+67>: mov edi,0x4008ec
0x00000000004007a3 <+72>: mov eax,0x0
0x00000000004007a8 <+77>: call 0x4005a0 <__isoc99_scanf@plt>
0x00000000004007ad <+82>: mov eax,DWORD PTR [rbp-0x14]
0x00000000004007b0 <+85>: cmp DWORD PTR [rbp-0xc],eax
0x00000000004007b3 <+88>: je 0x4007c6 <main+107>
0x00000000004007b5 <+90>: mov edi,0x4008ef
0x00000000004007ba <+95>: call 0x400550 <puts@plt>
0x00000000004007bf <+100>: mov eax,0x1
0x00000000004007c4 <+105>: jmp 0x40082c <main+209>
0x00000000004007c6 <+107>: add DWORD PTR [rbp-0x10],0x1
0x00000000004007ca <+111>: cmp DWORD PTR [rbp-0x10],0x13
0x00000000004007ce <+115>: jle 0x400785 <main+42>
0x00000000004007d0 <+117>: mov edi,0x4008f8
0x00000000004007d5 <+122>: call 0x400550 <puts@plt>
0x00000000004007da <+127>: lea rax,[rbp-0x14]
0x00000000004007de <+131>: mov rsi,rax
0x00000000004007e1 <+134>: mov edi,0x4008ec
0x00000000004007e6 <+139>: mov eax,0x0
0x00000000004007eb <+144>: call 0x4005a0 <__isoc99_scanf@plt>
0x00000000004007f0 <+149>: mov eax,DWORD PTR [rbp-0x14]
0x00000000004007f3 <+152>: cmp eax,0xdeadface
0x00000000004007f8 <+157>: jne 0x40081d <main+194>
0x00000000004007fa <+159>: mov edi,0x40090d
0x00000000004007ff <+164>: call 0x400550 <puts@plt>
0x0000000000400804 <+169>: mov eax,0x0
0x0000000000400809 <+174>: call 0x4006c6 <flag_generator>
0x000000000040080e <+179>: mov rdi,rax
0x0000000000400811 <+182>: call 0x400550 <puts@plt>
0x0000000000400816 <+187>: mov eax,0x0
0x000000000040081b <+192>: jmp 0x40082c <main+209>
0x000000000040081d <+194>: mov edi,0x400916
0x0000000000400822 <+199>: call 0x400550 <puts@plt>
0x0000000000400827 <+204>: mov eax,0x1
0x000000000040082c <+209>: mov rdx,QWORD PTR [rbp-0x8]
0x0000000000400830 <+213>: xor rdx,QWORD PTR fs:0x28
0x0000000000400839 <+222>: je 0x400840 <main+229>
0x000000000040083b <+224>: call 0x400560 <__stack_chk_fail@plt>
0x0000000000400840 <+229>: leave
0x0000000000400841 <+230>: ret
얘는 아까 문제보다 살짝 길어졌다. flag_generator은 보니까 아까 루틴이랑 똑같고 xor하는 값만 바꼈기 때문에 딱히 핸드레이 할 필요가 없다. 이번에는 플래그 주는 조건만 분석하겠다.
먼저 scand함수로 0xfeedc0de를 인자로 넘겨준다. 10진수로 바꾸면 4276994270이다.
0x000000000040078f <+52>: call 0x4005b0 <rand@plt>
0x0000000000400794 <+57>: mov DWORD PTR [rbp-0xc],eax
0x0000000000400797 <+60>: lea rax,[rbp-0x14]
0x000000000040079b <+64>: mov rsi,rax
0x000000000040079e <+67>: mov edi,0x4008ec
0x00000000004007a3 <+72>: mov eax,0x0
0x00000000004007a8 <+77>: call 0x4005a0 <__isoc99_scanf@plt>
0x00000000004007ad <+82>: mov eax,DWORD PTR [rbp-0x14]
0x00000000004007b0 <+85>: cmp DWORD PTR [rbp-0xc],eax
0x00000000004007b3 <+88>: je 0x4007c6 <main+107>
0x00000000004007b5 <+90>: mov edi,0x4008ef
0x00000000004007ba <+95>: call 0x400550 <puts@plt>
0x00000000004007bf <+100>: mov eax,0x1
0x00000000004007c4 <+105>: jmp 0x40082c <main+209>
0x00000000004007c6 <+107>: add DWORD PTR [rbp-0x10],0x1
0x00000000004007ca <+111>: cmp DWORD PTR [rbp-0x10],0x13
0x00000000004007ce <+115>: jle 0x400785 <main+42>
그다음 rand부분 부터 보면 아까 flag_generator를 분석했을때 봤던 형태와 유사하다. rbp-0x10이 카운트 변수이고 반복문임을 알 수 있다. scanf로 입력을 받고 입력받은 값이 rand함수로 생성한 값과 같은지 체크한다. 이 과정을 20번 거친다.

20번 모두 맞으면 마지막으로 입력을 한번 더 받아서 0xdeadface인지 체크하고 맞을 경우에는 flag_generator를 호출하는것을 볼 수 있다.
rand함수는 seed값을 기반으로 내부적으로 복잡한 비트 연산을 거쳐서 랜덤처럼 보이는 값을 생성하는걸로 알고 있다. 즉 seed값이 같으면 수행하는 연산이 항상 같으므로 같은 값이 나온다는 말이다. 그래서 보통 time과 같이 계속해서 변하는 값을 seed로 주는데 그래도 예측하기 쉽지만 위와 같이 seed값을 고정시킬 경우에는 사실상 랜덤이 아니게 된다.
from ctypes import *
libc=CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(0xfeedc0de)
for i in range(20):
print(libc.rand())
print(0xdeadface)
python ctypes를 이용해서 seed를 똑같이 주고 C언어의 rand를 부르면 값이 똑같이 나온다.

이렇게 나온 값을 복붙하면
root@fc0ce980d0ed ~/hacking
❯ ./prob2
guessing random number!
1298921064
602727545
1936120676
10262152
1169740271
1286574116
1898974427
1351434765
1657355885
1589073855
1596799434
937238354
879507685
1388661204
618576540
649908474
16549218
2040108771
1813507610
468945779
3735943886guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
guessing random number!
input correct number
correct!
Layer7{Y0u_are_m45ter_0f_A55EMB1Y!}
플래그가 나온다.