리버스 엔지니어링

리버싱 연습 - abex crackme2 (3) 시리얼 핵심 루틴

Injel me 2020. 6. 30. 19:47
abex crackme2를 풀 수 있는 방법은 정해져 있지 않다.

시리얼을 알아낼 수 있는 방법 정도로 풀 때,
비교할 시리얼이 어떻게 만들어지는지 핵심 루틴이 궁금했다.

시리얼이 어떻게 만들어지는지는 조금 분석 따라가기가 버겁다.
리버싱 핵심원리에서 읽었고, 읽어본 대로 책을 이후에 보지 않고 따라가 보았다.

버튼 이벤트 리스너 함수에서 내리다보면 Name 필드에 입력한 값을 복사하는 명령어가 있다.
그 아래에도 Name 필드의 값을 복사하고
VbaVarMove 함수를 호출한다.
아래에 자주 호출되는 VbaVarMove 함수는 무슨 내용인지 자세히 모르겠다.
조금 더 디버깅하면 VbaLenVar 이라는 함수가 나오는데 함수 호출 전에 4를 Local.53에 복사하는 것을 볼 수 있다.
VbaLenVar은 문자열의 길이를 구하는 함수 같았다.
그대로 디버깅 해 따라가 보았다.
VbaLenVar의 함수 모습
처음의 함수 프롤로그와 TlsGetValue 등의 함수를 출력하는 곳까지는 의미 없어 보인다.
사실 뭐 하는 내용인지 확실히는 모르겠다.

위의 캡처 사진에서 Eax에 있는 Name 필드의 값이 들은 주소를 스택에 한 번 더 넣고 내부에서 함수를 한 개 더 호출한다.
호출된 함수의 모습이다.
사실 잘 모른다고 했던 호출 부분들에서 이미 문자열의 길이를 구했었나보다.
유니코드로 된 문자열이 할당된 길이가 구해진? 위치는 Eax - 4였고,
Eax에 그 값을 복사해 왔을 때 0xA였다.

그 다음에 오른쪽으로 1비트 쉬프트 연산으로 0x5가 되었고, 이는 유니코드의 문자열이 잡아먹는 길이가 2비트씩 되기 때문에 char 형 문자열 길이(실제 문자만의 길이)를 구하기 위해 shift 연산을 한 것 같다.

abcde는 61 00 62 00 63 00 64 00 65 00으로 저장되어 있을 것이고, 차지하는 공간이 10인데 실제 문자열의 길이는 5니 1비트 쉬프트 연산을 해서 값의 1/2를 한 5가 나온 것이다.


호출된 함수에서 값이 리턴되고 나서 Eax에 문자열의 길이의 값이 저장되어 있는데, 그 값을 edi + 8의 위치에 복사한다.
그 이후에 함수를 리턴한다.

다시 아까 이벤트 핸들러 함수의 위치로 돌아와서,
아래의 VbaVarTstLt 함수를 디버깅 따라 가 보기로 했다.

조금 헤매다가, 어쩐지 아까 구한 문자열의 길이와
4를 비교하는 식이 있었다.
아래의 창에서 Ecx와 Esi + 8에 있는 주소에 있는 값을 볼 수 있다.
문자열의 길이가 4 이하일 때 뜨는 메세지와, 내가 넣은 abcde의 문자열의
길이인 5를 비교하는 것을 봤을 때 문자열의 길이를 비교해서
if - else문으로 나뉘어져 있는 형태처럼 보인다.
그 다음 줄에서 먼저 점프를 해서 위로 올라간 후
JL 명령어로 4보다 작을 때 점프하게 된다.
점프하지 않게 된 후 중간의 함수를 나오게 된다.
그런데 함수를 나오고 나서 보니 호출한 함수명이 VarCmp라는 것을 알게 되었다.
strcmp와 비슷한 맥락의 함수같다.

함수를 계속 따라가서 나오다 보면 다시 이벤트 핸들러 함수쪽으로 리턴 해 오게 된다.
Ax와 Ax를 Test 연산 한 값을 보고 점프를 한다.

따라갔던 함수는 매개 변수인 문자열의 구조체 위치와, 비교하고 싶은 길이를 넣고 비교해 리턴값으로 연산 결과를 Eax 레지스터에 반환하는 함수라고 정리하면 될 것 같다.

그 아래는 이전에 실습했던 것처럼 점프를 뛰지 않으면 문자열의 길이가 4보다 적기 때문에 그에 적절한 메시지를 띄우게 된다.

아래로 내려와서, 또 다시 문자열의 길이를 비교하는 모습이 보인다.

