본문 바로가기
리버싱/CodeEngn 문제해결

Basic L14

by 멍청이 개발자 2026. 9. 18.
반응형

문제는 다음과 같다.

그림0. 문제화면

DIE로 열어보니 UPX패킹이 된걸 확인 할 수 있다.

그림1. UPX

우선 패킹을 해제 한 후 덤프를 떠서 저장시켰다.
이제 언패킹된 덤프를 디버거에 올리고 실행해봤다.

그림2. 덤프를 디버거에 올렸다.

우선 프로그램은 아래와 같이 입력창이 두 개 존재하고 체크 버튼으로 키를 확인한다.

그림3. 프로그램 화면

어셈블리 코드를 봤을때 위 프로그램은 윈도우API를 사용하므로 텍스트를 가져오기 위해서 GetDlgItemText라는 함수를 호출 할 것이다.

그림4. 모듈간 호출로 검색

모듈간 호출을 검색해보니 정확히 GetDlgItemTextA함수가 2개 보인다.

그림5. 입력을 가져오는 부분

위와 같이 나란히 GetDlgItemTextA함수가 보인다. BP를 걸고 사용자의 입력을 어떻게 가져오는지 확인해봤다.

그림6. 위와 같이 입력했다.
그림7. 텍스트를 가져온 모습

위와 같이 403038주소에는 첫번째 입력창, 403138주소에는 두번째 입력창 내용을 가지고 와서 각각 길이가 0보다 큰지만 판단후 넘어가고 있는 모습이다.

그림8. strlen

strlen함수를 통해서 첫번째 입력의 길이를 eax에 저장한다.

그림9. 암호화 및 비교문

그 이후 위와 같이 암호화 반복문이 존재하고 eax의 값과 esi 값을 비교해서 성공여부를 판단하는 모습이다.
암호화문을 분석 해보자.

xor esi,esi
mov ecx,eax
mov eax,1
mov edx,[403038]
mov dl,[eax+403037]
and edx,FF
mov ebx,edx
imul ebx,edx
add esi,ebx
mov ebx,edx
sar ebx,1
add esi,ebx
sub esi,edx
inc eax
dec ecx

esi값을 0으로 초기화.
아래내용을 문자열의 길이만큼 반복한다.
반복하면서 문자열의 문자를 앞에서부터 하나씩 가져온다.
가져온 문자의 아스키코드 값을 제곱하여 esi에 더한다.
이전에 가져왔던 문자에 sar 1을 하여 2를 나눈다.
이 값을 esi에 더한다.
esi에서 가져온 문자의 아스키 값을 뺀다.
 
이것을 간단하게 C++로 작성해봤다.

#include <iostream>

int main()
{
	char input[0x100] = { 0, };
	std::cin.getline(input, 0x100);
	int cnt = strnlen_s(input, 0x100);

	int result = 0;
	for (int i = 0; i < cnt; ++i) {
		result += input[i] * input[i];
		result += (input[i] >> 1);
		result -= input[i];
	}

	std::cout << std::uppercase << std::hex << result << std::endl;
	return 0;
}
그림10. C++코드 실행

코드를 실행하고 문제에서 요구하는 CodeEngn을 넣어보았다.
129A1이 나왔다.
 
실제로 프로그램 화면에 CodeEngn을 입력후 암호화가 끝나는 지점에 BP를 걸고 ESI값을 확인하면 129A1이라는 값이 들어있는 것을 확인 할 수 있다.

그림11. 결과


다음으로는 두번째 입력창에 입력한 값의 변화를 살펴봐야 한다.

그림12. 사용자 입력2

보면 우선 esi값을 스택에 백업해두고 내가 입력했던 1234라는 문자열을 매개변수로 하여 401383함수를 호출하고 esi레지스터의 값과 비교하여 성공여부를 판단하고 있다.
그래서 401383함수를 따라봤다.

그림13. 401383함수

우선 1234의 길이를 측정하고 ebx를 0으로 초기화 한다.
마찬가지로 문자열의 길이 만큼 반복을 하기위해 ecx값을 문자열의 길이로 세팅하고 esi에 문자열을 넣는다.
ecx의 값을 스택에 백업한다.
eax를 0으로 만들고 lodsb명령어를 통해서 문자열에서 앞 1바이트씩 가져와서 eax에 저장한다.
eax에 0x30을 빼고 ecx를 1 감소 시킨다.
그 후 eax에 eax * 10 ^ ecx를 저장한다.
eax값을 ebx에 저장한다.
ecx값을 복원후 이어서 ecx가 0이 될때까지 작업을 반복한다.
 
이 함수는 문자열을 숫자로 변환시키는 함수 이다. C++로 아래와 같이 표현해봤다.

#include <iostream>

int main()
{
	char input[0x100] = { 0, };
	std::cin.getline(input, 0x100);
	int cnt = strnlen_s(input, 0x100);

	int result = 0;
	for (int i = 0; i < cnt; ++i) {
		int toInt = input[i] - 0x30;
		for (int j = 0; j < cnt - i - 1; ++j) {
			toInt = toInt * 0xA;
		}
		result += toInt;
	}

	std::cout << std::uppercase << std::hex << result << std::endl;
	return 0;
}

아스키 코드에서 문자로 나타내지는 숫자 0의 시작점이 0x30이다. 그러므로 아스키 값에서 0x30의 값을 뺐을때 숫자 0이 되므로 아스키에서 0x30만큼 뺀값이 실제 숫자에 해당하게 된다.
추가로 자릿수에 따라서 10을 곱하여 변환하고 있다.

그림14. 결과

실행한 결과이다.
4D2는 10진수로 1,234이다.
 
따라서 우리는 우리가 두 번째로 입력한 값을 숫자로 변환한 16진수 값과 129A1을 비교하여 결과를 도출한다는 것을 알 수 있다.
129A1은 10진수로 76193이므로 76193을 입력하면 성공 메시지가 나온다,
 

그림15. 성공

정답은 76193 이다.
 
추가로 401383함수의 특성때문에 답이 여러개가 가능하다.
자릿수를 맞추기 위해서 10을 곱하는 부분이 있는데 여기서 숫자가 0이면 아무리 곱해도 영향을 미치지 않는다.
그래서 이런 답도 가능하다.

그림16. 앞에 0은 몇개가 있든 무시된다.

 
하지만 문제에서 요구하는 것은 5자리 이므로 76193이 맞다.

반응형

'리버싱 > CodeEngn 문제해결' 카테고리의 다른 글

Basic L16  (0) 2026.09.19
Basic L15  (0) 2026.09.18
Basic L13  (0) 2026.09.18
Basic L12  (0) 2026.09.18
Basic L11  (0) 2026.09.18