분석 1
이번 프로그램은 Autoit으로 만들어졌고, UPX로 패킹되어 있다.
이 프로그램은 실행되면 위와 같이 창이 뜨고 몇 초 뒤에 꺼진다.
분석 2 및 풀이
UPX로 패킹이 되어 있기 때문에 EP 코드 부분이 위와 같이 pushad로 시작하는데, UPX는 특성상 복호화 코드가 pushad와 popad 명령어로 감싸져 있다.
스크롤을 쭉 내리다보면 위와 같이 4AF386 주소에 popad 명령어가 있다.
해당 주소에 BP를 걸어두고
4AF394 주소에서 417770주소로 점프하는 명령어에 의해 복호화가 완료된 후 EP 코드 부분으로 점프한다.
417770 주소로 이동하면 위와 같은데 어떤 함수를 호출한 뒤 4175F3 주소로 점프한다.
4175F3 주소는 위와 같다.
F9 키를 눌러 진행하면
위와 같은 메시지 창이 뜨는데
아무래도 디버거 중인지를 검사하는 것 같다.
417770(EP code) -> 417775 -> 4175F3 -> 417700(함수 호출) -> 40EAF9(함수 호출) -> 40EAF9(함수 호출) -> 40E961
위의 루트를 따라오면 위와 같이 IsDebuggerPresent() 함수를 호출한다.
함수의 결과로 eax에 1이 담기면 현재 디버깅 중인 것으로 40E069 주소의 jne 조건 분기가 성립되어 4338DE 주소로 점프하고
4338DE 주소는 "This is a compiled AutoIt script. AV researchers please email avsupport@autoitscript.com for support." 메시지 창을 띄우는 부분이다.
40E967 주소의 jne 명령을 위와 같이 nop으로 채운 뒤 패치된 내용을 저장하고 패치된 파일로 분석을 진행한다.
그래야 IsDebuggerPresent()로 인한 디버깅 방지가 우회된다.
문제에서 몇 밀리세컨드 후에 종료하는지를 물어봤기 때문에 타이머와 관련되어 있을 것이고, 대표적인 타이머 함수가 SetTimer와 KillTimer 함수가 있다.
x32에서 SetTimer 함수를 검색해보니 위와 같이 4개가 나왔고
4개의 SetTimer 함수의 인자들을 확인하니 위와 같이 4개의 인자를 받는다.
그 중 두 번째 인자가 각각 2EE, 2EE, 40A, 28이다.
하지만 위 4개 모두 답이 아니다.
x32dbg에서 time을 검색하니 위와 같이 나오는데
그 중 TimeGetTime부터 보기로 한다.
TimeGetTime을 입력해 필터링하니 위와 같이 총 13개가 나오는데
444C3E 주소만 timegettime 함수를 호출하는 부분이 아니라 edi 레지스터에 timegettime 함수의 주소를 저장하고 있다.
444C3E 주소에 BP를 걸고 F9 키를 이용해 실행한다.
https://learn.microsoft.com/ko-kr/windows/win32/api/timeapi/nf-timeapi-timegettime
444C44 주소에서 timegettime()를 호출하는데, timegettime() 함수는 특정 이벤트가 수행된 시간을 구할 때 쓰는 함수이며, edi 레지스터에 timegettime 함수의 주소를 넣고 edi를 이용해 호출한다.
timegettime 함수를 호출 후 48E8D3 주소의 값과 0을 비교하는데 48E8D3 주소에는 01이 담겨있다.
eax를 esi에 옮긴 후 위의 비교문에서 같지 않았으므로 444D54 주소로 점프하지 않는다.
ebx에 esp+14 주소에 있는 데이터를 저장하고 ebp에 sleep 함수의 주소를 담은 뒤 edi를 호출하는데 edi는 timegettime 함수의 주소가 담겨있다.
444C5F 주소에서 timegettime 함수를 호출한 뒤 반환값이 eax에 담기는데 esi와 eax를 비교한다.
(esi는 444C4D 주소에서 timegettime 함수의 반환값이 담긴다.)
eax의 값은 1A615A6이고, esi의 값은 1A5E6A7이며 EAX가 더 크기 때문에 444C63 주소의 조건 분기는 성립되어 444D38로 점프한다.
eax의 값에서 esi의 값을 뺀 후 eax의 값이 ebx+4 주소에 있는 값보다 이상이면 444C71 주소로 점프하고, 작으면 스택에 A라는 값을 넣고 ebp에 담긴 함수를 호출한 뒤 48E8D3 주소의 값과 0을 비교후 다르면 444C5F 주소로 점프한다.
444C5F는 timegettime 함수를 호출하는 부분이다.
그럼 다시 위의 부분을 보면 timegettime() 함수를 호출하고, 반환값을 eax에 담은 뒤 esi에 저장한다.
그리고 esp+14 주소의 값을 ebx에 담고, ebp에는 sleep 함수의 주소를 담는다.
그 후 timegettime() 함수를 한 번 더 호출하고, 반환값을 eax에 담은 뒤
첫 번째 호출한 timegettime() 함수의 반환 값 esi와 두 번째 호출한 timegettime() 함수의 반환값 eax를 비교하여
eax의 값이 esi의 값보다 이상이면, 444D38 주소로 점프한다.
즉, 프로그램을 실행했을 때의 시간과 프로그램이 실행된 후 몇 초가 지났을 때의 시간을 비교하는 것이다.
점프한 후 eax의 값에서 esi의 값을 뺀 값 즉, 프로그램이 실행된 후 경과된 시간과 ebp+4 주소에 있는 값을 비교 후
eax의 값이 ebp+4 주소에 있는 값 이상이면 444C71 주소로 점프한 뒤 메시지 창을 띄우고
eax의 값이 더 작으면 A(10)을 스택에 넣고, ebp에 있는 함수(sleep 함수)를 호출한 뒤 48E8D3 주소의 값과 0을 비교후 0이 아니면 444C5F 주소로 가서 timegettime() 함수를 호출한다.
timegettime() 함수 호출 후 esi(맨 처음에 timegettime() 함수 호출 후 반환값)보다 이상이면 444D38 주소로 점프한뒤 위에서 분석한 내용을 반복한다.
위의 부분에서 eax와 ebx+4 주소의 값을 비교하는데 eax는 프로그램이 시작되고 켜져 있는 시간이 담겨있고 ebx+4에는 지정된 밀리세컨드 값이 담겨있다고 볼 수 있고
해당 미리세컨드 값보다 이상이면 메시지 창을 종료하고
밀리세컨드 값보다 작으면 timegettime() 함수를 다시 호출해 켜져 있는 시간을 다시 측정하고 해당 값이 밀리세컨드 값보다 큰 지 작은지 검사한다.
그렇다면 정리했을 때 444D3A 주소에서 eax는 프로그램이 켜진 후 경과된 시간이 담겨 있고, ebx+4에는 정해져 있는 시간이 담겨 있으며, eax가 정해진 시간보다 같거나 크다면 444C71 주소로 가서 종료하게 되고, 정해진 시간보다 작다면 계속해서 timegettime() 함수를 호출한 뒤 반환값에 esi의 값을 빼고 해당 값이 ebp+4 주소의 값과 비교한다.
그러므로 ebp+4의 값을 보면 위와 같이 2B70이다.
2B70을 10진수로 하면 11,120인데
1ms = 1000분의 1인 시간이고, 10ms는 100분의 1, 100ms는 10분의 1, 1000ms는 1초인 것이다.
그러므로 11,120ms는 11.12초이다.
인증
위의 URL에 들어가서
위와 같이 입력하면 된다.
'전쟁 > codeengn' 카테고리의 다른 글
[codeengn] Advance RCE 01 (0) | 2022.10.19 |
---|---|
[codeengn] Basic RCE 20 (0) | 2022.10.17 |
[codeengn] Basic RCE 18 (0) | 2022.10.14 |
[codeengn] Basic RCE 17 (0) | 2022.10.12 |
[codeengn] Basic RCE 16 (0) | 2022.07.31 |