Review

2018 Hacking Festival[게임문제]

D4tai1 2018. 12. 24.

※ 학교 내에서 1학년이 참가하는 대회로 기초지식 내 문제를 출제하였다.

※ 지식을 전달보다 시야를 넓히는데 목적이 있었다.


1. 게임문제

1) 서론

▶ 흔히 게임문제는 프로그래밍 능력이나 기초지식이 많이 필요하지 않는 문제 중 하나이다.

▶ 그래서 기왕이면 많은 사람들이 맞추기를 바랬고 힌트도 많이 주었다.


2) 소스

 [1] mole_catch4.h

#pragma once
#pragma comment(lib, "msimg32.lib")
#include "resource.h"


 [2] mole_catch4.cpp

#include "stdafx.h"
#include "mole_catch4.h"
#include<time.h>
#include<math.h>

void Init(HWND hWnd, LPRECT rect, int *x, int *y, int *r, int *score, long long int *stage) {
	srand(time(NULL));

	GetClientRect(hWnd, rect);
	//창 생성 시 화면의 좌표영역을 얻어온다.

	*x = rand() % rect->right;
	*y = rand() % rect->bottom;

	*r = 992;				//하드코딩 하지 않기 위해 일부러 값을 변경하여 설정하였다.

	//두더지의 반지름
	
	*score = 777;		//하드코딩 하지 않기 위해 일부러 값을 변경하여 설정하였다.
	*stage = -111;		//하드코딩 하지 않기 위해 일부러 값을 변경하여 설정하였다.

	MessageBox(hWnd, _T("가장 먼저 [11Stage]를 달성하세요!!!"), _T("Start"), MB_OK);

	SetTimer(hWnd, 1, 1000, NULL);
	//1번타이머 = 1초에 한 번씩 WM_TIMER메세지 발생
}

void Draw(HDC hdc, LPRECT rect, int *x, int *y, int r, int score, long long int  stage, HDC memdc, HBITMAP hBitmap) {
	TCHAR str[80];
	SetBkMode(hdc, TRANSPARENT);
	_stprintf_s(str, _T("Stage : %lld     Score : %d     Position : (%d, %d)     Mole_Size : %d"), stage + 112, score-777, *x, *y,  (-stage)-101);
	//%lld는 8바이트로 작성하기위해 일부러 사용한 것이다.
	TextOut(hdc, 400, 5, str, _tcslen(str));
	//현재 정보 출력

	Rectangle(hdc, 5, 25, rect->right - 5, rect->bottom - 5);

	
	SelectObject(memdc, hBitmap);
	TransparentBlt(hdc, *x - (r - 900), *y - (r - 900), 2 * (r - 900), 2 * (r - 900), memdc, 0, 0, 441, 412, RGB(255,255,255));
	//StretchBlt(hdc, *x - r, *y - r, 2 * r, 2 * r, memdc, 0, 0, 441, 412, SRCCOPY);
	//BitBlt(hdc, *x, *y, 3 * r, 3 * r, memdc, 0, 0, SRCCOPY);
	DeleteDC(memdc);

	//Ellipse(hdc, *x - r, *y - r, *x + r, *y + r);
}

void Timer(HWND hWnd, LPRECT rect, int *x, int *y, int r, long long int  stage) {
	*x = rand() % rect->right;
	*y = rand() % rect->bottom;
	//영역 내 두더지의 좌표 얻어오기

	if (*x < (r - 900) + (r - 900) * 0.5) {
		*x += (r-900);
	}
	else if (rect->right - (r - 900) - (r - 900) * 0.5 < *x) {
		*x -= (r - 900);
	}
	else if (*y < (r - 900) + (r - 900) * 0.5) {
		*y += (r - 900);
	}
	else if (rect->bottom - (r - 900) - (r - 900) * 0.5 < *y) {
		*y -= (r - 900);
	}

	if (-102 < stage) {
		KillTimer(hWnd, 1);
		//1번타이머 죽이기
		int k = MessageBox(hWnd, _T("Error!!!"), _T("비정상적인 접근으로 게임을 종료합니다."), MB_OK);
		// 강제로 스테이지 변경 시 종료

		if (k == IDOK) {
			PostQuitMessage(0);
		}
	}

	InvalidateRgn(hWnd, NULL, TRUE);
}

