2018 Hacking Festival[게임문제]
※ 학교 내에서 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 |
---|
댓글