힙 메모리는 스택과 다르게 RET, SEH가 존재하지 않으므로 공격을 성공시키기 위해 여러가지 다양한 기법들이 동원됩니다.
주로 힙 메모리에 존재하는 다른 오브젝트, 포인터 및 헤더 등을 덮어쓰는 방법으로 공격합니다.
예제 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char* buf1 = (char*)malloc(0x20);
char* buf2 = (char*)malloc(0x20);
printf("buf1: %p\n", buf1);
printf("buf2: %p\n", buf2);
memset(buf1, 'A', 0x30);
free(buf1);
free(buf2);
return 0;
}
32바이트로 동적할당을 합니다.
그리고 memset의 buf1의 48바이트를 채워서 buf2의 일부를 침범합니다.
windbg로 확인을 해보겠습니다.
힙 버그들은 감지하기 어려움으로, gflag에 page heap을 활성화 하고 확인을 하면 됩니다.
1바이트만 넘어가도 즉시 크래시를 탐지합니다.
(21a8.3af8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=41414141 ebx=05a62fe0 ecx=00000010 edx=05a60fe0 esi=762f0130 edi=05a60ff0
eip=7490e809 esp=008ff8c8 ebp=008ff940 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
VCRUNTIME140!memset+0xf9:
7490e809 f30f7f4710 movdqu xmmword ptr [edi+10h],xmm0 ds:002b:05a61000=????????????????????????????????
eax가 414141로 덮였네요.
디스어셈블을 보면 다음과 같습니다.
7490e805 f30f7f07 movdqu xmmword ptr [edi], xmm0
7490e809 f30f7f4710 movdqu xmmword ptr [edi+10h], xmm0
buf1에 0x30을 덮으려고 해서 overflow가 발생했습니다. buf1은 edi
dd edi로 edi의 값을 확인을 해보면 다음과 같습니다
0:000> dd 05a60fe0
05a60fe0 41414141 41414141 41414141 41414141
05a60ff0 41414141 41414141 41414141 41414141
05a61000 ???????? ???????? ???????? ????????
05a61010 ???????? ???????? ???????? ????????
05a61020 ???????? ???????? ???????? ????????
05a61030 ???????? ???????? ???????? ????????
05a61040 ???????? ???????? ???????? ????????
05a61050 ???????? ???????? ???????? ????????
총 32바이트를 할당받았고, 나머지는 page heap에 감지가 되었습니다.
이제 page heap을 끄고, main의 bp를 걸고 분석을 해보겠습니다
디스어셈블한 결과를 보니 다음과 같네요
009e1053 57 push buf1 (edi)
009e1054 6808219e00 push 9E2108h
009e1059 8bd8 mov ebx, eax
009e105b e8b0ffffff call Project14!printf (9e1010)
009e1060 53 push buf2 (ebx)
009e1061 6814219e00 push 9E2114h
009e1066 e8a5ffffff call Project14!printf (9e1010)
buf1은 edi라는 것을 알 수 있습니다. 그러면 오버플로우가 발생한 지점으로 가보겠습니다
발생한 지점으로 가고 edi를 확인을 해보면, 48바이트가 41로 덮인 것을 알 수 있습니다.
0:000> dd edi
00e33ce0 41414141 41414141 41414141 41414141
00e33cf0 41414141 41414141 41414141 41414141
00e33d00 41414141 41414141 41414141 41414141
하지만 크래시는 발생하지 않았다는 것을 알 수 있습니다.
!address buf1의 시작주소
Usage: Heap
Base Address: 00e30000
End Address: 00e3f000
Region Size: 0000f000 ( 60.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 00e30000
Allocation Protect: 00000004 PAGE_READWRITE
More info: heap owning the address: !heap -s -h 0xe30000
More info: heap segment
More info: heap entry containing the address: !heap -x 0xe33ce0
Content source: 1 (target), length: b320
heap 영역이고 commit 할당되어 있다는 것을 알 수 있습니다.
0:000> !heap -x 0xe33ce0
ERROR: Block 00e33d00 previous size 448 does not match previous block size 5
HEAP 00e30000 (Seg 00e30000) At 00e33d00 Error: invalid block Previous
힙 오버플로우로 인한 청크 메타데이터 손상을 보여주고 있습니다.
0xe33ce0 은 malloc한 버퍼 시작 주소입니다.
오류 메세지를 다시 살펴보면 0x00e33d00는 이 청크 다음 청크의 주소입니다.
이 다음 청크가 "내 앞 청크는 448"이라고 하는데, 현재 청크의 크기는 0x28입니다. 청크 경계와 크기 정보가 불일치합니다
즉, 현재 청크의 끝부분 또는 이후 메모리를 오버플로우 -> 다음 청크의 PrevSize 필드가 덮여버렸습니다.
0:000> dd 00e33d00
00e33d00 41414141 41414141 41414141 41414141
청크의 메타데이터 영역이 41 41 41 41로 오염되었습니다. 앞에서 오버플로우한 청크가 0x00e33d00 이후 메타데이터까지 덮었고, Flink/Blink/Size/PrevSize 같은 필드가 망가졌습니다.
다음 예제코드를 살펴보겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char* buf1 = (char*)malloc(0x20);
char* buf2 = (char*)malloc(0x20);
printf("buf1: %p\n", buf1);
printf("buf2: %p\n", buf2);
memset(buf2, 'A', 0x30);
free(buf1);
free(buf2);
return 0;
}
이번에는 buf2에 memset을 하고 있습니다. 이러면, 덮을 곳이 존재하지 않는데요. 이래도 힙 오버플로우로 덮을 수는 있긴합니다
0:000> r
eax=00b90fb8 ebx=00b90fb8 ecx=00000010 edx=00b8a628 esi=75d30100 edi=00b8a628
eip=006c1075 esp=007cf730 ebp=007cf7a4 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
buf2 (ebx) 00b90fb8
buf1 (edi) 00b8a628
buf2가 buf1을 덮을 수는 없습니다.
0:000> dd ebx
00b90fb8 41414141 41414141 41414141 41414141
00b90fc8 41414141 41414141 41414141 41414141
00b90fd8 41414141 41414141 41414141 41414141
00b90fe8 00b8008c 00b8008c 00b80038 00b80038
00b90ff8 00b91000 000ee000 ???????? ????????
00b91008 ???????? ???????? ???????? ????????
00b91018 ???????? ???????? ???????? ????????
00b91028 ???????? ???????? ???????? ????????
하지만 정상적으로 덮히기는 한 것을 알 수 있습니다.
그 다음 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char* buf1 = (char*)malloc(0x20);
char* buf2 = (char*)malloc(0x20);
printf("buf1: %p\n", buf1);
printf("buf2: %p\n", buf2);
memset(buf2, 'A', 0x50);
free(buf1);
free(buf2);
return 0;
}
0x50으로 수정하고 다시 memset을 해서 보면
0:000> dd ebx
0086afc0 41414141 41414141 41414141 41414141
0086afd0 41414141 41414141 41414141 41414141
0086afe0 41414141 41414141 41414141 41414141
0086aff0 00869290 00869290 00869290 00869290
0086b000 00869290 00869290 00868f08 00869290
0086b010 00869290 00869290 00869290 00869290
0086b020 00869290 00869290 00868f08 008634a8
0086b030 00869290 00000000 0a97c2be 080056f5
위와 같이 되네요
0:000> !address ebx
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
Usage: Heap
Base Address: 007e0000
End Address: 007f1000
Region Size: 00011000 ( 68.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 007e0000
Allocation Protect: 00000004 PAGE_READWRITE
More info: heap owning the address: !heap -s -h 0x7e0000
More info: heap segment
More info: heap entry containing the address: !heap -x 0x7f0fb8
Content source: 1 (target), length: 48