double Length(int x1, int y1, int x2, int y2) {
	return sqrt((x2 - x1)*(x2 - x1) + (y2 - y1) * (y2 - y1));
	//클릭한 마우스의 범위를 구하기 위해 사용하며 두더지으로부터 거리 반환
}

void Mouse(HWND hWnd, LPARAM lParam, LPRECT rect, int *x, int *y, int *r, int *score, long long int  *stage) {
	int mx = LOWORD(lParam);
	int my = HIWORD(lParam);
	//마우스의 좌표 얻어오기
	static int time = 1000;
	//if (*x - *r < mx && mx < *x + *r && *y - *r < my && my < *y + *r) {
	if (Length(*x, *y, mx, my) < (*r - 900)) {
		//마우스의 좌표가 두더지의 내부에 있다면?

		*score += 100;
		//스코어 증가
		
		*x = rand() % rect->right;
		*y = rand() % rect->bottom;
		//새로그리기 위한 두더지의 좌표 확보

		if (*x < (*r - 900) + (*r - 900)) {
			*x += (*r - 900);
		}
		else if (rect->right - (*r - 900) - (*r - 900) < *x) {
			*x -= (*r - 900);
		}
		else if (*y < (*r - 900) + (*r - 900)) {
			*y += (*r - 900);
		}
		else if (rect->bottom - (*r - 900) - (*r - 900) < *y) {
			*y -= (*r - 900);
		}

		if (1000 < (*score)-777) {
			//점수가 1000점을 초과했다면?
			(*stage)++;
			//다음스테이지로
			*score = 777;
			//스코어는 초기화
			*r -= 10;
			//난이도 조절을 위해 두더지의 반지름은 감소
			time = time - 30;
			//시간도 감소
		}

		if (-102 < *stage) {	
			KillTimer(hWnd, 1);
			MessageBox(hWnd, _T("C0DEFACE"), _T("key!!"), MB_OK);
			// 10스테이지가 넘어가면 메세지 출력
			
			PostQuitMessage(0);
		}
		else {
			KillTimer(hWnd, 1);
		}

		//1번타이머 죽이기
		
		SetTimer(hWnd, 1, time, NULL);
		//1번 타이머 생성
		//죽이고 다시 생성하는 이유는 클릭했을 때 기존 두더지의 위치에서 대기하지 않도록하기위함.

		InvalidateRgn(hWnd, NULL, TRUE);
	}

}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc, memdc;

	static HBITMAP hBitmap;

	static RECT space;
	//게임영역

	static int x;
	static int y;
	//두더지의 좌표

	static int r;
	//반지름

	static long long int stage;
	//스테이지
	static int score;
	//점수

	switch (message)
	{
	case WM_CREATE:
		Init(hWnd, &space, &x, &y, &r, &score, &stage);
		hBitmap = (HBITMAP)LoadBitmap(hInst, MAKEINTRESOURCE(IDB_MOLE));
		break;

	case WM_TIMER:
		Timer(hWnd, &space, &x, &y, r, stage);
		break;

	case WM_LBUTTONDOWN:
		if (stage <= 10) {
			Mouse(hWnd, lParam, &space, &x, &y, &r, &score, &stage);
		}
		//Mouse(hWnd, lParam, &space, &x, &y, &r, &score, &stage);
		break;

	case WM_COMMAND:
	{
		int wmId = LOWORD(wParam);
		// 메뉴 선택을 구문 분석합니다:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
	}
	break;
	case WM_PAINT:
	{
		hdc = BeginPaint(hWnd, &ps);
		memdc = CreateCompatibleDC(hdc);
		Draw(hdc, &space, &x, &y, r, score, stage, memdc, hBitmap);
		EndPaint(hWnd, &ps);
	}
	break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

  ▶ 소스를 보면 시간이 그렇게 많이 투자된 느낌이 들지 않을수도 있다.

  ▶ 기존의 Windows API 게시판에 있는 원 잡기에서 일부 내용을 추가한 것이다.

  ▶ https://ccurity.tistory.com/160 이 곳에서 원 잡기를 다루었다.


 [3] resource.h

  ▶ 리소스파일은 ID값으로 알아서 정의되지만 정의가 안될 경우 직접 정의해주어야 한다. 

  ▶ 아래의 리소스는 리소스추가에서 추가할 수 있다.

  ▶ 이미지의 배경에 흰색이 있는 것을 지우고 투명한 색으로 바꾸기 위한 과정도 필요하다.

  ▶ TransparentBlt( ) 함수를 사용하면 간단한 이미지의 경우 배경색을 투명하게 변경할 수 있다.

  ▶ 필요한 그림을 만들고 편집할 수 있는 포토샵 실력이 있는 사람이 부럽다..(포토샵 초보는 웁니다..ㅠㅜ)


  ▶ 위 그림은 게임에서 나올 두더지의 비트맵이다.


  ▶ 위 그림은 출력된 마우스 커서의 비트맵이다.


  ▶ 위 그림은 두더지게임 실행파일의 아이콘이다. 


3) 게임실행




  ▶ 위와 같이 Score가 1000을 초과할 경우 Stage가 올라가며 두더지의 크기가 작아지고 두더지가 움직이는 시간이 빨라진다.

  ▶ 10Stage가 되면 두더지는 정말로 조그마하며 0.7초마다 움직인다.


