[Assembly/C] srand와 rand 어셈블리 분석, 구현
https://learn.microsoft.com/ko-kr/cpp/c-runtime-library/reference/rand?view=msvc-170
rand
의사 난수를 생성합니다. 이 함수의 보다 프로그래밍 방식으로 안전한 버전을 사용할 수 있습니다. 를 참조하세요 rand_s. 생성된 rand 숫자는 암호화된 보안이 아닙니다. 보다 암호화적으로 안전한 난수 생성을 위해 C++ 표준 라이브러리
위에서 말하는 rand가 안전하지 않은 이유는 뭘까? 어셈블리어를 분석해보고 구현해보면 그 답을 알 수 있다.
기본 설명
rand는 0~32767의 값을 갖는다. 이는 signed 16비트의 양수범위를 의미함. 그리고 srand를 사용하여 호출 rand하기 전에 의사 숫자 생성기를 시드한다.. 즉 초기 시드값를 통해 랜덤을 반환하는 구성인것이다. 살펴보자.
살펴볼점
먼저 srand()로 같은 시드값을 설정하면 rand()를 여러번 호출한 결과 리스트는 프로그램을 몇번 실행하든 결과 리스트는 동일하다. 즉 시드값을 알면 n번째의 랜덤값을 유추 가능한것이다.
따라서 암호화적으로 안전하지 않다는 것을 의미한다. 이를 어셈블리어로 분석해보자
코드
#include <iostream>
int main() {
srand(10); // 시드값 설정
// 5개의 난수 출력
for (int i = 0; i < 5; i++) {
printf("%d\n", rand());
}
return 0;
}
srand
00651090 55 push ebp
00651091 8B EC mov ebp,esp
00651093 51 push ecx
srand(10); // 시드값 설정
00651094 6A 0A push 0Ah
00651096 FF 15 C4 20 65 00 call dword ptr [__imp__srand (06520C4h)]
0065109C 83 C4 04 add esp,4 //ret ip지점
call 바로 전 스택에 10이 채워진 상태 <- esp
__imp__srand (06520C4h)
761C2520 8B FF mov edi,edi
761C2522 55 push ebp
761C2523 8B EC mov ebp,esp
// mem 1
761C2525 E8 92 E7 FE FF call __acrt_getptd (761B0CBCh)
761C252A 8B 4D 08 mov ecx,dword ptr [ebp+8]
761C252D 89 48 18 mov dword ptr [eax+18h],ecx
761C2530 5D pop ebp
761C2531 C3 ret
mem 1
0x00CFFA0C 1c fa cf 00 .??. // <- ebp,esp (값은 old ebp)
0x00CFFA10 9c 10 65 00 ?.e. // ret ip
0x00CFFA14 0a 00 00 00 // 10 시드값 --> 즉 [ebp+8]은 시드값을 의미
함수가 호출되어 위처럼 진행된 상태임 이후 mov의 결과는 시드값을 ecx와 eax+18h 두 곳에 저장
rand
다음 rand을 실행
call dword ptr [__imp__rand (06520C8h)]
call __acrt_getptd (761B0CBCh)
// 1
imul ecx,dword ptr [eax+18h],343FDh
add ecx,269EC3h
mov dword ptr [eax+18h],ecx
shr ecx,10h
and ecx,7FFFh
mov eax,ecx
ret
1
__acrt_getptd (761B0CBCh) 실행을 하면 ecx는 다른값으로 대체된다. eax+18h에는 그대로 시드값이 남아있다.
이후 중요한점은 계산 중 중간값을 eax+18h에 다시 넣는다는 점이다! 대충 코드로 변환한다면,
seed = (seed * 343FDh) + 269ec3h
[eax+18h] = seed // 계산값을 다시 저장
seed >> 16 // 16비트 우측 시프트
seed = seed & 0x7FFF // 7FFF 하위 15비트 마스킹 (반환값 범위)
return seed
두번째 rand와 결론
똑같은 rand 함수를 실행한다
call dword ptr [__imp__rand (06520C8h)]
이번에 계산이 되는 시드값은 이전 랜드함수 중간의 계산값이였다! 계속 이런식으로 rand를 진행하게된다.
결과적으로 즉 초기 시드값을 안다면 rand()함수의 n번째 랜덤값 유추가 가능한 것
코드로 구현한다면?
#include <iostream>
unsigned int state_seed;
void mysrand(unsigned int seed) {
state_seed = seed;
return;
}
unsigned int myrand() {
unsigned int result;
result = state_seed * 0x343FD + 0x269EC3;
state_seed = result;
result = result >> 16;
result = result & 0x7FFF;
return result;
}
int main() {
srand(10);
mysrand(10); // 시드값 설정
// 5개의 난수 출력
for (int i = 0; i < 5; i++) {
printf("rand: %d\n", rand());
}
// 5개의 난수 출력
for (int i = 0; i < 5; i++) {
printf("myrand: %d\n", myrand());
}
return 0;
}
rand: 71
rand: 16899
rand: 3272
rand: 13694
rand: 13697
myrand: 71
myrand: 16899
myrand: 3272
myrand: 13694
myrand: 13697
몇번을 실행하든 결과는 같다. 직접 구현해보니 이제 rand를 쓸때마다 친숙한 느낌이 들겠다:)