해당 링크에서 crackme2.exe 파일을 다운받아서 진행하는 실습이다.
401238 : 401E14 구조체를 스택에 push해
40123D : 401232를 Call해줘
(JMP .&ThunRTMain을 호출해달라는데 그게 401232의 label이다.)
401232 : ThunRTMain()로 이동하자.
여기서 한가지 특징은 바로 간접호출이다. ThunRTMain()을 40123d가 직접 점프하는 것이 아니라 401232를 call 시켜서 점프하게 만드는 방식이다. 이런 indirect call은 VC++나 VB 컴파일러에서 많이 사용하는 기법이라고 한다.
참고로 401e14 구조체는 ThunRTMain() 함수의 파라미터인 RT_MainStruct 구조체이다. 무슨 구조체냐구여? 저도 모르죠... 이게 어떤 과정으로 진행되는지 상상해보면 좋다.
사용자가 문자 입력 -> 입력 문자와 정답값 비교 -> 틀릴 시 wrong serial 메시지 출력
이러지 않겠는가. 그럼 우리는 저 문자열 근처에 무언가를 비교하는 구문을 찾으면 된다.
스크롤을 조금만 위로 올리면 두개의 비교 구문이 나온다.
이것과
이것 이렇게 두개이다. 뭐가 우리가 찾는 건지는 책에서 안 나왔지만 내가 추측해보았다.
wrong serial 메시지의 주소는 403458이다. 위의 jump는 403408로 이동하고 아래의 jump는 4034F1으로 이동한다.
403408 : 첫번째 구문 jump |
403458: wrong serial 메시지 |
4034F1 : 두번째 구문 jump |
즉 두번째 사진이 입력값과 정답 serial을 비교하는 것이라면 틀릴 시 wrong serial을 건너뛰어서 점프해버리게 된다. 안되겠지. 그러므로
얘가 맞아.
이제 자세히 살펴보면
ebp-44 주소의 값을 edx에 넣고, ebp-34 주소의 값을 eax에 넣네. 그리고 edx랑 eax 값을 각각 스택에 push하고서 <___vbaVarTstEq>를 호출하고 있어.
즉 <___vbaVarTstEq>가 문자열 비교 함수라면 edx랑 eax는 그 함수의 들어갈 파라미터 값이기에 스택에 push 됐을 것이다.
SS:[EBP-44] 에서 SS는 스택 세그먼트인데, 즉 이 주소는 스택 내 주소이다. 바로 함수에서 선언된 로컬 객체 주소이다. (로컬 객체는 스택 영역에 저장되니까)
확인해보자. 403329에 bp를 걸고 실행해보자.
eax와 edx의 값이 각각 19f158과 19f148이 들어있다. 스택을 확인해보자.
이렇게만 봐서는 모르니까 스택주소 우클릭-> 덤프에서 dword 따라가기로 보자.
![]
VB에서의 문자열은 가변 길이 문자열 타입을 사용한다. 즉 여기에 바로 문자열이 보이지 않고 16바이트 크기의 데이터인 문자열 객체가 나타난다. 이 값은 마치 메모리 주소처럼 보인다.
10번째, 11번째 바이트가 다른 것을 알 수 있다.
우클릭해서 '스택에서 따라가기'를 눌렀다.
아하 19F148에는 정답 serial 인 "C5C8D1CD"가 있었고 19F148엔 내가 입력한 'passkey'가 있었구나.
알아낸 시리얼로 다시 시도해보자 우왕. 그치만 이 serial은 모든 name에 대해서 유효한 것이 아니다. admin이라는 name에만 유효하게 만들어진 serial이다. 같은 serial이지만 name이 다르면 실패한다. 즉 name을 기반으로 serial이 그때 그때 생성된다는 것이다.아까 찾았던 조건 분기 코드는 어떤 큰 함수 안에 속해있을 것이다.
왜냐면 아까 break point까지 실행했을 때 Check 버튼을 눌러야만 문자열 비교 함수가 호출되었으니까. 그럼 우리는 뭘 찾아야 하냐?
Check 버튼의 event handler를 찾아보자.
코드 스크롤을 올려보자.
이렇게 언제 함수가 찾아질 줄 알고 쭉 스크롤을 올려야하는지 나도 공감이 안된다. 그치만 올려보면 지난 시간에 스택 프레임을 공부했을 때 익숙했던 무언가가 나온다.
push ebp
mov ebp,esp
sub esp, c
이거 다 뭐였어? 새로 함수를 호출했을 때 그 callee 함수의 스택프레임을 만들어주는 과정이었다. 즉 402ED0 여기가 이 Check 버튼의 event handler인 것이다.
참고로 nop 명령어는 아무 동작도 하지 않고 그냥 cpu클럭만 소모하는 명령어이다.
402ed0에 bp를 걸고 디버깅하자.
디버깅하기 전에 어떤 코드가 구현되어야 할지 예측해보는 것이 좋다고 한다. 내가 예상해본 구상은
Name 문자열을 가져와서 읽기 -> 문자를 암호화 -> Serial 생성
이런 거 아니겠어?
스택에 쌓인 문자열을 가져오는 call 명령어를 유심히 보면서 디버깅해보자. 어떻게 하는지 혹시 모르는 분들은 name에 이름 넣고 serial에 임의 값 넣고 check 버튼 누르면 실행되면서 F8로 하나씩 디버깅하면 된다.
참 Call이 많은데 이 중에서 뭐로 짚어내는 건지 잘 모르겠다. 책에 따르면 402f8e가 로컬 객체 ss:[ebp-88]를 edx로 가져와서 스택에 push하는 과정을 거친다. 즉 함수의 파라미터로 전달하는 과정이다.
ebp-88 주소를 덤프로 따라간 후, 스택으로 보면 내가 입력한 새로운 name인 "irip" 문자열을 볼 수 있다.
먼저 루프문을 찾을 수 있다.
이게 왜 루프의 시작인지는 모르겠다. 시작이라니까...
ebx에 4를 넣어주는 이유는 ebx가 loop의 카운트 횟수를 담당하게 된다. 즉 몇번 반복할 것인지를 결정한다.
더 내려가면 403197을 만난다. 여기가 loop의 시작점이다. test 논리 연산을 진행하고서 그 결과로부터 루프의 진입과 탈출이 결정지어진다. 루프의 점프문인 4032a5로 가보자.
4032a0 여기가 루프의 마지막이다. 다시 403197로 되돌아가는 점프 보이지?
또한 위의 vbaVarForNext 또는 vbaVarForInit 같은 함수들은 linked list에서 next pointer를 이용해서 element를 참조하듯이 문자열 객체에서 한 글자씩 참조할 수 있게 해준다.
4031f6 push eax : name문자열에서 가져온 하나의 유니코드 문자
4031f7 이게 뭐냐면 가져온 유니코드 값을 ASCII 코드 값으로 바꾸라는거야.
그 코드값을 edx에 넣으렴
여기까지 실행시켰을 때 스택을 관찰해보자.
EAX : 19F0B0
ECX : 19F0F0
EDX : 19F138
메모리 주소 한번 가보자.
우선 EAX부터
암호화 키값인 '64'가 들어있다.
ECX 가보자
덤프에서 본 것을 스택에서 따라간 것이다. 값이 비어있는 것이 여기는 암호화한 결과를 저장하는 버퍼이다.
Name 문자열에서 가져온 걸로 만든 아스키 코드값 69가 들어있다.
이제 ___vbaVarAdd를 진행하게 되면 아스키 코드값과 키값을 더하게 된다.
즉 69+64
403243까지 실행하고 났을 때의 ecx를 보면 계산값 CD를 가진다.
그후 40325b에서 이 아스키값을 다시 유니코드 값으로 바꾸게 된다. 이 함수까지 실행하고 나면 eax는 다음과 같이 된다.
즉 eax가 아까의 계산값 cd를 유니코드 문자열 값으로 보존하고 있다.
마지막으로 두 문자열을 이어 붙이는 함수가 있다.
이 루프를 실행하면 이런 식이다.
1. 주어진 name 문자열 앞에서부터 한글자씩 읽기
2. 문자를 아스키 숫자로 변환
3. 변환된 숫자에 64 더 함
4. 숫자를 다시 유니코드 문자로 변환
5. 변환된 문자를 연결시킴
6. 4번 반복