본문 바로가기

My Study/Reversing

실행 시 함수 얻어오는 바이너리 분석

어떤 프로그램을 보니 Export Table, ImportTable에 아무것도 없는 프로그램이 있길래 
궁금해서 해당 부분을 한번 봐보았습니다. 
( 나름 안티리버싱 기법 인거 같습니다. 프로그램 개발자는 인라인 어셈으로 다 구현한거 같습니다... )


Exeinfo로 봐본 모습입니다. 각 테이블에 아무것도 없습니다.

분명히 프로그램 안에서는 API함수들을 사용할탠데 말이죠.

분석해볼 첫번째 함수입니다.

뭔가 TEB, PEB쭉쭉 들어가고 있는거 같습니다.
그러면 어떠한 값을 리턴하는지 분석해보겠습니다.

TEB부터 쭉 들어와본 결과입니다.

그러면 최종적으로 Flink가 가리키는 곳에서 +8만큼 떨어진 곳의 값을 리턴하는데 그 값이 무엇인지 알아보겠습니다.
일단 InInitializationOrderModuleList는 더블링크드 리스트로 되어 있으며 현재 프로세스에 붙어 있는 각각의 모듈의 정보를 
담고 있는 구조체를 서로 가리키고 있습니다.

그래서 한번 InInitializationOrderModuleList구조체 변수가 있는 곳의 메모리를 봐보았습니다.

ntdll.dll의 정보가 있었습니다. 책에서 찾아보니 따로 구조체가 정의되어 있었습니다.
MSDN에 없는걸보니 비문서화 된 구조체 인거 같습니다.

구조체 이름은 LDR_MODULE입니다.

세번째 변수로 InInitializationOrderModuleList변수가 보입니다.
그러면 다시 위 구조체 형태로 메모리를 봐보겠습니다.


빨간색 네모 친 부분을 따라가보면 그 다음 모듈인 kernel32.dll의 정보가 나옵니다.
그러면 리턴 하는 값이 +8자리의 값이었으므로 0x7C800000입니다.
해당 값은 kernel32.dll의 Base주소 입니다. 즉, 메모리 상에서 MZ가 시작되는 주소이지요.

저렇게 위 어셈 코드에서는 kernel32.dll의 BaseAddress를 가져오는 코드입니다.

이제 저 주소를 가지고 분명히 API주소를 가져올건데 어떻게 가져오는지 봐보도록 하겠습니다.
중요 함수만 보도록 하겠습니다.


첫번째 인자 : 해당 프로세스의 data섹션 중 특정 위치 ( 어떠한 배열인거 같음 )
두번째 인자 : kernel32.dll 의 BaseAddress

이렇게 되는데 Call명령어 전에 2개의 값이 PUSH가 됩니다. 첫번째 값은 kernel32.dll의 BaseAddress인데
두번째 값은 아까 data섹션에 있던 배열 값 중 첫번째 값이었습니다. 
그리고 그 배열 값은 반복문을 돌면서 다음 값을 가리키게 됩니다.

여기서 그 배열에 어떤 값이 있는지 봐보겠습니다.


정말 아무 의미없이 생긴 4Byte값들입니다. 일단 첫번째 Call할땐 0x31A39F15라는 값이 전달이 됬습니다.
이 값이 어떤 의미가 있는지 뒤에서 보도록 하겠습니다.

이제 Call한 함수 내부로 들어가보겠습니다.
여기는 쫌 복잡하여 Comment 단 것 까지 보여드리겠습니다.


일단 일부분 입니다.

먼저 kernel32.dll의 BaseAddress를 사용해 Export Table의 대한 정보를 가져오고 있습니다.
AddressOfName : 함수의 이름이 저장되어 있다.
AddressOfNameOrdinals : 함수 위치 정보 ( 인덱스 )
AddressOfFunctions : 인덱스에서 가리키는 번째의 주소를 가져올 때 여기서 .. 실제 사용되는 함수주소 있음

일단 이렇게 얻어옵니다. 그리고 각 주소들을 지역변수에 담습니다. LOCAL이 지역변수 입니다.
그 아래 코드를 보시겠습니다.


각 함수들의 동작들은 분석해서 적어뒀습니다.

