리버싱 #17 PE패치

이립·2024년 11월 7일
0

리버싱

목록 보기
17/18

PE패치

소프트웨어에 원하는 DLL을 로딩하는 방법으로 전에 dll 인젝션을 배웠다. 하지만 이렇게 인젝션 말고 그냥 직접 pe를 수정해서 dll을 끼워넣어도 된다. 이지하다.

원리 설명하기 전에 실습을 설명한다.

사전 분석

뜯어고칠 실행파일은 Textview.exe라는 것으로 그냥 텍스트를 보여주는 뷰어다.

실행파일


임포트하는 dll 목록을 보려면 rdata 섹션의 IMPORT Directory Table을 보면 된다. 현재는 4개의 dll을 임포트하고 있다.

여기에 우리가 원하는 dll을 추가시켜 총 5개를 만드는 게 목적이다.

DLL

끼워넣을 dll은 myhack3.dll이다. 얘가 하는 기능을 보기 위해 소스코드를 보자. 그리고 세가지 함수를 볼거야.
구조만 보면 이렇다.

Dll Main을 보니까 그냥 원격으로 스레드를 실행하는 것이다. ThreadProc이 콜백되는 걸 보니까 그럼 ThreadProc을 가봐야지.
ThreadProc은 두 함수를 호출한다. 하나는 DownloadURL( )이고 하나는 DropFile( )이다.

1. DownloadURL( )


뭐 이런건데 걍 szURL에 들어온 인터넷 파일을 다운 받아서 szFile에 명시된 경로에 저장하는 것이다. 여기선 구글 사이트에 들어가서 index.html 다운 받을거야.

2. DropFile( )


얘도 복잡하다. 뭐냐면 다운받은 파일이 있을거 아냐. 예를 들어 index.html을 다운받겠지. 그걸 패치한 실행파일 TextViw_patched.exe에 드롭해서 뷰어로 열어서 내용을 보여주게 하는것이다.

방식은

  1. TextView_patched.exe의 메인 window handle을 구한다.
  2. WM_DROPFILES 메시지를 전송한다.
  3. PID로부터 Window Handle을 구해서 PostMessage(WM_DROPFILES) API를 호출한다.

뭐 복잡하다. 하지만 우리가 지금 소스코드 스터디가 아니잖아?
ㄱㅊㄱㅊ

3. dummy 함수 (중요)

사실 얘 설명하려고 하는 것이다. 보면은 함수에 내용이란 게 없다.

아무짝에도 안 쓰는 함수를 export하는 이유는 형식 상 필요해서이다.

PE 파일 입장에서 dll을 임포트한다는 것은,
반대로
DLL 입장에선 내 함수를 익스포트 한다는 것이다.

즉 DLL이 제공하는 함수를 export 함수를 호출하는 것이다. 그래서 PE의 임포트 테이블에 dll을 추가하려면 dll에 형식적이나마 익스포트함수가 있어야 한다.

패치 실습

여기 하려면 PE 구조, 특히 IID, IAT, INT, 이런거 잘 알아야 하니까 헷갈리면 보고 와라.

PE 파일 구조

IDT 분석

PE가 임포트하는 dll 정보들을 모아두는 구조체 리스트가 있다. 그게 Import Directory Table이다.
이 IDT는 IID(IMAGE_IMPORT_DESCRIPTOR)라는 구조체들이 배열되어 있다.

IDT를 찾아가는 주소는 Optional Header의 DataDirectory[1]에 있었지. IMAGE_DIRECTORY_ENTRY_IMPORT의 Virtual Address 멤버 읽으면 알 수 있어.
참고하셔.

IDT의 주소는 rva 84CC다. 84CC 가보자구.

IID들이 쭉 배열되어 있다. 현재 DLL이 4개 로딩되어 있으므로 IID도 4개다. IID는 DLL개수만큼 있어야 하거든.

IID 하나의 사이즈는 20바이트야.
지금 전체 IID들의 영역은 84CC부터 852F까지다.
(852C까지라고 하는 멍청한 짓 금지. 852C부터 시작해서 00 00 00 00 갔으니까 2F까지다.)

파일에서 IID들이 어떤 값이 담겨있는지 보기 위해 rva가 아닌 raw로 offset에 가보자. RVA 84CC는 RAW 76CC라고 한다. PE View가 알아서 바꿔준다.

헥스 뷰어로 가보자.

IID 분석


