Jump to content

Kernel patches hunters ... UNITE to dominate them! :)


Recommended Posts

  • Supervisor

Waiting for the managers of the patches for owners of AMD CPUs to decide to release the new ones for everyone

Below is a simple method to start understanding how you can move to study on your own Several years ago Pikeralpha gave directions

 

if you want below you can try it too

https://github.com/Piker-Alpha/ssdtPRGen.sh/issues/261#issuecomment-246691848

 

then analyse Patches name you need to find ie :

 

i386_switch_lbrs

 

and search with an editor you prefer:

Spoiler

i386_switch_lbrs:
ffffff80003b2f50    pushq    %rbp
ffffff80003b2f51    movq    %rsp, %rbp
ffffff80003b2f54    pushq    %r15
ffffff80003b2f56    pushq    %r14
ffffff80003b2f58    pushq    %r12
ffffff80003b2f5a    pushq    %rbx
ffffff80003b2f5b    movq    %rsi, %r12
ffffff80003b2f5e    testq    %rdi, %rdi
ffffff80003b2f61    je    0xffffff80003b2f77
ffffff80003b2f63    movq    _kernel_task(%rip), %r14
ffffff80003b2f6a    cmpq    %r14, 0x6a8(%rdi)
ffffff80003b2f71    setne    %sil
ffffff80003b2f75    jmp    0xffffff80003b2f80
ffffff80003b2f77    movq    _kernel_task(%rip), %r14
ffffff80003b2f7e    xorl    %esi, %esi
ffffff80003b2f80    movq    0x6a8(%r12), %r15
ffffff80003b2f88    testb    %sil, %sil
ffffff80003b2f8b    jne    0xffffff80003b2f96
ffffff80003b2f8d    cmpq    %r14, %r15
ffffff80003b2f90    je    0xffffff80003b3165
ffffff80003b2f96    movl    $0x1d9, %ecx            ## imm = 0x1D9
ffffff80003b2f9b    rdmsr
ffffff80003b2f9d    andl    $-0x2, %eax
ffffff80003b2fa0    movl    $0x1d9, %ecx

Above spoiler is the patch location in 11.4 kernel

 

obviously is not so simple to solve in many cases..

but I think it is a good start if you want understand for your own

 

to transform kernel in a txt files you can use this command:

 

otool -tV /S*/L*/Kernels/kernel > ~/Documents/kernel.txt

 

I would like to advice to start with previous kernel (with yet discovered kernel patches I mean) 🙂

 

  • Like 2
  • Cross Finger 1
Link to post
Share on other sites
  • fabiosun changed the title to Kernel patches hunters ... UNITE to dominate them! :)
  • 2 weeks later...

https://github.com/mhaeuser/AmdXnuSupportPkg 

 

This is the most promising project to correctly emulate a real Intel CPU, without the hassle to patch the xnu kernel at every release... unfortunately we lost the contribute of the original developer because he retired, probably because of the bad behaviour of the AMD "community", so trying to make it work will be a big trouble...

Edited by tomnic
  • Like 1
  • +1 1
Link to post
Share on other sites

This is what I've understood starting from the debug log of clover, I think it is similar with OC.

 

For example, let's pick the patch algrey - cpuid_set_info - jmp to calculations and set cpuid_cores_per_package - 10.15/10.16 it is complete because we have a hex find, a mask find, a hex replace and a mask replace. It says:

 

Find: 7571E800000000488B050000000048890500000000
Mask: FFFFFF00000000FFFFFF00000000FFFFFF00000000

     Replace: 744E000000000090890D00000000E97E0000006690
Replace Mask: FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF

 

How does the patch engine work? We are searching a hex pattern, the "find" one, but we're using a byte mask: this changes a bit our search criteria:

 

7571E8 00000000 488B05 00000000 488905 00000000 (find)
FFFFFF 00000000 FFFFFF 00000000 FFFFFF 00000000 (mask find)
-----------------------------------------------
7571E8 xxxxxxxx 488B05 xxxxxxxx 488905 xxxxxxxx

 

So we're effectively searching 7571E8 xxxxxxxx 488B05 xxxxxxxx 488905 xxxxxxxx ... where xxxxxxxx is a wildcard! Every hex combination found in place of xxxxxxxx of its exact bytes lenght is good.

 

