챌린지/Reversing.kr

Challenge - Music Player

하루히즘 2020. 12. 3. 23:04

Reversing.kr의 네 번째 문제인 Music Player다. 이전 Easy 시리즈들과는 달리 푼 사람의 수가 비약적으로 줄어든 것을 볼 수 있다. 아마 여기서부터는 장난은 끝난다는걸 의미하는 것 같기도 하다. 글을 쓰기 전에 먼저 말해둘 것은 나도 이 문제를 풀지 못해서 다른 사람의 풀이를 보고 풀었다는 것이다. Easy 시리즈들을 풀때는 나 혼자의 힘으로 풀 수 있었지만 아직 리버싱 초보인 이상 문제를 푸는 데 한계가 슬슬 다가오는 것인지 바보같이 dll 내부까지 들어가서 로직을 파악하느라 끙끙대는 실수를 저지르고 말았다. 여기서 시간과 에너지를 너무 소모해버려서 결국 힌트를 보고 풀게 되었지만 다시는 엉뚱한 곳을 분석하지 말자는 교훈을 얻을 수 있었다.

 

어쨌든 이번 챌린지의 목적은 이 음악 재생 프로그램을 1분 넘게 재생시키는 것이다.

음악을 1분 넘게 재생시켰다면 플래그를 보여주는 것 같은데 여러 개의 1분 확인 루틴이 존재한다고 한다. 프로그램은 비주얼 베이직으로 작성되었으며 관련 dll은 문제 파일과 동봉되어 있다.

프로그램을 실행시켜보면 다음과 같은 간단한 UI의 음악 재생 프로그램이 나타난다.

슬라이더를 이용해서 음악 재생 시간이나 볼륨을 조절하는 건 참신한 것 같다. 그건 그렇고 조작해볼 수 있는 부분이 꽤나 많은데 먼저 Open 버튼을 눌러보면 파일 탐색기가 열리고 재생할 음악 파일을 찾는 것을 볼 수 있다.

아래에 있는 슬라이더는 조작해도 별다른 변화가 없고 대신 맨 왼쪽 버튼(재생 버튼)을 눌렀을 경우 다음과 같은 메시지가 뜨는 것을 볼 수 있다. 가운데 버튼(일시중지 버튼)은 눌러도 아무런 반응이 없었고 맨 오른쪽 버튼(중지 버튼)을 누르자 00:00 / 00:00 이라는 문자열이 갑자기 나타나는 것을 확인할 수 있었다.

가운데 슬라이더가 음악 재생 시간을 표시하는 것이기 때문에 중지 버튼을 누르면 타이머 텍스트를 00분 00초로 초기화하는 것 같다. 그렇다면 아까 Open 버튼을 눌러서 아무 음악 파일이나 선택한 후 재생해보도록 하자.

재생 버튼을 누르니 00:00 / 01:00 처럼 텍스트가 바뀌었다. 음악 재생을 1분으로 제한하는 듯 한데 1분 넘게 재생하려고 하면 어떻게 될까? 확인을 위해 슬라이더를 직접 끝으로 움직여보니 다음과 같은 메시지가 나타나는 것을 볼 수 있었다.

다시 시간 문자열은 00:00 / 00:00 으로 초기화되었다. 실제로 1분동안 기다리지 않아도 슬라이더를 넘기면 음악 파일의 재생 시간을 감지하여 정지 버튼을 누르게 되는 것 같은데 그렇다면 이 "1분 미리듣기만 가능합니다"라는 메시지 박스가 뜨는 로직 부근을 먼저 살펴봐야 할 것 같다. 더이상 얻을 수 있는 정보는 없기 때문에 디버거로 실행해보기로 했다.