쭉 보시면 AddressOfName배열에서 순서대로 함수 이름 주소를 가져옵니다.
그리고 그 함수 이름을 가지고 바로 찾고 싶은 함수 명과 비교 후 인덱스를 사용해 함수 주소를 가져오면 되는데
중간에 무슨 함수 이름을 소문자로 바꾸고 함수 이름 길이를 또 가져오고 있습니다.

그리고 가장 아래 있는 Call에서는 소문자로 바뀐 함수 이름과 함수 이름 길이를 가지고 이상한 생쇼를 합니다.
아마 인코딩하는거 같았습니다.
분석이 짱나 그냥 Hex Ray로 열어봤습니다.

복잡하군요. 그냥 인코딩하는 루틴이므로 분석할 필요는 없습니다.
최종적으로 어떠한 값을 리턴하는군요.

그리고 위 어셈코드를 보시면 리턴한 그 값과 두번째 인자로 전달됬던 
data섹션의 배열에서 첫번째 값과 비교를 하고 있습니다.

비교 결과 다르면 점프를 해버립니다.

점프문를 하지 않고 안으로 들어왔을 때의 결과를 봐보았습니다. 그 아래 코드를 보시겠습니다.

첫번째로 찾은 함수는 CloseHandle함수 였습니다. 인덱스는 31입니다.
그 아래서는 인덱스 31를 가지고 Export Table에서 해당 함수 실제 주소를 가져와서 리턴하고 있습니다.

이제 이 함수가 끝나고 이 함수를 호출했던 함수로 다시 가보겠습니다.

그냥 여기서 하는 일은 그 리턴된 실제 함수 주소를 전역변수 배열에 넣고
이상한 값이 있던 배열 인덱스를 1 증가시켜 그 다음 값을 가지고 또 Call하고 있습니다.

아마 이상한 값이 있던 테이블에서 0을 가리키게되면 모든 함수를 다 얻어왔고 해당 루틴을 빠져나갈 것입니다.

이제 지금까지 했던 과정을 적어보겠습니다.
1. TEB -> PEB -> LDR_MODULE -> InInitializationOrderModuleList -> Flink로 가서 kernel32.dll의 BaseAddress얻음
2. kernel32.dll의 Export Table -> AddressOfName에서 함수 이름 얻어옴
3. 얻어온 함수 이름을 전부 소문자로 변경
4. 함수 이름의 길이를 구함
5. 소문자로 변경된 함수 이름과 함수 길이를 가지고 인코딩해서 특정 값 생성
6. 전역변수로 있는 특정 테이블에 있는 값과 비교를 해서 다르면 2번으로 가서 그 다음 함수 이름 얻어옴
7. 맞으면 그 때의 인덱스를 사용해 실제 함수 주소 얻어옴
8. 전역 변수 배열에 그 함수 주소를 넣음
9. 이렇게 특정 테이블에 있는 모든 인코딩된 값에 맞는 함수를 구해옴 ( 반복 )

구해진 함수명들을 보겠습니다.


일단 kernel32.dll에선 이렇게 얻어왔습니다. 그 외에도 다른 dll에서 함수를 가져오는데 위와 같은 방법으로 가져오게 됩니다.

사실 이 프로그램은 바이러스 입니다. 안티리버싱 기법으로 이렇게 구현한거 같습니다.
첫번째, Export Table, Import Table을 비워두면서 어떠한 함수를 썼는지 알기 힘들게 함
두번째, 얻어올 함수 이름을 인코딩 시켜 하드코딩 해버려서 문자열 검색으로도 어떠한 함수를 썼는지 알 수 없음

구현했던 사람은 무척 까다롭겠지만 이것은 라이브디버깅을 하면 전혀 문제될 것 없는 코드입니다.

'My Study > Reversing' 카테고리의 다른 글

Cheat Engine의 축하 메시지??  (3) 2010.12.27
Visual C++ SEH Filter,Handler 루틴  (0) 2010.11.11
Machine Code & C Language  (2) 2010.09.28
Stack Based Obfuscation ( Binary Code )  (2) 2010.09.10
C++ 참조자 어셈블리로 어떻게??  (0) 2010.07.27