Looking in the clover debug log we can find some precious info about the patches:

 

55:733  0:010  OC: Kernel patch [9] algrey - cpuid_set_info - jmp to calculations and set cpuid_cores_per_package - 10.15/10.16
55:749  0:015  Replace 7571E800000000488B050000000048890500000000/FFFFFF00000000FFFFFF00000000FFFFFF00000000(7571E8B6090000488B0577EAED0048890538EBED00) by 744E000000000090890D00000000E97E0000006690/FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF(744EE8B609000090890D77EAED00E97E0000006690) at ofs:1273

 

The effective code found in the kernel is:

7571E8B6090000488B0577EAED0048890538EBED00

7571E8 B6090000 488B05 77EAED00 488905 38EBED00

7571E8 xxxxxxxx 488B05 xxxxxxxx 488905 xxxxxxxx

 

...a nice match with our find mask!!!

 

So now in the replace side we do the same calculations:

744E000000000090890D00000000E97E0000006690
FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF

744E 0000000000 90890D 00000000 E97E0000006690
FFFF 0000000000 FFFFFF 00000000 FFFFFFFFFFFFFF
----------------------------------------------
744E xxxxxxxxxx 90890D xxxxxxxx E97E0000006690

 

We must replace in the actual found hex bytes "string" found with the previous calculations, 7571E8B6090000488B0577EAED0048890538EBED00:

 

7571E8B6090000488B0577EAED0048890538EBED00 (actual found code)

744E000000000090890D00000000E97E0000006690 (replace data)
FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF (replace mask)

744E 0000000000 90890D 00000000 E97E0000006690 (replace data)
FFFF 0000000000 FFFFFF 00000000 FFFFFFFFFFFFFF (replace mask)

7571 E8B6090000 488B05 77EAED00 48890538EBED00
744E xxxxxxxxxx 90890D xxxxxxxx E97E0000006690
----------------------------------------------
744E E8B6090000 90890D 77EAED00 E97E0000006690

 

The xxxxxxxx hex "string" must be left exactly as it has been found, the other bytes must be replaced... so the final actual replace is 744EE8B609000090890D77EAED00E97E0000006690.

 

Offsets are calculated and applied automatically by OC / Clover kernel patch engine.

 

 

Link to post
Share on other sites
7 hours ago, tomnic said:

This is what I've understood starting from the debug log of clover, I think it is similar with OC.

 

For example, let's pick the patch algrey - cpuid_set_info - jmp to calculations and set cpuid_cores_per_package - 10.15/10.16 it is complete because we have a hex find, a mask find, a hex replace and a mask replace. It says:

 


Find: 7571E800000000488B050000000048890500000000
Mask: FFFFFF00000000FFFFFF00000000FFFFFF00000000

     Replace: 744E000000000090890D00000000E97E0000006690
Replace Mask: FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF

 

How does the patch engine work? We are searching a hex pattern, the "find" one, but we're using a byte mask: this changes a bit our search criteria:

 


7571E8 00000000 488B05 00000000 488905 00000000 (find)
FFFFFF 00000000 FFFFFF 00000000 FFFFFF 00000000 (mask find)
-----------------------------------------------
7571E8 xxxxxxxx 488B05 xxxxxxxx 488905 xxxxxxxx

 

So we're effectively searching 7571E8 xxxxxxxx 488B05 xxxxxxxx 488905 xxxxxxxx ... where xxxxxxxx is a wildcard! Every hex combination found in place of xxxxxxxx of its exact bytes lenght is good.

 

Looking in the clover debug log we can find some precious info about the patches:

 


55:733  0:010  OC: Kernel patch [9] algrey - cpuid_set_info - jmp to calculations and set cpuid_cores_per_package - 10.15/10.16
55:749  0:015  Replace 7571E800000000488B050000000048890500000000/FFFFFF00000000FFFFFF00000000FFFFFF00000000(7571E8B6090000488B0577EAED0048890538EBED00) by 744E000000000090890D00000000E97E0000006690/FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF(744EE8B609000090890D77EAED00E97E0000006690) at ofs:1273

 

