보안 공부하는 꼬깔이

- ImagePrc

 

ImagePrc를 클릭해 문제를 다운받자.

 

[ Exeinfo Pe ]

패킷여부 : Not packed

제작언어 : C++

 

 

문제 파일을 실행 시켜보면 흰 백그라운드의 창하나와 CHECK 버튼으로 구성된 창이 하나 뜬다.

흰 백그라운드의 창에는 마우스로 클릭하고 움직이면 사진과 같이 줄이 그어진다.

 

Check 버튼을 클릭하면 해당 시도가 틀렸을때 나오는 문구가 출력된다.

제출자가 원하는 그림을 그려서 제출하는 문제인가...?

 

해당 프로그램에서 사용되는 라이브러리 목록이다.

그래픽과 GUI에 관련된 작업들을 할때 사용하는 라이브러리들이 무수히 존재한다.

이번 문제에서도 역시 실패 했을때 출력되는 문구인 "Wrong" 을 찾아 분석을 시작해보자

 

Wrong 을 출력하는 어샘코드 부분이다.

004013AA 주소의 분기문으로 인해 "Wrong" 이 출력되는 코드로 분기하는 모양이다.

그렇다면 분기문을 만나기전 비교를 하는 구문이 있을것이다.

위 사진을 보면 실제로 레지스터에서 어떠한 작업이 이루어지고 그것을 CMP 레지스터로 비교하는 부분이 존재한다.

 

더욱 위로 올라오면 FindResourceA, LoadResource , LockResource 등의 함수들이 사용되는 것을 확인 할 수 있다.

해당부분은 이러한 함수들로인해 리소스를 찾고 그 리소스가 존재하면 Load를 하게끔 코드가 구성되어있다.

 

FindResourceA 함수를 call 하는 부분에 브레이크 포인트(F2)를 걸고 프로그램을 시작한 뒤 FindResourceA 함수를 사용하고 난 뒤 결과값을 확인했더니 리소스가 저장되어 있는 주소의 위치가 0x47E048 주소 라는것을 알게 되었다. 

그리고 해당 리소스를 LOAD해서 EAX에 저장시킨다.

 

리소스를 찾고 로드하는 함수들의 과정이 끝나면 해당 코드가 수행된다.

ESI 레지스터에는 사용자가 그린 그림의 영역 주소가 저장되고 EAX에는 리소스 영역의 주소가 저장된다.

결국 해당 코드는 사용자가 입력한 그림과 리소스 여역의 그림을 픽셀 단위로 0x15F90 번 만큼 반복하면서 검사를 진행한다.

검사도중 일치하지 않는 부분이 발견되면 그 즉시 "Wrong!" 문구가 출력되는 쪽으로 분기를 시켜버린다.

반대로 검사하는 모든 리소스가 일치하여 반복문이 끝나면 특정한 영역을 CALL 하게 된다.

 

그렇다면 어떤식으로 프로그램에 저장되어 있는 리소스를 알 수 있을까?

 

프로그램안에 이미지에 해당하는 데이터가 존재하면 그것을 MainFest에 저장하게 된다.

실제로 PEview를 통해 imageprc.exe 파일의 ManiFest를 확인해보니 리소스가 존재했다.

그리고 해당 리소스를 확인했을떄 256의 색을 FF이하로 표현을 하며 비트맵 방식을 사용하는 BMP 이미지 파일로 유추를 하였다.

 

해당 리소스를 resource_hacker 툴을 사용해 파싱한 후 해당 내용을 Notepad++ 에 복사 후 저장했다.

현재 리소스에는 해더가 존재하지 않기 때문에 우리가 임의로 BMP 해더를 넣어주어야 한다.

 

그리고 리소스의 본래 사이즈를 코드상에서 알아내었다. 가로의 크기는 0x96(200) 세로의 크기는 0xC8(150) 이다

 

