rev-basic-4

main 함수의 전체적인 움직임은 v4라는 배열에다가 사용자로부터 받은 입력 문자열을 저장해둡니다. (라인 5~7)
그 후, 함수 sub_140001000()을 통해서 키값을 대조하여 서로 맞는지 틀린지 비교하는 형태입니다.
함수 sub_1400011C0()과 sub_140001220()은 자세히는 들여다 보지않았지만 아마 각각이 printf와 scanf와 비슷한 동작을 하는것으로 유추됩니다.
키값을 알아내기 위하여 함수 sub_140001000()안을 살펴보겠습니다.

안에는 조건문이 또 하나 존재합니다.
이를 보기쉽게 쓰면 다음과 같습니다.
for ( i=0; i<28; ++i) {
if ((16 * al[i]) | (al[i] >> 4) != byte_140003000[i]){
return 0;
}
}
return 1;
>> 여기서 연산자 |은 OR 연산이며, 연산자 >>는 우측 시프트 연산입니다.
인자로 받은 al 배열에 대해서 16을 곱한 값과, al 배열을 4만큼 우측 시프트 연산한 값을 OR연산을 하여
이 값이 byte_140003000 배열의 문자열과 같은지 틀린지 확인하는 것입니다.
먼저 역연산 과정을 생각해보겠습니다. al[i]를 P, byte_140003000[i]를 Q라고 하겠습니다. 이에 따르면
(P * 16) | (P >> 4) = Q 이 때, P * 16이라는 연산은 조금 특이합니다. 예를들어 a라는 문자열을 들어보자면,
a = 0110 0001 입니다. 여기다 16진수 16을 곱해주면 다음과 같습니다.
a * 16 = 0110 0001 0000 왼쪽으로 4비트가 밀리고 그 자리가 0으로 채워졌습니다. 이는 좌측 시프트 연산(<<)과 동일한 동작입니다.
즉,
(P << 4) | (P >> 4) = Q 가 됩니다.
여기서 직관적으로 봤을 때
하나의 문자열을 왼쪽으로 4비트, 오른쪽으로 4비트 옮기는 각각의 행위는 마치 앞/뒤 4비트를 서로 바꾸는 것처럼 보여집니다.
보다알기 쉽게 이러한 동작응 아까 문자열 a를 가지고 예로 들어보면, a = 0110 0001
a << 4 = 0110 0001 0000
a >> 4 = 0110 여기서 위로 가셔서 다시 조건문을 들여다보시면, 해당 조건문에 연산값을 unsigned int8로 캐스팅을 하는것을 볼 수 있습니다. 8비트만 보겠다는 얘기입니다.
정리하자면 다음과 같습니다 a = 0110 0001 일 때,
a << 4 = 0001 0000
a >> 4 = 0000 0110
각각의 시프트 연산을 거친 결과와 a를 비교해보면 문자열의 앞/뒤를 각각 표현하고있습니다.
여기다 OR연산을 거쳤을 때 다음과 같습니다
(a << 4) | (a >> 4) = 0001 0110
처음 문자열 a와 정확히 앞/뒤 4비트가 서로 바뀌었습니다.
이는 비단 a뿐만 아니라 아스키 코드 전부에 적용됩니다.
최종적으로 우리에게 필요한 역연산 공식은 다음과 같습니다.
(Q * 16) | (Q >> 4) = P
이는 다시,
(byte_140003000[i] * 16) | (byte_140003000[i] >> 4) = al[i]
입니다.
이제 배열 byte_140003000[]을 살펴보겠습니다.

반복문이 총 27회만 동작하므로, 뒤의 5개의 0은 제외하고 역연산 코드를 짰습니다.
#include <stdio.h>
int main(void) {
unsigned char byte_140003000[27] = {
36, 39, 19, 198, 198, 19, 22, 230, 71, 245, 38, 150, 71, 245, 70, 39, 19, 38, 38, 198, 86, 245, 195, 195, 245, 227, 227};
for(int i = 0; i < 27; ++i) {
printf("%c", (byte_140003000[i]*16) | (byte_140003000[i] >> 4));
}
printf("\n");
}
replit으로 테스트 결과 키값이 잘 출력됩니다.