The effective code found in the kernel is:

7571E8B6090000488B0577EAED0048890538EBED00


7571E8 B6090000 488B05 77EAED00 488905 38EBED00

7571E8 xxxxxxxx 488B05 xxxxxxxx 488905 xxxxxxxx

 

...a nice match with our find mask!!!

 

So now in the replace side we do the same calculations:


744E000000000090890D00000000E97E0000006690
FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF

744E 0000000000 90890D 00000000 E97E0000006690
FFFF 0000000000 FFFFFF 00000000 FFFFFFFFFFFFFF
----------------------------------------------
744E xxxxxxxxxx 90890D xxxxxxxx E97E0000006690

 

We must replace in the actual found hex bytes "string" found with the previous calculations, 7571E8B6090000488B0577EAED0048890538EBED00:

 


7571E8B6090000488B0577EAED0048890538EBED00 (actual found code)

744E000000000090890D00000000E97E0000006690 (replace data)
FFFF0000000000FFFFFF00000000FFFFFFFFFFFFFF (replace mask)

744E 0000000000 90890D 00000000 E97E0000006690 (replace data)
FFFF 0000000000 FFFFFF 00000000 FFFFFFFFFFFFFF (replace mask)

7571 E8B6090000 488B05 77EAED00 48890538EBED00
744E xxxxxxxxxx 90890D xxxxxxxx E97E0000006690
----------------------------------------------
744E E8B6090000 90890D 77EAED00 E97E0000006690

 

The xxxxxxxx hex "string" must be left exactly as it has been found, the other bytes must be replaced... so the final actual replace is 744EE8B609000090890D77EAED00E97E0000006690.

 

Offsets are calculated and applied automatically by OC / Clover kernel patch engine.

 

 

 

tomnic ,

 

Thanks, the is a very clear explanation of the mask function.

 

***

 

Now, leading from this is the 'what'? That is, what exactly are we replacing? Does the substituted data make this section inoperable, avoiding the panic and boot failure?

 

The other questions, stemming from the initial link by fabiosun, that is still unclear to me is: how is this location even chosen? Is it found by looking at the error log during a failed boot?

 

In the link fabiosun provided there is a discussion about XCPM and locating ”_xcpm_init:". (I don't what to talk about XCPM in detail but use this as a model of how we're to approach the topic of patch creation.) What wasn't clear in that link was what lead to looking at ”_xcpm_init:" in particular? Again, was there an error code from a log that pointed out this particular section as causing the panic?

 

Next, there is an extraction from the kernel:  "xxd -s 0x2283c0 -l 28 -psu /S*/L*/Kernels/kernel”. I'm assuming that without a drive reference, this is doing an extraction of the boot kernel. However, during patch creation, by definition, we are not working on a boot kernel since it won't boot. So we need to reference a drive with the problematic macOS already installed, or can this be extracted from the macOS Installer app?

 