그림판에서 가로 200, 세로 150의 크기를 가진 임의의 파일을 하나 만들고 해당 파일을 HXD 툴을 이용해 파싱한 리소스에 BMP 해더를 붙여 저장하게 되면 어떤 문구가 적힌 BMP 파일이 출력되는데 해당파일이 우리가 입력한 그림과 비교를 하던 리소스 파일이며 이 문제의 정답이다.

 

 

ImagePrc clear!

 

 

 

 

 

 

 

 

 

- Replace

 

 

Replace 문제를 클릭해 다운받자!

 

[ Exeinfo Pe ]

패킹여부 : Not packed

제작언어 : C++

 

 

문제파일인 Replace.exe 파일을 실행하면 위 사진과 같은 사용자가 입력가능한 텍스트 박스와 그 값을 넘겨주는 버튼이 존재하고 Wrong 이라는 문구가 출력되어있다.

텍스트 박스는 오직 숫자만을 입력하게끔 되어있다.

 

임의의 숫자를 입력하고 Check 버튼을 클릭하면 작동이 중지되었다는 문구가 출력되고 프로그램이 종료된다.

현재 PC의 문제라고 생각되어 다른 PC에서 테스트 해보았는데 똑같은 현상이 나타났다.

이런 현상부터 문제의 시작이 아닌가하여 디버깅을 해보니 문제의 의도였다!

왜 이런 오류가 뜨는지 디버깅을 통해 알아보도록 하자

 

프로그램을 실행시키고 임의의 숫자를 입력 후 0040466F 주소를 보면 0x90(NOP)를 EAX에 저장하는데 여기서 문제가 발생한다.

 

현재 EAX에는 "601605CC" 라는 주소가 저장되어 있는데 해당 주소는 접근이 불가능한 주소이다.

접근이 불가능한 주소에 0x90 이라는 데이터를 저장하려고 시도하니 에러가 출력되는 것이다.

프로그램이 오류를 출력하고 뻗어버리는 이유를 알았으니 본격적인 분석을 시작해보도록 하자 .

 

프로그램 시작시 사용자가 임의의 숫자를 입력할 수 있는 부분까지 정상적으로 동작하는 것을 생각해보면 해당 기능을 수행하는 함수 역시 정상 동작을 한다는 것이다. 그렇다면 해당 기능을 하는 함수를 기준으로 분석을 해보도록 하자.

현재 프로그램에서 사용하고있는 함수 목록을 출력해주는 기능을 사용하자. (Ctrl + N)

위 사진을 보면 GetDigItemInt 함수가 보일것이다. 해당 함수는 입력된 정수형 데이터를 얻는 함수이다.

아무래도 DialogBoxParamA 함수가 사용자 입력 창을 띄워주고 사용자가 값을 입력하면 GetDigltemlnt 함수가 입력된 정수형 데이터를 가져오는 구조인듯 하다.

 

GetDigltmelnt 함수의 CALL 부분이 위치한 0040105A주소에 브레이크 포인트(F2)를 걸고 실행시켜보자.

 

해당 함수는 사용자가 입력한 숫자를 16진수로 변환해 EAX에 저장하는 모양이다.

위 사진을 보면 내가 입력한 "1234"를 "0x4D2"로 변환해 저장한 것을 알 수 있다.

함수 호출이 끝나면 EAX에 저장된 값을 4084D0 주소에 저장한다. 그 다음 0040466F 주소를 CALL 하는데 이 주소는 처음에 봤던 접근이 불가능한 주소에 NOP 주소를 저장시키려고 시도하여 오류가 출력되던 부분이다.

0040466F 주소를 CALL 해서 어떤 과정을 진행하는지 확인하기 위해 해당 주소로 진입(F7)해보자

0040466F 주소에 진입하면 위 사진의 코드가 출력되는데 0040466F 부분을 보면 0040467A 주소를 CALL 한다.

0040467A 주소는 어떤 과정을 진행하는지 살펴보기위해 주소로 진입해보자.