비주얼 베이직의 익숙한 진입점을 볼 수 있다. 여기서부터 실수가 시작된 것이 나는 저 0x00401472의 ThunRTMain을 타고 들어가서 msvbvm60.dll부터 분석을 시작했다. 그러나 이 dll 파일은 Music Player에서 사용하는 비주얼 베이직의 API들이 적혀있을 뿐 내가 리버싱을 해서 어떻게 할 수 있는 파일이 아님에도 불구하고 온갖 함수호출까지 파고 들어가다가 혼자 지쳐떨어졌던 기억이 아직도 생생하다. 물론 비주얼 베이직이 Microsoft에 의해 공개되지 않은 부분이 많다고 하니 이렇게 분석하는게 정말 어떤 분야에서는 도움이 될 수 있어도... 지금은 이 Music Player 프로그램 자체의 로직을 분석해야 할 때가 아닌가 싶다. 

그래서 Music_Player.exe에서 다른 모듈에 대한 호출을 찾아보았다. 어쨌든 메시지 박스를 띄우거나 음악을 재생하려면 관련 코드가 모두 구현되어 있지 않는 이상 이 Music_Player.exe만으로는 불가능할테니 다른 dll, 정확히는 동봉되어 있는 msvbvm60.dll에서 무슨 함수를 사용하는지 찾아본 것이다.

탐색 결과 다음과 같은 여러 함수들을 사용하는 것을 볼 수 있었는데 아까 음악 재생 시간이 1분이 넘으면 메시지 박스를 띄워서 사용자에게 알림을 보냈기 때문에 메시지 박스를 호출하는 함수, 비주얼 베이직에서는 rtcMsgBox 함수에 대해 중단점을 걸고 진행해보기로 했다.

Abex CrackMe에서 비주얼 베이직을 분석하느라 꽤나 고생했기 때문에 이런 함수가 눈에 바로 들어온다.

일단 다시 음악 파일을 골라서 테스트해보려고 하는데 파일 탐색기 창을 열면 갑자기 프로그램이 다음과 같은 중단점에 걸리는 것을 볼 수 있다.

이는 어떤 버그인지, 아니면 필수적으로 걸리는 중단점인지 알 수 없지만 F9(x96dbg 기준)를 눌러서 무시하고 넘어가면 그 다음부터는 뜨지 않는다. 이후 재생 버튼을 눌러서 음악을 재생시키고 1분을 기다리거나 슬라이더를 1분 가까이 넘기면 어떻게 될까?

맨 마지막 rtcMsgBox 함수 호출에 중단점이 걸리는 것을 확인할 수 있다. 이 코드 부근으로 이동해보도록 하자.

일단 이 메시지 박스가 호출된다는 것은 1분 로직에 걸려서 음악 플레이어를 더이상 재생할 수 없다는 것을 의미한다. 그렇다면 어디서 이 메시지 박스를 호출하는 분기를 타게 되는 것일까? 일단 멀리가지 않고 근처의 비교 명령어와 점프 명령어를 찾아보니 두 쌍을 찾을 수 있었다. 0x00404563과 0x0040457A에서 cmp 명령어로 비교 후 각각 jl, jge 명령어로 점프하고 있는데 후자의 경우 0x00404590으로 점프하기 때문에 결국 점프하든 안하든 메시지 박스를 호출하는 명령어까지 이동하게 된다. 그렇기 때문에 이를 배제하고 전자를 살펴볼 필요가 있다.

이 부분의 코드는 매 초마다 실행된다. 이를 알아보기 위해 0x00404563에 중단점을 걸고 실행시켜보면 음악이 나오다가 끊기면서 계속 중단점에 걸리는 것을 확인할 수 있는데 그렇다면 이 부분에 실시간으로 음악의 재생 시간을 파악하여 1분을 지났는지 비교하는 로직이 자리잡고 있다고 추측할 수 있다. 재밌는 것은 0x00404563의 cmp 명령어의 피연산자들인데 eax 레지스터와 0xEA60을 비교하고 있다. 그런데 0xEA60은 10진수로 60,000을 뜻하며 보통 프로그램에서 밀리세컨드(ms)로 시간을 재기 때문에 1000ms가 1초인 것을 감안하면 이는 60초, 즉 1분의 값을 가지고 있다고 볼 수 있다.

 