HxD로 가보면 여긴다. 뭐가 뭔지 알아보기 힘들까봐 내가 정리해줄게.
형광펜으로 하이라이트한 것 하나하나가 다 IID다.

그리고 첫번째 핑크색 iid에서 빨강 동그라미친 멤버가 INT의 주소를 담고 있는 Original First Thunk다.

핑크색으로 동그라미친 멤버가 Name 멤버다.
마지막 보라색 동그라미가 IAT 주소, 즉 First Thunk다.

보라색 형광펜은 00으로 된 null padding인데 IID 배열의 끝을 의미한다.

우리는 여기에 추가로 myhack.dll을 위한 iid를 더 쓰고 싶다. 보라색 null 패딩을 20바이트 뒤로 미루고 그 사이에 글을 적으면 되지만 지금 null 패딩 바로 뒤에 다른 데이터들이 들어와있어서 여기 위에 덮어쓸 순 없다.

그래서 우리는 다른 곳에 IDT를 새로 옮겨야 한다. 다른 더 넓은 곳으로..마치 새식수 생기면 이사가야 하는 것처럼.

IDT를 이동시키는 방법은 세가지이다. 쉬운 난이도부터 적으면

  1. 파일의 비어있는 다른 영역 찾기(예: 무의미한 null 패딩)
  2. 파일 마지막 섹션을 크기를 늘려서 활용
  3. 파일 끝에 아예 새로운 섹션을 마련해주기.

아래로 갈수록 건드려야 할 헤더가 많아지니 복잡하겠지. 걍 빈 공간 찾자.

IDT 이주시키는 방법

보통 섹션들은 alignment 단위의 끝까지는 null로 채워져있지.

ㅇㅇ 글치. 그럼 여기다가 원래 IDT들 다 옮겨주면 되겠네.

근데 확인해야지. 진짜 RVA 8C60~8DFF는 안 쓰는 곳일까?

확인하려면 PE View로 다시 이 .rdata 섹션의 섹션 헤더를 살펴보자.

지금 Size of Raw Data는 2E00이다. 즉 파일에서의 이 섹션의 크기는 2E00이다.

근데 Virtual Size는 2C56이다.

사실 버추얼 사이즈가 더 작다는게 제일 이해가 어려웠다. 파일이 메모리에 로딩될 때 일부가 사용되지 않는다는 것이다. 크기로는 2E00-2C56인 0x1AA만큼이 생긴다는 것이다.

그래서 메모리에서 섹션이 끝나는 지점. 즉 RVA 6000+Virtual Size인 8c56부터는 비어있는 값이라서 써도 된다는 것이다.

근데 내가 궁금한 건 어차피 Section Alignment는 1000이잖아.
.rdata 섹션은 6000부터 8FFF까지 3000만큼은 써도 되면 그냥 저어어ㅓ어기 어디 8E00 이런데다가 IDT 이주시키면 안되나. 그게 더 고민거리를 줄일 것 같은데.

뭐암튼 이제 IDT를 적당히 RVA 8C80에 옮겨두기로 결정했다. RVA 8C80은 오프셋으론 7E80이다.

실전 1단계 IMPORT Table 값 변경

실제로 IDT를 이주시키려면 먼저 IDT의 주소를 담고 있던 구조체에도 수정이 이뤄져야 한다.

IMAGE_OPTIONAL_HEADER의 Import Table 구조체, 즉 DataDirectory[1]을 수정해야 한다.

얘는 RVA라는 멤버랑 Size라는 멤버가 있었지.
RFVA가 84CC인 걸 이주시킨 새주소 8C80으로 바꾸자.

그리고 Size도 원래는 dll 4개였으니까 20바이트 4개+마지막 null 20바이트라서 100바이트= 0x64였던 걸 20바이트 추가해서 0x78로 바꿔야해.

이건 PE View에서 바꾸는게 아니라 HxD에서 바꾸는거야. rva 160이나 offset 160이나 같지. 헤더쪽이니까.얘를요로콤 바꿔줘.

실전 2단계 Bound Import Table 제거

Optional 헤더엔 BOUND IMPORT TABLE이란게 있어. 이걸 아예 지워줄거야. 이게 뭐냐면 DLL로딩 속도를 좀 향상시켜주는 기법인데 왜 지우냐고?

일단 반드시 존재해야 할 필요는 없는데 지금 우리가 DLL 정보들을 수정하는데 DLL 로딩 속도를 다루는 이게 새로운패치랑 다른 채로 남아있으면 실행이 안되는 에러가 생길거니까 그럴바엔 걍 날리자는거야.