4) 풀이방법

  ▶ 문제를 푸는 방법은 4가지 정도가 있다.

 [1] 먼저 치트엔진의 unrandomizer를 체크하면 두더지가 나오는 랜덤함수가 정상적으로 동작하지 않는방법이 있다.


 [2] 두 번째는 난이도가 오를수록 변화하는 것이 있다. 그것은 두더지의 사이즈와 두더지가 나오는 시간이다.

  ▶ 위 소스를 보면 알다시피 Size, Score, Stage 등을 포함한 대부분의 값이 하드코딩 되어있지 않다.

  ▶ 그럼에도 불구하고 하드코딩 되어있는 것은 시간이다. 시간은 1초에 1000이라는 값을 의미한다.

  ▶ 1000이라는 값을 주의깊게 관찰해보면 Stage가 증가할 때 줄어들 것이다.


 [3] 세 번째는 Score는 무조건 100씩 증가한다는 것이다.

  ▶ 클릭 시 100씩 증가하는 곳의 주소를 찾아서 클릭시 2000씩 증가하도록 변경하면 된다.


 [4] 마지막은 Detect It Easy로 실행파일을 열어보면 UPX로 패킹되어 있는 것을 확인할 수 있다.

  ▶ 이것을 언패킹한 후 디버거 툴에 올려보면 [search for - All referenced text strings]에서 key값이 하드코딩되어 있는 것을 확인할 수 있다.


 [5] 이 문제의 정답은 메세지박스에 출력되는 것을 적는 것이 아니다.

  ▶ 여기서 [C0DEFACE] 라는 내용을 가진 메세지박스가 출력되지만 이것은 16진수이다.

  ▶ [C0DEFACE]까지만 찾고 답이 맞지않다고 컴플레인이 어느정도 있었다.

  ▶ 이 부분은 어느정도 예상했으며 답은 대체로 16진수로 적지 않고 적더라도 0x를 붙여서 적는다.

  ▶ 자세히 보면 O(영어)가 아니고 0(숫자)임을 알 수 있으며 이것은 계산기를 사용해서 10진수로 바꾸면 된다.

  ▶ 처음에 말한 시야를 넓혀주는 부분이 툴을 사용하는 것도 있지만 여기서도 일차원적인 시각이 아닌 다른 방향으로도 생각하기를 바라는 마음으로 낸 것이다.

  ▶ 바로 위에 보이는 3235838670이 문제의 정답이 된다.


 [6] 출제방향

  ▶ 만약 리버싱 기초를 배운사람들에게 내는 문제라면 key값을 하드코딩하지 않고 알고리즘을 이용하여 풀도록 할 것이다.

  ▶ 또한 답도 참가자별로 다르게 설정할 것이다.


 [7] 느낀점

  ▶ 코딩 시에 항상 중요한 것 같다.

  ▶ 이유는 나는 상상도 못했지만 참가자들 중에서 두더지가 작아지니 드래그해서 풀기를 시도하는 사람이 있었다.

  ▶ LBUTTONDOWN 메세지가 발생했을 때 동작하도록 작성하여 다행이다.

  ▶ 이 외에도 두더지를 고정시키는 툴을 사용해서 푸는 사람도 있었다.

  ▶ 추후에는 랜덤값이 아닌 현재 시간을 가지고 연산하는 문제를 내면 좋을 것 같다.


'Review' 카테고리의 다른 글

2018 Hacking Festival[악성코드]  (0) 2018.12.28

댓글