이전에 생각했던 것처럼 if - else 구문이 아닌 if - if 로 if문을 2개 연달아 쓴 형태같다.

두 번째 비교 - 점프문은 점프하지 않았을 때가 아닌 점프했을 때 이벤트 핸들러 함수를 리턴시키게 진행이 된다. 그러므로 문자열의 길이가 4 이상이면 점프하지 않는다.

아래로 천천히 따라가다 보면 루프문이 시작될 것 같은 모양의 위치가 있다.
리버싱 핵심원리 책에서 나왔듯 VbaVarForInit는
링크드 리스트의 연결된 객체들이 Next 포인터를 통해 다음 객체를 따라가는 것처럼 문자열 객체를 문자 하나하나 따라갈 수 있게 반복문에서 사용한 것 같다.
함수를 따라가다 보니 헤매어서 Ctrl + F9(Execute Until Return)키를 이용해서 함수를 나왔다.
이후에 필요 없어 보이는 곳이나 이해가 되지 않는 부분은 과감하게 스킵해가면서 플레이했다.
위의 캡처 사진에서 Call Ebx를 따라가다가 나온 위치.
따라가기 벅차지만 어떻게 디버깅 해 가다 보니 내가 넣은 Name을 함수에 인자로 넣는 곳을 찾았다.
abcde가 있는 주소를 인자로 스택에 넣는 모습
따라가다가 문자열이 있는 주소를 복사해서 Ebx에 넣고, Eax가 가지고 있는 값을 더한다. 이 값은 원하는 문자열의 다음 문자를 뽑기 위해 Ebx가 가리키고 있는 주소에 (문자의 개수 * 2(유니코드는 2바이트를 차지하기 때문에)) 를 더해 가리키게 한다.

열심히 따라가서 핸들러 함수까지 다시 복귀 해 왔다.

쭉 내려가서 VbaStrVarVal 함수를 마주치게 된다.
함수를 호출 후 나오게 되면 Eax의 값에 반복문을 거친 N 번째 문자를 뽑아낸 주소가 나온다.
Hex Dump 창으로 주소를 따라갔을 때 0x63(알파벳 'c')를 찾을 수 있다.
반복문을 2번 더 실행시켜 현재는 c가 구해져 있다.
그 주소를 스택에 넣고 또 다른 함수를 실행시킨다. 따라가보자.
함수의 내부인데 중간에 WideCharToMultiByte라는 함수 호출이 보인다.
함수 이름만 봐도 문자를 멀티바이트 유닛으로 바꾸는? 함수같아 보인다.
실제 함수의 하는 일은 유니코드를 멀티바이트로 바꿔주는 함수라고 한다.

하는 일은 알았지만 디버깅으로 따라갔을 때 어떻게 작동하는지는 이해를 하진 못했고, 빠르게 함수를 나왔다.

큰 함수도 리턴식을 거쳐 다시 핸들러 함수로 왔다. 리턴했을 때 Eax에는 'c'의 16진수 숫자로 0x63이 저장되어 있었다.

아직 문자 하나를 어떻게 바꾸는지는 나오지 않았다.

더 내려와보니 VbaVarAdd함수라는게 나왔다. 지금까지의 행동으로 보건대 Var~ 함수는 문자 또는 데이터를 어떻게 수정하거나 데이터를 가지고 검증하거나 하는 데이터 관련된 함수 이름들이다. 문자를 가져온 값을 가지고 얼마를 더한다는 함수일지도 모른다.

함수를 호출하기 전에 Local.53에 0x64의 값을 복사해 넣는 것이 보인다. 함수를 따라가 보자.
함수를 시작하자마자 바로 인자를 넣고 내부에서 함수를 또 호출한다. 따라가보자.
따라가다보니 아래에 익숙한 숫자가 보여서 멈췄다. Esi + 8에 있는 0x63은 아스키코드로 'c'를 의미하고, 75DC065E의 주소에 있는 실행문에 있는 Edi + 8은 인자로 넣었던 0x64이다.
그 다음 줄에선 Edx에 Eax를 더하는 것을 볼 수 있는데, 윗 두 실행문을 거치면
EDX에 뽑아왔던 문자가, Eax에 0x64가 있게 된다.
더해진 값은 C7이 되었다.

뽑아온 문자 한개를 0x64와 더한다는 것을 알게 되었다.