그렇다면 피연산자로 비교되는 eax 레지스터에는 당연히 음악의 재생 시간이 들어가리라 추측할 수 있는데 확인하기 위해 중단점을 풀고 음악을 좀 재생시키다가 다시 걸어보면 위와 같이 0x1A72에서 0xB48A로 값이 증가한 것을 볼 수 있다. 이는 시간의 흐름에 따라 재생 시간이 증가한 것으로 최종적으로는 0XEA60, 즉 1분까지 증가하다가 이를 초과하면 다른 분기를 타게 될 것이다. 그렇다면 1분이 지나기 전에 수행하던 로직을 1분이 지난 후에도 수행하려면 어떻게 해야 할까? 간단히는 조건 점프 명령어(jl)를 일반 점프 명령어(jmp)로 바꾸면 될 것이다. 일단 그렇게 어셈블하고 1분을 넘겨보자.

어딘가 문제가 있는지 예외가 발생하면서 프로그램이 정상적으로 종료되지 않았다. 코드는 바뀐 것 없이 단순히 점프 명령어만 바꿔줬을 뿐인데 왜 예외가 발생하는 것일까? 확인해보기 위해 아까 어셈블한 점프 명령어에 중단점을 걸고 따라가보기로 했다. 아까 말했듯이 이 주변의 코드 블록은 1초마다 실행된다. 그래서 중단점을 걸어두고 그냥 실행시키면 매 초마다 중단점에 걸리기 때문에 매번 F9를 누르기도 여간 귀찮은 일이 아닌데 일단 중단점을 풀고 슬라이드를 58초쯤으로 넘긴 다음에 eax 레지스터값을 보면서 0xEA60을 넘지 않을 때까지 F9를 눌러서 넘기면 된다.

그리고 확인할 수 있었던 것은 0x004046B9의 vbaHresultCheckObj 함수에서 에러가 발생한다는 것이다. 그런데 이상한 부분은 아무런 수정이 가해지지 않은 원래 프로그램 로직에서도 이 함수는 호출되지 않는 것을 확인할 수 있었다.

원래 프로그램에서는 애초에 1분이 지나면 이 곳으로 올 리가 없으니 상관이 없는데 1분이 지나기 전 이곳에 도착하면 0x004046A7에서 test하는 eax 레지스터에는 0이 저장되어 있어 test eax, eax 즉 eax 레지스터가 0인지 확인하는 것에 부합하여 ZF가 설정, 0x004046AB에서 0x004046BF로 분기를 타게 된다. 그러나 위에서 jl 명령어를 jmp로 어셈블하면서 1분이 지난 후에도 이곳에 도착하게 되는데 그 경우 eax 레지스터가 0x800A017C 값을 가지기 때문에 점프하지 않아 결국 함수 호출에서 예외가 발생하게 된다. 그렇다면 저 부분도 항상 점프하도록 jge가 아닌 jmp로 어셈블하면 어떨까?

성공이다! 모든 1분 검사 루틴을 우회하여 1분 넘게 재생할 수 있었으며 플래그는 윈도우의 제목 부분에 나타나는 것을 확인할 수 있었다.

 

 

아직도 이 문제를 처음 풀었을 때의 절망이 잊혀지지 않는다. 왜 그때 dll 파일에 들어가가지고 몇시간동안이나 이상한 곳에서 디버깅하고 있었던 건지... 그래도 다른 사람들의 풀이에서 힌트를 얻어서 올바른 길로 들어설 수 있어 다행이었다. 원래는 풀이는 무조건 보지 말자고 생각하고 있었는데 정 안되겠다 싶은 경우는 조금씩 보면서 힌트를 얻는것도 괜찮은 공부 방법인 것 같다.

'챌린지 > Reversing.kr' 카테고리의 다른 글

Challenge - Replace  (0) 2020.12.05
Challenge - Easy Unpack  (0) 2020.11.23
Challenge - Easy Keygen  (0) 2020.11.11
Challenge - Easy Crack  (0) 2020.11.02