편의상 ButtonListner라는 레이블을 붙여보자.
주요 내용을 훑어보자.
함수 프롤로그는 대충 보고 GetDigItemTextA라는 함수를 호출하는 것이 보인다.
텍스트박스의 텍스트를 가져오는 함수인 것 같다.
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getdlgitemtexta#parameters
대화 상자의 핸들러, 컨트롤러의 아이디?, 문자열을 입력받을 버퍼 위치, 버퍼의 크기 순 인자인것 같다. 스택에 들어가는 순으로 보면
eax가 가장 마지막 인자이고, 인자가 총 3개 들어가는 것을 보니 getDigItemTextA의 상위 함수 중 문자열 길이를 받지 않는 3개의 인자를 받는 함수를 사용한 듯 싶고, eax에 들어가는 주소가 문자열을 받아 저장하는 버퍼의 주소인 듯 싶다.
함수를 호출 후 eax에 들어있는 주소를 덤프 창에서 한번 확인 해 보자.
0019F6F4엔 아무것도 없다. 함수를 호출하고 나면,
메모리에 입력했던 123이 들어간 것을 볼 수 있고, 함수의 리턴값을 받아온 eax가 3의 값을 가지는 것을 볼 수 있다. 3은 문자열 길이로 추정된다.
여길 다시 보면, 함수를 호출하고 나와서 스택을 정리하지 않고 바로 실행문이 나오고, 이미 정리된 스택의 위치를 가리키는 esp스택의 4바이트 뒤에 다시 저장된 문자열의 주소가 나온다.
함수를 호출하고 난 뒤의 바로 다음 실행문은 비교를 위한 cmp문이다.
esp + 5는 문자는 4바이트 뒤부터 시작하는 문자열의 2번째 문자로서 123에서 2를 가리키고 있다.
'2'와 'a'를 비교하고 있다.
jne 401135인데, 같지 않으면 점프한다.
점프된 곳을 따라가보자.
Incorrect Password를 인자로 넣은 메시지 박스를 띄우고 리턴한다. 이 주소가 password를 검사하는 루틴에서 틀렸을 때 점프되는 곳이다. 간단히 레이블을 붙였다.
다시, 검사하는 곳으로 와서
password를 검사하는 첫 루틴은 문자열의 두 번째와 'a'를 비교해 같아야 하는 것이다.
빠른 진행을 위해 nop로 바꿔 넘겼다. 다음 루틴을 봐 보자.
스택에 2를 넣고, esp + A에 있는 주소를 ecx에 넣어서 ecx도 스택에 넣고, "5y"라는 문자열이 들은 주소도 스택에 넣고 401150주소를 call한다.
하나 하나 실행해 가며 진행을 해 보자.
먼저, push 2
스택에 2를 넣어서 esp가 가리키는게 바뀌었다.
다음 실행문에서 esp + A의 주소를 ecx에 넣는데, 자세히 보면
현재 esp가 가리키는 데서 A만큼 더해진 주소 0019F6EC + A = 0019F6F6은
내가 입력한 문자열을 가져온 버퍼 주소와 근접하는 것을 볼 수 있다.
버퍼의 첫 시작 주소는 F6F4에서부터 '1'이 시작하니, F6F6은 세 번째 문자인 '3'이 들어있을 것이다.
실행문의 주소 등의 내용을 알려주는 창에서도 문자 '3'이 들어있는 것을 볼 수 있다.
ecx에 문자열 중 3번째의 문자 주소를 넣고 스택에 넣는다.
스택에 넣는 세 개의 실행문을 다 거치고 난 뒤, 2, "5y"가 들어가 있는 문자열의 주소, 버퍼에서 3번째 문자가 시작되는 주소까지 들어가게 됐다.
함수에서는 순서가 거꾸로 들어갈 것이다.
함수를 호출시켜보기 전에 함수를 호출시키고 난 뒤의 실행문을 먼저 보려고 한다.
함수를 실행시키고 나서 스택 포인터에 C를 더해서 스택을 정리하는 것을 볼 수 있다.
함수의 호출 규약이 _stdcall인 것을 볼 수 있다. 이런 것도 리버싱 공부에 사소하지만 중요하다고 생각한다.
아무튼, 본론으로 들어와서 함수를 호출 후, eax를 test연산해서 플래그가 0이 아니면 점프하게 된다.
test실행문은 두 오퍼랜드간 비트 &연산을 하며, 결과는 저장하지 않고 플래그 수정만 하게 된다.
보통 비교문을 cmp를 사용하는데, test연산으로 같은 레지스터를 연산하면 레지스터에 0이 이 아닌 값이 있으면 점프를 하게 된다.
같은 값을 &연산 하게 되면 레지스터의 값이 0이 아닌 이상 제로플래그는 0이 되기 때문에 레지스터에 아무 값도 없어야 test연산을 했을 때 제로플래그가 1이 된다.
함수를 호출 후 반환받은 eax의 값이 0이 아닌 다른 값으로 수정되면 검사 루틴에서 걸리게 되어 틀리게 된다.
eax의 값에 유의하면서 함수를 살펴보도록 하자.
크게크게 흐름을 잡아보자. 함수의 형태이다.
스택 프레임 기법에 의한 함수 프롤로그가 있고, 중간을 좀 넘어서 cmp문이 있다.
ecx와 eax에 유독 많은 연산을 하게 되는데, 함수의 리턴 값은 보통 eax레지스터에 저장된다는 것을 유의하면서 보도록 해 보자.
중간중간에 같은 레지스터를 xor연산을 하는 모습이 보이는데, 같은 레지스터를 xor 연산을 하면 레지스터의 값이 0이 되게 된다.
eax를 0으로 만들어서 리턴을 하게 되면 루틴에서 틀리지 않게 되는데,
cmp문을 거쳐 무언갈 비교하고 나면 같을 때 ecx의 값을 eax로 옮기고 함수를 마치게 된다.
cmp문의 바로 직전 실행문은 xor ecx, ecx이고, ret 이전의 점프문은 저 비교문이 마지막이다. eax를 연산하는 다른 것을 모른다고 해도 저 부분만 보면 간단히 풀 수 있게 된다.
eax의 값이 0이 된 채 ret하게 되면 루틴을 클리어 할 수 있게 되고, cmp문에서 비교하는 것이 같아야 0이 된다는 것을 비로소 깨닫게 되었다.
그럼 비교하는 식은 무엇인가 조금 위를 보면서 해석해보자.
우선, 위에서 esi와 edi가 무슨 일을 도맏게 되는지 살펴보자.
edi에 함수의 인자 첫 번째를 넣고, esi에는 인자의 두 번째를 넣는다.
repe cmpsb는 ecx를 카운터로 해서 REPeat when Equal하며 CMP String Byte하라는 것이라고 해석하면 된다고 한다. 아직 어셈블리어 전체를 다 알지 못하여 찾아보았다.
다시 말하자면 strcmp같이 문자 하나하나를 현재 edi와 esi에 있는 것을 string byte(문자열을 문자 하나하나)로 반복해 가면서 같은 동안 비교해라는 뜻이라고 해석하면 될 것 같다.
버퍼의 세 번째 문자랑 "5y"의 문자랑 비교해 가면서 esi와 edi를 줄인다는 것인데, 내가 입력했던 값인 "3"과 "5y"는 당연히 같지 않다.
그럼 repeat하면서 두 문자열이 정확히 같으면 repe 실행문 이후에 esi와 edi가 가리키는 위치는 둘 다 처음 가리킨 문자열의 맨 뒤 + 1일 것이다.
둘 다 같은 문자열인 "5y"였으면 "5y"의 뒤를 가리키게 되는 것이다.
본론으로 와서, repe cmpsb를 거치면서 두 문자열을 가리키는 edi레지스터와 esi레지스터가 가리키는 값들은 변경될 것이다.
그 후에 esi - 1과 edi - 1의 값을 찾게 되는데, 반복 실행되고 난 뒤의 두 문자열의 끝을 가리키는 주소에서 1을 뺀 값은 문자열의 끝 문자일 것이다.
처음에 내가 넣은 값으로 생각해보자.
"123"문자열을 넣었을 때 3의 위치가 인자로 들어가게 되었고, "3"과 "5y"를 문자 하나하나 비교해 가며 주소를 바꿨을 때 '3'과 '5'는 같지 않으므로 cmpsb를 딱 한번 실행해 esi는 'y'를 가리키고 edi는 '3'의 뒤인 NULL값을 가리킬 것이다.
넣은 값이 "5y"와 같다고 가정해보자.
"125y"문자열을 넣었을 때 5의 위치가 인자로 들어가게 되었고, "5y"와 "5y"를 문자 하나하나 비교해 가며 주소를 바꿨을 때 두 문자열은 정확히 같으므로 cmpsb가 두 번 실행될 것이고, 두 레지스터는 두 문자열의 끝인 위치를 가리키게 될 것이다.
문자열 비교 연산을 실행한 후 cmp에서는 edi - 1의 값과 al, 즉 esi - 1의 값을 비교하는데, 두 문자열이 정확히 같다면 esi -1 과 edi - 1의 문자도 같을 것이다.
즉, repe cmpsb가 문자열이 정확히 같은지 edi와 esi레지스터의 값이 바뀌어 가면서 실행되고, 문자열이 같았다는것을 cmp문에서 한 번 더 검증하게?된다고 생각하면 될 것 같다.
je하게 되면,
아까 생각했던 eax에 0을 넣고
리턴을 하게 된다.
반대로 ja하게 되면
ecx에 not연산 등이 추가되며 eax에 0이 아닌 값이 들어가게 된다.
결국 이 함수의 루틴은 내가 넣은 문자열의 세 번째, 네 번째 문자가 "5y"와 같아야 한다는 것이다.
일단 빠른 진행을 위하여 강제 점프를 하게 해 깬것처럼 실행하게 하였다.
함수가 리턴되고 난 뒤 test문을 지난 뒤 점프하지 않게 된 것을 볼 수 있다.
두 번째 루틴 클리어다.
현재 두 번째 루틴까지 알아낸 것은
1. 입력한 문자열의 두 번째 문자가 'a'여야 한다.
2. 입력한 문자열의 세번째 문자부터 정확히 두 문자가 "5y"여야 한다.
password는 모르는 부분을 X라고 하고,
Xa5yXXXXXXXX~ 까지 풀었다.
주요 내용을 훑어보자.
함수 프롤로그는 대충 보고 GetDigItemTextA라는 함수를 호출하는 것이 보인다.
텍스트박스의 텍스트를 가져오는 함수인 것 같다.
대화 상자의 핸들러, 컨트롤러의 아이디?, 문자열을 입력받을 버퍼 위치, 버퍼의 크기 순 인자인것 같다. 스택에 들어가는 순으로 보면
eax가 가장 마지막 인자이고, 인자가 총 3개 들어가는 것을 보니 getDigItemTextA의 상위 함수 중 문자열 길이를 받지 않는 3개의 인자를 받는 함수를 사용한 듯 싶고, eax에 들어가는 주소가 문자열을 받아 저장하는 버퍼의 주소인 듯 싶다.
함수를 호출 후 eax에 들어있는 주소를 덤프 창에서 한번 확인 해 보자.
0019F6F4엔 아무것도 없다. 함수를 호출하고 나면,
메모리에 입력했던 123이 들어간 것을 볼 수 있고, 함수의 리턴값을 받아온 eax가 3의 값을 가지는 것을 볼 수 있다. 3은 문자열 길이로 추정된다.
여길 다시 보면, 함수를 호출하고 나와서 스택을 정리하지 않고 바로 실행문이 나오고, 이미 정리된 스택의 위치를 가리키는 esp스택의 4바이트 뒤에 다시 저장된 문자열의 주소가 나온다.
함수를 호출하고 난 뒤의 바로 다음 실행문은 비교를 위한 cmp문이다.
esp + 5는 문자는 4바이트 뒤부터 시작하는 문자열의 2번째 문자로서 123에서 2를 가리키고 있다.
'2'와 'a'를 비교하고 있다.
jne 401135인데, 같지 않으면 점프한다.
점프된 곳을 따라가보자.
Incorrect Password를 인자로 넣은 메시지 박스를 띄우고 리턴한다. 이 주소가 password를 검사하는 루틴에서 틀렸을 때 점프되는 곳이다. 간단히 레이블을 붙였다.
다시, 검사하는 곳으로 와서
password를 검사하는 첫 루틴은 문자열의 두 번째와 'a'를 비교해 같아야 하는 것이다.
빠른 진행을 위해 nop로 바꿔 넘겼다. 다음 루틴을 봐 보자.
스택에 2를 넣고, esp + A에 있는 주소를 ecx에 넣어서 ecx도 스택에 넣고, "5y"라는 문자열이 들은 주소도 스택에 넣고 401150주소를 call한다.
하나 하나 실행해 가며 진행을 해 보자.
먼저, push 2
스택에 2를 넣어서 esp가 가리키는게 바뀌었다.
다음 실행문에서 esp + A의 주소를 ecx에 넣는데, 자세히 보면
현재 esp가 가리키는 데서 A만큼 더해진 주소 0019F6EC + A = 0019F6F6은
내가 입력한 문자열을 가져온 버퍼 주소와 근접하는 것을 볼 수 있다.
버퍼의 첫 시작 주소는 F6F4에서부터 '1'이 시작하니, F6F6은 세 번째 문자인 '3'이 들어있을 것이다.
실행문의 주소 등의 내용을 알려주는 창에서도 문자 '3'이 들어있는 것을 볼 수 있다.
ecx에 문자열 중 3번째의 문자 주소를 넣고 스택에 넣는다.
스택에 넣는 세 개의 실행문을 다 거치고 난 뒤, 2, "5y"가 들어가 있는 문자열의 주소, 버퍼에서 3번째 문자가 시작되는 주소까지 들어가게 됐다.
함수에서는 순서가 거꾸로 들어갈 것이다.
함수를 호출시켜보기 전에 함수를 호출시키고 난 뒤의 실행문을 먼저 보려고 한다.
함수를 실행시키고 나서 스택 포인터에 C를 더해서 스택을 정리하는 것을 볼 수 있다.
함수의 호출 규약이 _stdcall인 것을 볼 수 있다. 이런 것도 리버싱 공부에 사소하지만 중요하다고 생각한다.
아무튼, 본론으로 들어와서 함수를 호출 후, eax를 test연산해서 플래그가 0이 아니면 점프하게 된다.
test실행문은 두 오퍼랜드간 비트 &연산을 하며, 결과는 저장하지 않고 플래그 수정만 하게 된다.
보통 비교문을 cmp를 사용하는데, test연산으로 같은 레지스터를 연산하면 레지스터에 0이 이 아닌 값이 있으면 점프를 하게 된다.
같은 값을 &연산 하게 되면 레지스터의 값이 0이 아닌 이상 제로플래그는 0이 되기 때문에 레지스터에 아무 값도 없어야 test연산을 했을 때 제로플래그가 1이 된다.
함수를 호출 후 반환받은 eax의 값이 0이 아닌 다른 값으로 수정되면 검사 루틴에서 걸리게 되어 틀리게 된다.
eax의 값에 유의하면서 함수를 살펴보도록 하자.
크게크게 흐름을 잡아보자. 함수의 형태이다.
스택 프레임 기법에 의한 함수 프롤로그가 있고, 중간을 좀 넘어서 cmp문이 있다.
ecx와 eax에 유독 많은 연산을 하게 되는데, 함수의 리턴 값은 보통 eax레지스터에 저장된다는 것을 유의하면서 보도록 해 보자.
중간중간에 같은 레지스터를 xor연산을 하는 모습이 보이는데, 같은 레지스터를 xor 연산을 하면 레지스터의 값이 0이 되게 된다.
eax를 0으로 만들어서 리턴을 하게 되면 루틴에서 틀리지 않게 되는데,
cmp문을 거쳐 무언갈 비교하고 나면 같을 때 ecx의 값을 eax로 옮기고 함수를 마치게 된다.
cmp문의 바로 직전 실행문은 xor ecx, ecx이고, ret 이전의 점프문은 저 비교문이 마지막이다. eax를 연산하는 다른 것을 모른다고 해도 저 부분만 보면 간단히 풀 수 있게 된다.
eax의 값이 0이 된 채 ret하게 되면 루틴을 클리어 할 수 있게 되고, cmp문에서 비교하는 것이 같아야 0이 된다는 것을 비로소 깨닫게 되었다.
그럼 비교하는 식은 무엇인가 조금 위를 보면서 해석해보자.
우선, 위에서 esi와 edi가 무슨 일을 도맏게 되는지 살펴보자.
edi에 함수의 인자 첫 번째를 넣고, esi에는 인자의 두 번째를 넣는다.
repe cmpsb는 ecx를 카운터로 해서 REPeat when Equal하며 CMP String Byte하라는 것이라고 해석하면 된다고 한다. 아직 어셈블리어 전체를 다 알지 못하여 찾아보았다.
다시 말하자면 strcmp같이 문자 하나하나를 현재 edi와 esi에 있는 것을 string byte(문자열을 문자 하나하나)로 반복해 가면서 같은 동안 비교해라는 뜻이라고 해석하면 될 것 같다.
버퍼의 세 번째 문자랑 "5y"의 문자랑 비교해 가면서 esi와 edi를 줄인다는 것인데, 내가 입력했던 값인 "3"과 "5y"는 당연히 같지 않다.
그럼 repeat하면서 두 문자열이 정확히 같으면 repe 실행문 이후에 esi와 edi가 가리키는 위치는 둘 다 처음 가리킨 문자열의 맨 뒤 + 1일 것이다.
둘 다 같은 문자열인 "5y"였으면 "5y"의 뒤를 가리키게 되는 것이다.
본론으로 와서, repe cmpsb를 거치면서 두 문자열을 가리키는 edi레지스터와 esi레지스터가 가리키는 값들은 변경될 것이다.
그 후에 esi - 1과 edi - 1의 값을 찾게 되는데, 반복 실행되고 난 뒤의 두 문자열의 끝을 가리키는 주소에서 1을 뺀 값은 문자열의 끝 문자일 것이다.
처음에 내가 넣은 값으로 생각해보자.
"123"문자열을 넣었을 때 3의 위치가 인자로 들어가게 되었고, "3"과 "5y"를 문자 하나하나 비교해 가며 주소를 바꿨을 때 '3'과 '5'는 같지 않으므로 cmpsb를 딱 한번 실행해 esi는 'y'를 가리키고 edi는 '3'의 뒤인 NULL값을 가리킬 것이다.
넣은 값이 "5y"와 같다고 가정해보자.
"125y"문자열을 넣었을 때 5의 위치가 인자로 들어가게 되었고, "5y"와 "5y"를 문자 하나하나 비교해 가며 주소를 바꿨을 때 두 문자열은 정확히 같으므로 cmpsb가 두 번 실행될 것이고, 두 레지스터는 두 문자열의 끝인 위치를 가리키게 될 것이다.
문자열 비교 연산을 실행한 후 cmp에서는 edi - 1의 값과 al, 즉 esi - 1의 값을 비교하는데, 두 문자열이 정확히 같다면 esi -1 과 edi - 1의 문자도 같을 것이다.
즉, repe cmpsb가 문자열이 정확히 같은지 edi와 esi레지스터의 값이 바뀌어 가면서 실행되고, 문자열이 같았다는것을 cmp문에서 한 번 더 검증하게?된다고 생각하면 될 것 같다.
je하게 되면,
아까 생각했던 eax에 0을 넣고
리턴을 하게 된다.
반대로 ja하게 되면
ecx에 not연산 등이 추가되며 eax에 0이 아닌 값이 들어가게 된다.
결국 이 함수의 루틴은 내가 넣은 문자열의 세 번째, 네 번째 문자가 "5y"와 같아야 한다는 것이다.
일단 빠른 진행을 위하여 강제 점프를 하게 해 깬것처럼 실행하게 하였다.
함수가 리턴되고 난 뒤 test문을 지난 뒤 점프하지 않게 된 것을 볼 수 있다.
두 번째 루틴 클리어다.
현재 두 번째 루틴까지 알아낸 것은
1. 입력한 문자열의 두 번째 문자가 'a'여야 한다.
2. 입력한 문자열의 세번째 문자부터 정확히 두 문자가 "5y"여야 한다.
password는 모르는 부분을 X라고 하고,
Xa5yXXXXXXXX~ 까지 풀었다.
'리버스 엔지니어링' 카테고리의 다른 글
리버싱kr easy_Keygen 풀이 1 (0) | 2020.07.01 |
---|---|
리버싱kr easy_crackme풀이 3 (0) | 2020.07.01 |
리버싱kr easy_crackme 풀이 1 (0) | 2020.07.01 |
리버싱 연습 - Lenas reversing 1 클리어 과정 (0) | 2020.06.30 |
리버싱 연습 - abex crackme5 시리얼 타파 (0) | 2020.06.30 |