어샘 코드를 분석해보면 0x619060EB의 값을 406016 주소에 넣고 00404689 주소를 호출하는데 이 주소는 04084D0 주소의 DWORD 값을 1만큼 증가시키는 동작을 수행한다. 04084D0 에는 처음에 내가 입력한 "1234" 가 16진수로 변환되 저장된 값인 "0x4D2" 이 저장되있으며 1만큼 증가해 04084D0에는 "0x4D3" 이 저장된다.

그리고 Retn을 하는데 00404684 주소에서 00404689 주소를 CALL 했기때문에 00404689 주소로 돌아가 4084D0에 저장되어 있는 "0x4D3" 에 한번더 1만큼 증가시켜준다. 그러므로 4084D0에 저장되는 값은 "0x4D4" 가 되며 다시 RETN을 수행하게 된다.

두 번째로 리턴되는 주소는 0X404674 이다.

 

404674 주소의 코드를 확인하면 4084D0 주소에 601605C7을 저장한다.  이렇게 되면 4084D0 주소에 저장되어 있는 "0x4D4"      에 "601605C7" 가 합해진 "60160A9B" 이 주소가 사용자 값이 위치한 주소가 되는것이다.

그리고 00404684 주소를 보면 아까 4084D0 주소에 저장된 값에 2를 더해준 00404689 주소를 호출하는것을 확인할 수 있는데 이렇게 되면 다시한번 4084D0 주소에 저장된 값에 2를 더해 "60160A9D" 주소가 4084D0 주소에 저장 되게 된다.

하지만 전과 달리 RETN 되는 위치는 CALL STACK 상의 0x40106A 주소로 리턴된다.

0040106A 주소에서는 EAX를 초기화 하고 404690 주소로 분기한다.

 

분기한 404690 주소를 보면 사용자 입력값이 저장된 주소를 저장하고 있는 4084D0 주소의 값을 DWORD 크기 만큼 EAX에 넣는다.

그리고 4084D0에 2를 INC 시켜주는 주소인 00404689를 한번 더 호출한다. 그래서 4084D0에 저장된 주소는 "60160A9F" 가 된다.

404689 주소로 인해 40469F 주소로 리턴하면 0xC39000C6 값을 40466F 주소에 넣은 후 40466F 주소를 호출한다.

40466F 주소는 이전에 접근 불가능한 주소에 NOP를 저장하려해 문제가 발생한 영역이다. 처음 디버깅에서는 분명 문제가 없었는데 현재는 이러한 오류가 발생했다. 이유는 40466F 의 코드가 40469F 주소의 코드에 의해 변경되었고 EAX에 4084D0 주소의 값을 넣는 코드를 실행 하는 과정에서 해당 코드를 주소로 삼아서 0x90(NOP)를 넣으려 하니 처음에 봤던 오류가 발생하게 되는 것이였다.

만약에 EAX에 정상적인 주소가 들어있는 상태라면 그 다음은 어떤동작을 수행할까?

아마 그 주소에 NOP를 생성할 것이다.

 

그리고 464672 주소의 RETN 명령어가 실행되는데 위 사진의 스택상태를 보면 리턴할 주소는 4046AE 주소이다.

 

4046AE 주소를 확인하면 정상적인 주소가 들어있다고 가정한 EAX에 1만큼 증가를 시키며 그 주소가 EAX에 저장될 것이다.

그리고 40466F 주소를 호출하는데 해당 주소로 인해 또 다시 0x90(NOP)를 저장하는 코드로 넘어가게 되며 NOP를 입력후 4046B4주소로 RETN 하게 된다.

4046B4 주소에서는 40466F 주소에 0x6E8을 저장한다.

그리고 4046BE 주소에서 POP EAX가 실행되여 남아 있던 RETN 주소인 40469F 주소가 정리되고 4046C4 주소의 분기문으로 인해 401071 주소로 분기한다.

 