그래서 이 값을 0으로 만들어야 하는데 이미 되어있네.

실전 3단계 IDT 이주


기존 IDT는 RVA 84CC부터 852F까지였지,
offset으로는 76CC부터 772F다.

HxD에서 여기를 전부 복사해서, 새 위치에 붙여넣어준다.

새 시작주소 8C80은 Offset으론 7E80이다. 쇽.

실전 4단계 새 dll 추가

이제 새 dll을 삽입하기 위해서 IDT의 끝인 NULL 패딩 20바이트를 한 토막 뒤로 미루고 그 자리에 myhack3.dll의 IID 정보를 넣어야지. 이건 교재가 알려줬다.

어차피 myhack3.dll IID 적고 그뒤에는 다 null이니까 뭐 알아서20바이트만큼만 잘라서 iid의 끝이라고 인식할 것이다.

새 dll 정보를 보자.

멤버값(RVA)RAW로 변환
INT8D007F00
Name8D107F10
IAT8D207F20

교재가 임의로 설정한 이 IID 정보에서 8D00부터해서 이 주소들은 새로 이주한 IDT 주소 밑에 있다. 뭐 눈으로 같이 보기 편하니까 그런거겠지.

여기서 내가 궁금한건 이 새로운 DLL의 IID 정보들은 언제 PE에 적혀있었대? 원래는 없었을 거잖아 언제 8D00이나, 8D10, 8D20에 저런게 적혀있었대??
-> 니가 직접 적는거야.

그리고 저런걸새로 적으려면 또 빈 공간 찾아서 적어야 하지 않나?
-> 그치 빈공간 찾아야하는데 어차피 8D00부터 8FFF까진 다 섹션 빈 공간이잖아.

실전 5단계 dll 정보 입력

오프셋 7F00에 가서 INT, Name, IAT를 채우자.

INT에는 그 DLL이 임포트하는 함수가 들어있다. 8D30 주소로 가면 dummy( )함수의 문자열이 보일거야.

사실 정확하겐 INT 값은 함수 문자열 주소를 가리키는게 아니라 그 함수 문자열과 Hint 값이 담긴 IMAGE_IMPORT_BY_NAME 구조체를 가리키는 포인터야.

Name에는 dll 문자열인 myhack3.dll이 보인다.

IAT 역시 8D30으로 되어있네. 얜 INT랑 같아도 되고 달라도 돼. INT가 정확히 적혀있는게 중요해. 어차피 실행 하면 PE로더가 IAT를 새로 적어. 실제 함수가 적혀있는 정확한 위치로.

실전 6단계. IAT 쓰기 속성 부여

IAT는 PE로더에 의해서 메모리에 로딩될 때 실제 함수가 있는 정확한 주소로 덮어쓰인다고 했다.
그럴려면 IAT는 Write 되는 권한을 가져야 하므로 쓰기 속성을 갖고 있어야 한다.

IAT는 지금 .rdata 섹션에 있으니까 .rdata 섹션 헤더를 보자.


보셈 지금 write 없자나. 쓰기 속성은 80000000이니까 기존값 40000040에다가 추가하면 C0000040이겠지.

HxD로 가서 수정해주자. rva 224는 걍 offset 224니까.

얘를

얼른 바꿨지.

근데 왜 원래도 DLL은 있는데 섹션에 WRITE 권한 없어도 잘 실행된거냐고?
-> Image Optional Header의 DataDirectory 중에서 IMPORT Address Table이 있어. 여기서 명시해주는 주소 영역 범위 안에 IAT가 있었으면 write 속성 없어도 돼.


즉 6000부터 6154 rva 사이에 임포트 되는 함수들이 다 있으면 된다. 가볼까.

봐봐 모든 함수들이 다 여기에 있자나.

실전 번외. IAT 쓰기 속성 안주고 패치하기


원래는 이랬지.

근데 IAT에 쓰기 속성을 안 준다는건 IMPORT Address Table을 활용하는거야. 그럴려면 우선 여기 IMPORT Address Table 마지막에 dummy 함수를 추가해줘.요렇게.

그리고 optional header에 DataDirectory가서 이거 사이즈 8바이트 추가해줘. 늘어난 dummy 함수 주소만큼은 추가해야지.

profile
매일을 쌓는 것
post-custom-banner

0개의 댓글