The other confusing issue was the chosen offset of 0x20000. Why this value? (Maybe Piker discussed it elsewhere and it was common knowledge at the time, but I don't understand why this value was chosen.)

 

 

To summarize:

 

how are the patch locations located, via error logs?

how is the value to be substituted determined (the replacement value)?

which file is examined for this section, an already installed drive with the new macOS, or the installation app for this new macOS?

 

***

 

One target for how this is worked out may be the new code needed for Big Sur 11.4.0. There was a change in the "DhinakG - cpuid_set_cpufamily - force CPUFAMILY_INTEL_PENRYN - 11.3b1" after 11.3.0 Maybe this is a good place to start to see how this all is done? (And it seems that Monterey 12.0 broke this and needs a different, but similar value to 11.4.)

 

 

Link to post
Share on other sites
  • Supervisor
11 hours ago, iGPU said:

The other questions, stemming from the initial link by fabiosun, that is still unclear to me is: how is this location even chosen? Is it found by looking at the error log during a failed boot?

 

For that particular example pikeralpha showed the way to find location and what to find inside (callq tab and relative value)

 

11 hours ago, iGPU said:

In the link fabiosun provided there is a discussion about XCPM and locating ”_xcpm_init:". (I don't what to talk about XCPM in detail but use this as a model of how we're to approach the topic of patch creation.) What wasn't clear in that link was what lead to looking at ”_xcpm_init:" in particular? Again, was there an error code from a log that pointed out this particular section as causing the panic?

 

this was related to an instant reboot after ++++++ stage (in that time Clover was the main bootloader)

Pikeralpha showed "how to" then clover devs integrated this patch in an automatic way..and many users (me too) asked to have a flag because it caused some problem in their application way

 

_xcpm init is the first search to do in that case in disassembled kernel

Obscure for me was why to change value in C3 and the offset value  to subtract

from many OS Offset was the same to substract...but unkown how to find new one if it happens

 

OT (a GoldFish64 patch for Monterey b1 has only a replace with C3 value)

 

11 hours ago, iGPU said:

Next, there is an extraction from the kernel:  "xxd -s 0x2283c0 -l 28 -psu /S*/L*/Kernels/kernel”. I'm assuming that without a drive reference, this is doing an extraction of the boot kernel. However, during patch creation, by definition, we are not working on a boot kernel since it won't boot. So we need to reference a drive with the problematic macOS already installed, or can this be extracted from the macOS Installer app?

you must have (if systems does not start) a copy of the kernel you have to disassemble with Otool command

Link to post
Share on other sites
12 hours ago, iGPU said:

 

To summarize:

 

how are the patch locations located, via error logs?

how is the value to be substituted determined (the replacement value)?

which file is examined for this section, an already installed drive with the new macOS, or the installation app for this new macOS?

 

***

 

One target for how this is worked out may be the new code needed for Big Sur 11.4.0. There was a change in the "DhinakG - cpuid_set_cpufamily - force CPUFAMILY_INTEL_PENRYN - 11.3b1" after 11.3.0 Maybe this is a good place to start to see how this all is done? (And it seems that Monterey 12.0 broke this and needs a different, but similar value to 11.4.)

 

 

 

How are the patch location located, via error logs?

 

I think that this is fully automated by the patch engine of the chosen bootloader, the found offset is shown in the debug log. If you use a hex editor and go to that offset you'll find exactly the found pattern. Replaced values are only loaded in memory not actually written to the kernel.

P.s.: IMHO the offset piker uses, 0x200000, is related to its specific xcpm patch. Every patch has a different one, as you can see analyzing any debug log in the patch applying section.

 

 

How is the value to be substituted determined (the replacement value)?

 

This involves both assembly and xnu knowledge: the pattern we choose to replace some code is actually machine language code created by anybody who deeply knows xnu... the goal is to bypass or modify specific functions not suitable for AMD cpus. If you physically apply the patch and disassemble the kernel you'll get perfectly working assembly code, different from the source, but "perfect" to be directly run by AMD cpus.

P.s.: IMHO the right procedure would be: analyze the full source xnu kernel once published by Apple here https://opensource.apple.com/source/xnu/, change the functions / values not suitable for AMD cpus in c++ language, compile them and create a find / replace with masks to maintain durability and portability of the binary patches to obtain the same modded code without replacing the actual kernel.

 

 

 

Which file is examined for this section, an already installed drive with the new macOS, or the installation app for this new macOS?

 

You can take the kernel from the installation app or an official apple update package, or even from a real booted mac or intel hackintosh, installation kernel and booted kernel usually are the same version.

Link to post
Share on other sites
  • 2 weeks later...
  • 2 weeks later...

A Reverse-Reverse-Engineering Patch Tutorial

 

For those who already know what I'm posting here and consider this simplistic: sorry. I'm presenting this information for those, who like myself, may be unaware of how patches are created. Virtually every time I've read about a new patch, the patch is presented only by itself or only with the smallest of hints as to how it was created.

 

Recently, I've spent hours trying to get the SmallTree I211 kext to work under Monterey. I've not yet succeeded, but the attempt led me to think a patch might be necessary. SInce I didn't know how to make a patch, I thought a good way to learn might be to "reverse engineer" an existing patch.

 

That thought led me to study patches for the Aquantia ethernet port. There is a long thread over at InsanelyMac (here) where Aquantia patches are discussed for various macOS releases. These patches seemed to have become necessary with the release of Catalina and Big Sur (Mieze and Shikumo seem to have done most of the work). Recently, a new patch for Aquantia was found to be necessary for Monterey; it was discussed in this post by Mieze (from whom I've gleaned the most about kext patches). This means there are several examples to test-out the reverse engineering process. (BTW, what's described below was also tried out on the Big Sur Aquantia patch and this process works with that patch too.)

 

 

1. Initial Attempt - OTOOL/HEX FIEND

 

My first attempts to reverse engineer the Aquantia patch began by using the tools discussed in the first post on this thread (from Piker Alpha, using otool (part of macOS): extracting the contents of the Aquantia kext file into a text file), along with using Hex Fiend (a download). The Aquantia kext file is located inside the IONetworkingFamily kext as a plugin (you have to dig a bit to find it!): IONetworkingFamily/Contents/Plugins/AppleEthernetAquantiaAqtion/Contents/MacOS/AppleEthernetAquantiaAqtion.

 

[Note that all editing and work below was carried out on a copy of the kext file that was transferred from "S/L/E/IONetworkingFamily" to the desktop, so nothing destructive was done to the original file.]

 

The text file can be created with otool with the following command (drag and drop your file  location in place of the  "/~/~/" section):

otool -tV /~/~/AppleEthernetAquantiaAqtion > ~/Documents/Aquantia.txt

 

And this same AppleEthernetAquantiaAqtion file can be examined with Hex Fiend. The combination of otool's resulting text file (upper left) and Hex Fiend (upper right) are shown below, where they're also compared with the link from Mieze (bottom left):

 

1426999897_M3-Aquantia-41c7450000000000e9.thumb.png.1764ffb8a372ebd9e79550e5c56d68a0.png

 

Unfortunately, I couldn't figure out how to derive the patch from this data.

 

 

2. Successful Attempt - IDA64

 

It wasn't until I did some searching and came across a tool from the Belgium company, Hex-Rays. They offer a free download (here) of the IDA64 macOS app to try out and that is what I'll present next. The idea behind using this app is to de-compile the kext file. But what I'm actually trying to do is "reverse engineer" Mieze's "reverse  engineering" of the de-compiled kext file. If successful, then we can learn how to better make patches.

 

We can start this process from some knowledge shared on that thread: we know the area in the kext to examine ("checkConfigSupport", shown in above image) and the given Find string ("41 C7 45 00 00 00 00 00 E9"). Between these two items, we can zero-in on the area that needs to be patched.

 

The first step is in opening the file (again, the nested AppleEthernetAquantiaAqtion file contained within IONetworkingFamily from the latest Monterey ß3) with the IDA64 app. When opening, the default settings in IDA64 are best (shown in Spoiler below).

 

Spoiler

ida64.thumb.jpg.eb8dfc3cc1b7d7c77f99541d032dc37c.jpg

 

Once opened, the initial screen can have the windows rearranged (initial view shown in Spoiler below).

 

Spoiler

ida64-initalview.thumb.jpg.2ee0c7162ec95870a0266db287758e6c.jpg

 

I prefer the Function View be expanded on the left as shown below (also shown is the search window which is accessed from the menu bar). This particular search item was chosen based on discussions in the InsanelyMac thread, where Mieze describes (in Spoiler):

 

Spoiler

"In earlier versions the driver already checked certain hash values in order to identify Apple devices but with 10.13.4 they added a new function called checkConfigSupport() where they perform these checks and they have added more hash values to check helping to distinguish original Apple NICs from flashed 3rd party device and in case checkConfigSupport() returns 0, the driver will refuse the device. I haven't found the time to reverse engineer checkConfigSupport() in detail [ed. Mieze later did this in thread,] but I patched the "je" instruction which causes the driver to fail in case of the return value 0. Now it works."

 

At the end of this post, Mieze also adds:

 

"In order to enable the driver's debug output, you might want to add "apple-axge-debug=0xff" to your kernel flags."

 

 

ExpandedFxtn-search.thumb.jpg.abfbcda43feb8a4239776c47493a4aee.jpg

 

When this search is completed, and the Function View item on the left double-clicked, the following can be seen in the right pane. But the result is not obvious as the presentation is a bit complicated: the right pane must be scrolled (shown as separate images in the Spoiler below).

Spoiler

initial view:

view1.jpg

 

 

scroll down and see this:

view2.thumb.jpg.6311e72a0bff3ef0cb0557b085bc93bf.jpg

 

scroll down some more:

view3.thumb.jpg.97e9d00966c5c8fdeec031f02c36a18c.jpg

 

and then final view we need to study:

view4.thumb.jpg.61dbd73feaa15be8d0e3a1638ba01c04.jpg

 

After scrolling the right pane, we can begin the process. On the left pane, note that the Aquantia item is "107" which references the AQC107 port name. Next, even after scrolling, there are  many windows in the right hand pane. Since we're reverse engineering, we already know that we're looking for something that should say "__ZN30AppleEthernetAquantiaAqtion10718checkConfigSupportERiS0".

 

This is highlighted below on the right. As pointed out in the InsanelyMac thread, we need to change the value of "0" to "1", but first we need to find the corresponding hex code. We  do this by clicking on the "mov" command (highlighted below right). Once the "mov" command is selected, the corresponding hex code will also be selected for us, but to see this, we need to change views.
 

Mß3-AQC107-locate-block.jpg

 

 

To see the hex code, change views by clicking on Hex View near to the top of the window. The new view switches the right pane from the IDA View above to the Hew View below. Again, since we clicked on the "mov" command in the above step, the correct hex code for this command is already highlighed in dark green.


We then look for the Find string which is "41 C7 45 00 00 00 00 00 E9", but notice that the "mov" command did not contain the "E9". The next byte seems to be chosen when patching to help ensure only one site is patched. The replacement will simply over write this byte with the same value.

 

1967456649_M3-AQC107-isolate-hex-code.thumb.jpg.26256c7267e8ab9046d6bfbbcc347e37.jpg

 

 

But to change the "0" to "1", we need know which byte to change. This can easily be tested in real time by editing the hex code as shown below: right-click on the byte to change.

 

1319854986_M3-AQC107-editchanges.thumb.jpg.b1e36105559318eb2e3e7f766c3d9b16.jpg

 

 

The cursor will be in the middle of the byte to change the right least significant value as shown next. To make this change 'stick',  we need to apply it. This is done by again right-clicking.

 

527271890_M3-AQC107-applingchanges.thumb.jpg.309ddf245c2f3fd66a69ce2af0d6bd35.jpg

 

 

This change can be verified by switching from the Hex View to the IDA View. If incorrect, switch back to the Hex View, re-edit as needed and re-apply and return to the IDA View once more to verify if now correct. If the correct byte was changed, you'll see the following:

 

1605660523_M3-AQC107-appliedchanges.thumb.jpg.ea7315490b6affe597db50a7aca942e6.jpg

 

 

At this point, we can see that the Find value of "41 C7 45 00 00 00 00 00 E9" and the replacement value of "41 C7 45 00 01 00 00 00 E9" now all make sense and this seems to be how this patch was derived.

 

If anyone has any comments or corrections to the above let me know and I'll edit this post.

 

[BTW, I compared the Monterey ß3 Aquantia file to that of Monterey ß1, using Hex Fiend: there is no difference. However, this is quite a difference in this same file between Monterey and the latest release of Big Sur.]

 

 

ADDENDUM - Future Approaches

 

Spoiler

Much of Mieze's work seems to be from studying Linux Aquantia driver's source code, so this is another avenue for us to study too (as of this writing, I have not). Mieze also suggests another patch approach in this post

 

"checkConfigSupport().... tries to retrieve the NIC's "built-in" property from IORegistry.

 

"...If the "built-in" property isn't found, the check of the global chip identification register is skipped and the NIC will be accepted without further tests. This opens up a new opportunity for a patch which may even survive driver updates. Replacing the string "built-in" with something else by a Clover patch also causes the driver to accept the chip and even the error message disappears (I can confirm that this works!)."

 

This "built-in" approach is very interesting. I saw this entry during the writing of this post and intend to revisit it in a few days.

 

 

 

Edited by iGPU
  • Like 1
  • Thanks 1
Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.