분기한 401071 주소를 보면 401084 주소로 분기 시킨다. 그리고 40107E 주소를 확인하면 SetDigitemTextA 함수가 사용되며 401073 주소에는 Correct! 라는 성공을 의미하는 문구가 존재한다.

이렇게 되면 현재 이 함수는 다이얼로그 박스에 표시된 Wrong 를 Correct로 변경시켜주는 역할을 한다는 것을 알 수 있다.

그런데 401071 주소의 분기문으로 인해 "Correct!" 구문을 출력해주는 setDlgItemTextA 함수를 지나치게 된다. 결론적으로 401071주소의 코드를 우회할 방법을 강구하면 문제를 해결할 수 있을 것이다.

어떤 방법을 이용하면 우회가 가능할까 한참 고민하다가 401071 주소의 코드가 "EB 11" 2byte 크기의 코드라는 것을 확인했고 동시에 0x90(NOP) 값을 두 번 저장했던것이 생각났다. 그 0X90(NOP) 코드가 401071,401072 주소에 입력된다면 기존에 있던 분기문코드는 덮어 씌워지게 되고 SetDIGitemTextA 함수가 실행되어 "Correct!" 문구가 출력될 것이다.

[ 정리 ]

처음에 접근이 불가능한 주소에 0x90(NOP)를 저장하려해 오류가 발생한 부분은 40466F 주소에서 EAX 에 401071 주소가 저장된다면 해당 주소의 2Byte 코드가 0x90(NOP)가 되는데, EAX는 사용자가 입력했던 값이 저장되는 주소인 4084D0 주소의 값에 해당함으로 코드상의 연산을 끝내고 난 후 값이 401071 주소가 저장되는 어떠한 수를 넣으면 분기문의 코드가 NOP로 덮어 씌워 질것이고 문제가 풀릴것이다.

계산식 : FFFFFFFF - 601605CB(601605C7+inc+inc+inc+inc) + 401071 = "정답(16진수로 계산하고 10진수로 AUTH창에 넘겨야 함)"

 

 

Replace Clear!

 

 

- Easy Unpack


Easy Unpack 문제를 클릭하여 다운받도록 하자.

문제를 다운받고 압축을 푸면 Easy_Unpack.exe 파일과 함께 ReadMe.txt 파일이 존재했다.


ReadMe.txt 파일안에는 OEP를 찾으라는 문구가 적혀 있었다.

Pack 되어 있는 파일들은 프로그램 시작주소가 이상한 곳을 가르킨다.

이런 상태에서 디버깅을 진행하다 보면 코드가 Unpack 되어 보여지는 순간이 있는데 이 때의 프로그램 주소들을 확인하면 정상적으로 분석이 가능하며, 이 때 디버깅을 시도 했을 때의 시작 주소가 "OEP" 이다.


프로그램을 실행하면 흰 배경의 창하나를 출력하는 것 이외에는 아무런 동작을 하지 않는다.


ollydbg로 디버깅을 시도 해보면 패킹이 되어 있기 때문에 엔트리 포인트가 정상적이지 않다.

그리고 무수히 많은 분기문과 반복을 통해 의미없는 값들을 저장한다.


무수히 존재하는 분기문의 마지막 분기점을 확인하면 "OEP" 주소를 확인 할 수 있다.

해당 부분에 브레이크 포인트(F2) 걸고 프로그램을 시작시켜 OEP 주소를 찾아내는 Manual Unpacking 방식을 사용해보자.


마지막 분기점의 OEP주소에서 다시한번 프로그램을 실행하면 이렇게 unpaking 되어 Define Double word 를 확인 할 수 있다. 여기서 ctrl+A 단축키를 이용하면 code Analyze 기능을 통해 코드를 확인 할 수 있다.


Unpack 된 코드가 출력되었다.


[ 정답 ]

OEP 주소



Easy Unpack Clear!