문제
이 프로그램의 등록키는 무엇인가
다운로드
실행
리버싱 문제 국밥 유형인 등록키(시리얼키) 찾기 문제가 또 나왔습니다.
이번엔 입력할 라인이 2개네요. Username으로 추정되는 값 Unregistered... 랑 등록키로 추정되는 값 754-GFX-IER-954 는 이미 입력되어있습니다.
이상태서 Register now!를 누르면 당연히 실패했다고 나옵니다.
사실 시리얼키 보다도 저기 타이틀에 써져있는 영어 단어가 궁금했는데 꺼져! 라는 내용이네요
벌써부터 마음이 따뜻해집니다.
까칠한 반응과는 다르게 그래도 문제 난이도 자체는 쉬운편 입니다.
풀이
일단 풀기전에 주의할 점은 Detect it Easy(die.exe) 로 확인해보면 이 프로그램은 UPX로 패킹되어 있습니다.
UPX(the Ultimate Packer for eXecutables) 는 꽤나 유명한 패커로, exe 파일을 압축(Compressing)해주는 프로그램입니다.
보통 패킹을 복잡하게 걸어놓으면 대부분의 리버서들은 대가리가 깨져서 뇌수가 질질 흐릅니다만.. ex) 더미다, VMProtect 등등.. 그러나 UPX는 친절하게 압축 해제 기능 (Decompressing) 도 지원하므로, UPX로 패킹된 프로그램은 UPX로 언패킹도 가능합니다.
Detect it Easy 에서 UPX의 버전도 자동으로 추정해주기 때문에 우리는 같은 upx 버전 프로그램을 잘 찾아서 패킹을 풀어주면 되겠습니다.
패킹이란?
패킹은 쉽게 말해서 실행 파일(*.exe) 을 압축하는 개념입니다. 일반적으로 우리가 사용하는 파일이나 폴더를 묶어서 zip, rar 파일로 만드는걸 "압축" 했다 또는 "압축 파일" 로 표현하는데요. 이 압축을 실행 파일에 건 것이 바로 패킹입니다.
* 기본적으로 제 CrackMe 풀이 글은 문제 풀이 자체나 방법 등에 집중하고, 이런 이론과 같은 내용은 나중에 따로 글을 작성해 자세히 다룰 예정입니다. 우선은 간단하고 쉽게만 설명을 드리도록 하겠습니다.
패킹을 한 그림으로 나타내면 다음과 같습니다. 원래 패킹이 되지 않은 경우엔 보통 우리가 항상 하던대로 디스어셈블리 도구(IDA, 올리디버거, 기드라 등)로 기계어를 어셈블리어로 복구해서 바로 exe 파일의 기계어를 읽어낼 수 있습니다. 그러나 패킹 과정이 들어가면 기존의 어셈블리어 코드를 압축해서, 일종의 암호화가 이루어지고 그것을 풀어내는 코드[복호화 코드]가 삽입되게 됩니다.
그래서 패킹된 exe 파일은 결론적으로 압축된 어셈블리 코드 (압축상태라 제대로 어셈블리어로 읽을 수 없음) + 복호화 코드로 구성되게 됩니다. 디스어셈블리 도구로 패킹된 exe 파일을 열어서 분석해봐야, 원래 코드는 나오지도 않고 패킹을 푸는 코드 정도밖에 보이지 않게 됩니다.
그럼 원래 코드는 어떻게 되찾냐? 궁금하시죠??
만약에 패킹된 프로그램을 실행하면, 복호화 코드가 실행되고 그제서야 암호화된 "asdfj.." 를 원래 코드 mov eax, 10 ... 으로 복구하면서 제대로 프로그램이 돌아가게 됩니다.
결론적으로 압축 해제된 원본 어셈블리 코드는 *.exe 파일만 봐서는 모르고 실행 이후 RAM(메모리) 위에 서만 존재하게 되는것이죠. 모든 프로그램은 RAM위에서 돌아가니깐요.
사실 패킹이라는 것 자체도 예전에 HDD 용량이 적어서 실행 파일의 용량을 줄이고자 하는 목적도 있지만, 리버싱이 어렵도록 막는 보안의 목적도 있습니다. exe 파일을 그대로 열어도 분석을 못하게 하는것이죠. [상용 프로그램에서 보안을 위해 사용하기도 했지만 요즘은 상용프로그램에선 잘 사용하지 않고 바이러스가 분석이 어렵도록 악용을 위해 사용합니다.]
이러한 패킹을 뚫고 원래 코드를 찾는 방법은 크게 2가지가 있습니다.
1. 언패킹 도구를 사용한다
=> 패킹한 프로그램에 대한 언패킹 도구가 있으면 그것을 사용하는게 좋습니다. 다만.. 이렇게 어떤 툴에 의존하는걸 "툴키디" 라고 표현하듯이 어떤 패킹 프로그램에 대한 언패킹 도구가 존재하는 경우는 그렇게 많지 않습니다. 또 언패킹 도구가 작동하지 않으면 손을 쓸 방법도 없구요. ** 물론 우리가 지금 풀어낼 UPX는 스스로 언패킹 기능을 지원하기 때문에 크게 걱정은 안하셔두 됩니다.
2. 직접 실행하면서 복호화코드를 잘 찾아내고 복호화가 끝난 순간 RAM위로 올라온 원래 코드를 그대로 저장 한다. (==보통 DUMP 뜬다고 함)
=> 한마디로 수동으로 언패킹 하는 방법입니다. 영어 표현으로 매뉴얼 언패킹 이라고도 합니다. 복호화 코드 실행이 끝난 순간 원래 코드가 그때부터 RAM위에서 실행될 건데 그 순간 원본 코드를 그대로 저장해서 원래 코드를 되찾는 방법입니다.
근데 패킹 프로그램이 강력할수록 이 방법이 어려워집니다. 또 Stolen Byte라고 해서 이렇게 RAM위로 올라온 코드를 잘 저장(덤프) 했다고 해도 패킹 프로그램들이 조금더 보호를 위해 일부 코드들을 위나 아래로 빼놔서 미리 실행시켜버리는 Stolen Byte 라는 기법까지도 사용해 덤프를 떴는데도 코드가 몇개 비어있어서 동작을 안하는 불상사가 일어납니다.
한마디로 X같이 어렵다 이겁니다.
일단은 머리아프겐 생각하지 마시고 ㅎㅎ 그렇다 정도만 이해합시다.. 나중에 문제 풀다보면 수동으로 패킹하는 모습도 제가 글로 쓸 예정이니깐요. 여기선 UPX 툴로 언패킹만 할꺼니깐 걱정은 안하셔도 됩니다.
언패킹
위에서도 대충 이해하셨겠지만... 리버싱의 어려운 문제가 난독화나 패킹입니다. 특히 상용 패킹 프로그램으로 유명한 더미다의 경우 최신 버전은 진짜 제대로 뚫는 사람이 손에 꼽습니다. (카카오톡 PC 클라이언트도 더미다로 패킹되어 있습니다 -.-)
그래도 일단 UPX 는 언패킹 기능을 지원하니깐요. 걱정은 안하셔도 됩니다 보통 쉬운 리버싱 문제는 UPX로 패킹된 경우가 전부입니다. 좀 귀찮을 뿐이지 푸는건 간단합니다 ^^
https://github.com/upx/upx/releases/
우선은 제가 UPX 공식 사이트에서 공수해온 파일입니다. 완전한 최신 버전을 원하신다면 위 GitHub 사이트에서 받으실 수 있습니다.
일단은 이 CrackMe가 2000년도에 만들어져서 그런가 UPX도 1.01이라는 엄청난 구버전입니다.
그래서 무조건 맞는 버전으로 언패킹해야 될 줄 알았는데 그냥 UPX 최신 버전으로 해도 언패킹이 잘 되더라구요 -.-
일단은 C드라이브에 이렇게 upx 파일 압축도 풀고 동일 위치에 05.exe 파일도 옮겨주었습니다.
이제 CMD 창을 열고 아래 명령어를 입력해서 05.exe를 언패킹 합니다.
cd C:\upx_3.96
upx.exe -d 05.exe
upx.exe 를 실행시키는데 인자로 -d 옵션과 실행 파일 경로를 주면 됩니다.
저는 편의를 위해 한 폴더에 다 모아놓고 cd를 이용해 실행시켰습니다.
이런 메세지가 떴다면 성공입니다. -d 옵션으로 패킹을 풀면 기본적으로 upx은 원본 파일을 덮어 씌우기 때문에 따로 패킹 / 언패킹 된 파일로 구분되서 나오진 않습니다.
132608 에서 315392 로 용량이 증가했다고 나오네요. 압축이 풀렸으니 당연한겁니다.
우왕. DIE로 여니깐 UPX 패킹 내용이 사라진걸 확인할 수 있습니다.
이제 패킹이 풀려서 exe 파일 자체에 복호화 코드와 압축된 기계어 명령어는 없어지고 원본 바이너리 파일이 된 것입니다!!
이제 IDA로 이 프로그램을 분석할 수 있게 됩니다.
일단은 예시로 패킹되어 있는 05.exe를 IDA로 열면 다음과 같은 모습이 보입니다. 왼쪽에 Function 탭에서 이 프로그램의 함수 호출을 볼 수 있는데 시작지점 함수를 제외하곤 아무것도 보이지 않습니다. Segment 부분에 UPX1이라고 표시된걸로 봐선 저게 바로 원본 코드를 압축 해제하는 UPX 코드겠지요.
당연하지만 프로그램에서 사용하는 문자열도 잘 인식이 안되고... 그렇습니다
그런데 이제 패킹을 푼 05.exe를 열면..?!
엄청난 수의 함수 호출 분석과 함께 코드가 보입니다.
패킹이 풀렸으니깐 기존에 하던대로 분석이 가능해진 것이죠.
그런데 이 쪼매난 MB 단위도 안되는 프로그램이 함수 호출만 2600번을 하고있네요 -.-
인텔이나 AMD는 외계인을 어떻게 고문했길레 이 수많은 명령어를 몇 초만에 실행시키는건지 ㅎㄷㄷ
어쨌던 간에 이 많은 코드를 하나 하나 읽을 생각은 없으니 항상 하던대로 Strings 탭으로 가서 우리가 원하는 코드 위치를 찾아봅시다.
문자열이 좀 많네요. Ctrl + F를 눌러서 Beggar을 검색하면 아까전의 그 실패 메세지 창을 찾을 수 있습니다.
CODE 섹션에서 이 문자열이 저장된 형태를 보여주고 있는데요 저기 XREF 부분을 누르면 이 문자열이 사용된 곳으로 이동이 가능합니다.
그리고 여담이지만 이 프로그램은 확실히 델파이로 만들어진 거 같습니다. 아까 Detect it Easy에서도 분석되었지만, 저 SpeedButton이나 TForm이란게 찾아보니 델파이의 폼 항목들입니다.
여기 _TForm1_SpeedButton1Click 이라는 부분이 델파이에서 클릭 이벤트가 발생했을때 호출이 되는 함수 부분입니다.
VB6.0이나 C#에서 프로그래밍을 해보신 분들이라면 버튼 클릭 이벤트에 함수를 작성해서 프로그램을 만들어본 경험을 되살려보시면 이것도 간단히 이해하실겁니다.
실패로 분기하는 주요한 코드 부분은 저 2곳입니다. 제가 잘보이라고 F2를 눌러서 BP(브레이크 포인트) 를 걸어서 표시해뒀습니다. 저기 LStrCmp 라는 문자열 비교 함수를 호출하고 있는걸 확인해볼 수 있습니다.
LStrCmp은 윈도우32 API 함수입니다. 자세한 설명은 아래를 보시면 되는데, C언어의 strcmp 와 동일하게 동작합니다.
http://www.soen.kr/lecture/win32api/reference/Function/lstrcmp.htm
2개의 문자열을 인자로 받아서, 문자열을 비교하고 그에 따라 비교값 (양수, 음수 또는 0) 을 반환합니다.
그렇기에 LStrcmp 가 호출전에 비교를 위해 문자열 2개를 인자로 PUSH 해야 할 건데 이상하게 함수 호출전에 mov 하는 부분 밖에 안보이네요. 스택에 저장하는게 아니라 mov를 통해 eax와 edx에 이상한 값 2개를 저장하고 있습니다.
일단은 저게 비교할 문자열 2개인듯 싶은데요 왜 PUSH하지 않는지는 잘 모르겠습니다 -.-
아마 LStrCmp 함수는 비교시 받을 문자열 값을 스택 메모리에 저장해서 사용하는게 아니라 그냥 eax와 edx 레지스터에 문자열 시작 주소값을 그대로 저장해서 사용을 하지 않나 유추해봅니다.
어쨌던 간에 LStrCmp가 비교하는 문자열은 [ebp+var_4] 의 값과 미리 등록해둔 문자열 값(_str_GFX_754..., _str_Registered...)입니다.
일단은 IDA가 분석을 해둔 상태라 _str_로 시작하는 문자열 변수들의 값이 보이지 않는데요. 이경우 더블 클릭을 해보면 바로 알 수 있습니다.
확인해보니깐 _str_GFX_754_IER_954.Text 변수 값은 "GFX-754-IER-954" 이고, _str_Registered_User.Text 의 값은 "
Registered User" 입니다.
그러니깐 처음에 [ebp+var_4] 값이랑 Registered User 이랑 같은지 체크하고, 또 [ebp+var_4] 값이랑 GFX-754-IER-954 랑 같은지 체크해서 둘다 같으면 시리얼키가 맞다고 끝나는거네요.
그럼 [ebp+var_4] 의 값이 뭔지 알아야 하는데요. 솔직히 여기까지 했으면 리버싱 짬빱이 조금이라도 있으신 분들은 이미 눈치를 까셨을건데 프로그램 처음에 유저 네임에 적어야할 값이 "Registered User" 고 시리얼 값은 "GFX-754-IER-954" 라는걸요...
[ebp+var_4] 은 딱봐도 우리가 입력한 문자열의 값 일겁니다.
그래도 확실하게 확인을 해봐야 하니깐요. 디버깅을 통해서 확인해보겠습니다. 보통 동적 디버깅은 올리디버거가 편해서 IDA로는 정적으로 분석만하고, 디버깅은 올리디버거로 했는데 이번엔 특별히 IDA로 디버깅 하는걸 보여드리겠습니다.
[ebp+var_4] 가 우리가 입력한 문자열이란거만 확인하면 이 문제 풀이는 끝나게 됩니다.
일단은 F2를 사용해서 mov eax, [ebp+var_4] 위치에 또 BP를 걸어줍니다. 저 위치에서 [ebp+var_4]의 값을 봐야합니다.
* 참고로 mov eax, [ebp+var_4] 의 의미는 ebp+var_4 주소가 가르키는 위치에 메모리 값을 읽어서 eax 값으로 복사해라 라는 뜻 입니다. :: Memory Addressing Mode
IDA에서 디버깅 할려면 저렇게 디버깅에 사용할 디버거를 선택해야 합니다.
가장 간단한 방법으로는 Local Windows debugger 을 선택하고 저 시작버튼 (초록색 버튼) 을 누르면 됩니다.
그러면 이렇게 IDA에서 디버깅 창을 만나 보실 수 있습니다. 기본적으로 올리디버거랑 모습이나 배치가 크게 다르지 않습니다. 다만 IDA는 정적 분석 도구고, 올리디버거는 동적 분석 도구다보니 올리디버거 자체에서 스택 윈도우나 레지스터가 문자열 시작주소를 가리킬 경우 ASCII "문자열" 과 같은 형태로 자동으로 문자열 내용을 보여주는데 IDA는 이런 기능이 없는듯 합니다.
https://stackoverflow.com/questions/32575148/is-it-possible-to-view-strings-in-memory-using-ida-just-like-i-can-in-ollydbg
저랑 비슷한 질문이 똑같이 StackOverFlow 에도 올라왔는데 IDA로는 SHIFT + F12 를 눌러서 현재 읽혀지는 모든 문자열을 보는 방법 정도는 있다고 합니다 -.- 다만 진짜 프로그램에서 사용하는 모든 문자열이 나오기 때문에 혼란스럽기만 하고 쓸만한 정보는 아닙니다.
일단 Register now!를 누르면 실행되는 코드 부분에 BP를 걸어뒀기 때문에 저 기본값을 입력시켜둔 채로 Register now! 버튼을 누릅니다.
그러면 이렇게 딱 BP를 걸어둔 곳에서 멈추게 됩니다. 여기서 [ebp+var_4] 에 뭐가 들어있는지 확인해봐야 합니다.
한마디로 ebp+var_4 가 가리키는 메모리 주소의 위치에 가서 값을 확인해봐야 하는데요.
var_4의 값이 뭔지 확인해보기 위해 ebp_var_4 쪽에 오른쪽 마우스 클릭을 해보면 ebp - 4 라는 것으로 표시해주는걸 확인해볼 수 있습니다. ebp는 Extended Base Pointer Register 의 약자로 기본적으로 스택 프레임의 시작주소 (스택 메모리의 시작 부분) 를 가리키는 포인터입니다.
그곳으로부터 - 4 인 Offset 주소에 문자열의 시작주소가 들어있는 셈이 됩니다.
그러니깐 스택 메모리 창을 살펴봐야 합니다.
마우스를 올려보면 ebp+var_4 == ebp-4 의 주소가 0019F854 로 계산됨을 알려줍니다.
그래서 Stack View에서 해당하는 스택 메모리 주소의 값을 보면 0019F854 에 022A8008 라는 문자열의 시작주소를 저장하고 있습니다.
022A8008에 이제 어떤 문자열이 저장되어 있는지만 확인하면 우리가 원하는 답을 찾을 수 있겠죠..!
왼쪽 Hex View 창이 바로 RAM(메모리)에 어떤 값이 저장되어 있는지 실시간으로 볼 수 있는 창입니다. (올리디버거랑 똑같음)
저기 Stack view에서 022A8008를 오른쪽 클릭 , Follow in hex dump 를 누르면 022A8008 위치에 있는 램 주소로 바로 순간 이동이 가능합니다.
확인해보니 역시 022A8008 주소에는 우리가 입력한 Unregistered 가 저장되어 있습니다 ㅋㅋ
결론적으로 참조하는 [ebp+var_4] 값은 우리가 입력하는 문자열이 맞았네요...
올리디버거를 쓰면 이런 귀찮은 짓거리를 안해도 저장된 주소가 문자열 시작주소를 가리키고 있다면 이렇게 친절하게 문자열 내용을 미리 보여줍니다.
IDA도 이런 기능이 있으면 올리디버거를 쓸 이유가 전혀 없을탠데.. 좀 아쉽습니다.
어쨌던 결론을 내려보면 유저 네임은 "Registered User" 고 시리얼 값은 "GFX-754-IER-954" 이 맞네요.
출처
'보안 강좌 > CodeEngn' 카테고리의 다른 글
[CodeEngn] Basic RCE L07 풀이 (0) | 2023.01.16 |
---|---|
[CodeEngn] Basic RCE L06 풀이 (0) | 2023.01.12 |
[CodeEngn] Basic RCE L04 풀이 (0) | 2023.01.09 |
[CodeEngn] Basic RCE L03 풀이 (0) | 2023.01.06 |
[CodeEngn] Basic RCE L02 풀이 (0) | 2023.01.05 |