함수에서 빠져나오고 나서, 좀 더 내려와 보니 함수를 호출하는 부분이 있는데, 인자를 넣기 전에 Local.21과 Local.39에서 주소를 가져온다는 것이 보였다.
저 주소에는 무엇이 담겨 있을까 싶었는데 우연찮게도 Hex Dump 창에서 저 주소의 9번째 바이트에 아까 계산했던 C7이 담겨있다. 이것은 할당받은 문자열의 구조체 주소인가?

그 다음에 호출되는 함수 이름이 특이하다.
rtcHexVarFromVar이라는데, 값에서 Hex값을 얻어온다?라는 뜻 같아보인다.

리버싱 핵심원리에서 자세히 나오는데, 멀티바이트로 지정된 N번째의 뽑아온 문자에 0x64를 더하는 연산을 거치고 나서, 저 함수에서 C7이라는 데이터를 문자 자체로 사용하기 위해 호출하는 함수였다.
어떻게 거치고 나면 C7을 문자열 자체로 사용할 수 있게 바꿀 수 있을까.

빠르게 함수 안에서 호출하는 함수까지 들어가보자.

쭉 따라가다보니 루프문 형태를 띄는 곳이 보였다.
굉장히 이해하기 힘들었는데, 루프문에 처음 들어갈 때 AL과 CL에 들어가있는 데이터가 C7인 것을 보고 이것 또한 중요한 반복문인게 보였다.

AL에서 CL로 원본 데이터를 복사한다. 그 다음 0xf를 And연산하는데, 자릿수가 2자리 이상이면 char 형태로 자르려고 And 연산을 한 것으로 보인다.

그 다음 실행문도 비슷한 내용같은데, 정확히는 모르겠다.

Add Ecx, 30과 Cmp Cx, 39는 처음에 몰랐지만 오픈톡방에서 질문을 통해 알아낸 답으로는 0~9인지 확인하는 것이라고 한다.
0~9이면 Ecx에 7을 더하는 연산을 더 하게 된다.

그 이후에 EDI를 1 줄이고, EDX를 2 줄이는데, 예상으로는 EDI는 char 형태의 사이즈인 1을 빼고 EDX는 유니코드의 크기인 2를 빼서 연산 반복 횟수로 가지고 있는 것 같았다.

마지막으로 다시 돌아가기 전에 Eax에 4만큼 오른쪽으로 쉬프트 연산을 하는데, C7을 가지고 있던 Eax에 쉬프트 연산을 하면 7이 밀려서 C만 남게 된다.

그 후에 EDX가 가리키는 주소에 CX의 값을 넣는다. 루프문을 반복하게 되면 C와 7을 스택에 넣듯이 EDX - 4에 C가, EDX - 2에 7이 들어간다.

Word 크기로 넣은 문자를 문자열로 읽으면 낮은 비트부터 읽게 되니까
7C가 아닌 C7로 읽어야 한다.

함수를 나오고 나면 0xC7이 문자열 'C7'이 되게 된다.

다시 함수를 나오고 나서, 다음 호출 함수는 VarCat라는 이름이 붙은 함수가 나온다. c언어의 strcat처럼 뒤에 문자열을 붙이는 함수인 것 같다. 실제로도 그렇다.

비교해야 할 시리얼을 담을 할당받은 메모리에 방금 구한 문자열을 뒤에 붙이는 행동을 하게 된다.

곧 핵심 루틴 루프문이 끝나게 되는데, 처음에 나왔던 VbaVarForInit함수와 관련된 함수를 호출하는데, 리버싱 핵심원리 책에서 설명되어있다.
마치 문자열을 링크드 리스트에서 Next를 통해 찾아가듯이, 마지막 함수에서 Next가 가리키는 주소를 따라가게 해 주는 함수인 것이다.

그 다음에 바로 점프문에 이어서 다시 루프문이 시작하는 부분에 도달한다.

루프문이 돌아가는 형식은 말그대로 문자열에서 문자를 한 개씩 뽑아서,
뽑은 문자열을 따로 할당한 뒤 멀티바이트로 바꿔주는 함수를 호출해주고,
그 문자에 0x64를 더하는 연산을 한 뒤, 더해진 값을 문자열 자체로 바꿔서
시리얼을 담을 할당받은 메모리에 문자열을 이어 붙여서 시리얼을 만든다.

그리고 반복은 총 4번 하게 된다. 문자열이 아무리 길어도 문자열의 첫 4개만 사용해서 시리얼을 만들게 된다.

Name이 abcd일 때,
'a' + 0x64 = 'C5'
'b' + 0x64 = 'C6'
'c' + 0x64 = 'C7'
'd' + 0x64 = 'C8'

이렇게 거쳐서 최종 시리얼은 'C5C6C7C8'이 되는 방식이다.