Attended

Attended was on of the hardest box I ever even touched. It helped me to improve my pwn skills a lot so it's 100% worth it.

Recon

There are only 2 ports opened on the host, 25 (smtp), and 22 (ssh)

Let's setup an smtp server and see if we get an answer from any user. I use FakeSMTP:

Nilhcem/FakeSMTPDummy SMTP server with GUI for testing emails in applications easily. - Nilhcem/FakeSMTPgithub.com

After striking my head against the wall a bit, I decided to mail the creators of the box :).

I first tried guly and got the following response:

Saying he's busy with freshness (the other co-creator of attended). At this point I thought that maybe mailing guly prettending to be freshness might result in a different response

, and as expected

After reading this, I tried sending and email with an attachment:

and we got a third response:

From all these emails we can extract a few infos:‌

  • Guly expects an attachment from freshness

  • He'll open it with vim

  • We have python2 on the system

  • There are outbound traffic restrictions

  • The attachment is a config file (likely ssh)

  • The freshness user will test the config file in /home/shared

So our attack chain would likely be the following:‌

send a malicious attachment to get code execution -> Bypass traffic restrictions via data exfiltration -> Create a malicious ssh_config file -> Finally login as a user and work our way from there‌

Of course that wasn't my fist guess, it was actually the result of many frustrated attempts.‌

Vim

Searching for vim CVEs this one seems the most promissing one:

https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md

So I tried sending an attachment that would make the host ping my box, which seems the most reasonable test since there are traffic restrictions on the target

And it works!‌

After playing around a bit with the exploit I tried to exfiltrate data via http using python2 (at this point I was praying for the request lib to available on the host).‌

Here is the first thing I sent:

And listened on port 80. This will retreive the base64 encoded output of the command, After decoding it we get:

​‌

The tmp folder inside guly's home seems interesting...

It contains a vim swap of a config file, this will possibly hint us on how our malicious config must look like.‌

This is not simply ascii text so use strings instead of cat.

This confirms my theory that we need to drop a config file at /home/shared and it will be used as a ssh_config file. We can execute commands whenever our config file is used by abusing some special configurations such as ProxyCommand.‌

If we create a /home/shared/config file with ProxyCommand to write a ssh key to authorized_keys we might be able to login as freshness.

This is the config file I used.‌

We can try to write it with the above attachment and then we wait for freshness to test it.

And finally this works and we can login with ssh.

So far so good. Pretty nice warm up, now let's get to the really crazy part...‌

Authkeys

There is a note.txt file at /home/freshness/authkeys:

And also an ELF executable file called authkeys.‌

The note suggests that there is another host in the network. Reading /etc/hosts confirms that:

The term "Authkeys" rings a bell, there is a configuration that can be set on sshd_config to process data received from private/public key authentication on ssh. Reading /etc/ssh/sshd_config we see the following lines:

Those are commented out but from the note we can assume that they aren't on attendedgw.‌

Attendedgw isn't listening on port 22, so the natural thing is to test 2222 instead, which confirms ssh is listening, but running a portscan is also an option.‌

Now let's try to understand the arguments passed to the authkeys program. sshd_config(5) - OpenBSD manual pagesman.openbsd.org

Reading the documentation we can easily understand what %f %h %t %k are:

Testing with the following parameters we can see that there is a buffer overflow on the %k parameter (The base64 encoded public key):

Opening the binary on gdb on a openbsd vm and setting a ton of base64 encoded A's as our %k argument we can overwrite the rsp register to whatever we want and start work on a rop chain:

Before that we must consider that we'll send a private key that will then be converted to a public key, so thinking of the crafting of the key before building an exploit will prevent us from doing some extra work to adapt our padding to the correct format.‌

SSH-RSA

RSA (cryptosystem) - Wikipediaen.wikipedia.org

We know how RSA keys are built, now we have to think on how to make a private key that will produce a public key containing our payload. A RSA key is composed by p, q, n and e where p and q are any two prime numbers, n is the product of those two and e is an integer such that 1 < e < phi and gcd(e, phi) = 1; that is, e and phi are coprime.‌

So the value we have most control over and is present in both the private and the public key is e.‌

Let's make that e = our payload, p = a very large prime, q = another very large prime, n = p*q, phi = (p-1)*(q-1), this is the famous Euler totient function. To generate a private key we also need to find d, which is the modular multiplicative inverse of e modulo phi, and the crt_coef, which is the modular multiplicative inverse of p modulo q.‌

The last thing we must care about is that our payload (e) needs to be coprime to phi, so we can add a few bytes at the end and only modify those, so our payload will keep the same. I'll be using sagemath, which basically let's you run python with crazy alegebra functions.

SageMath Mathematical Software System - SageSageMath is a free and open-source mathematical software system.www.sagemath.org

Now show me the code!

Here are our first definitions, now we need to use an algoritm to modifly the last few bytes of e until we hit a coprime to phi:

I'm using the gcd(phi, e) = 1 condition I mentioned before to confirm that e is good to go. The reason for my range to be 1, 2e16 is that 2e16 is the biggest possible number that would perfectly fit the bytes we freed by adding 8 null bytes to the end of our payload.‌

Now we want to calculate our inverses.

And we can finally build a key!

Now let's extract our public key and test it on gdb to see if our overflow works as expected.

I first tried 850 A's as padding and we can see that we wrote 81 bytes after rsp, so now we just need to reajust that to be 761 A's + 8 B's to see if wee can perfectly overwrite rsp.

It works, the rsp value is overwritten an on the first return after the overflow it changes rip to bbbbbbbb which means we can start building our rop chain.‌

ROP

Using ropper to search for useful gadgets we can see that we can probably make a syscall. Also keep in mind that the user data is stored in the data section before being written onto the stack, which is good since there is no PIE, so we can store arguments we might want to use there, making ROP easier.

Our challenge now is to control rax, rdi, rsi and rdx. rax will be the syscall number; we want it to be 59, which is execve() on openbsd; rdi needs to be "/bin/sh", that string is obviously not available on the program but we can put it on our padding; rsi needs to be a pointer to an array of arguments and finally rdx is an optional argument that we want to be null.‌

RAX

Looking for gadgets to control rax anything that helps manipulating rax, eax, ax or al is useful since those are all the same register but each one allows controling a different amount of bytes.

x64 Architecture - Windows driversx64 Architecturedocs.microsoft.com

Two gadget can be chained to change rax to whatever we want‌

000000000400370: shr eax, 1; ret;

and‌

0x000000000040036d: not al; adc cl, 0xe8; ret;

At the moment of the crash rax is set to 0x0, shr (shift right) moves all bytes to the right and not swaps 0's and 1's.‌

With all we can control 8 bits so:‌

00000000 --> not --> 11111111

And for each shift we move everything to the right so:‌

1111111 --> shr --> 0111111

If we want to go from 0 to 59 we need the following operations:‌

00000000 --> not --> 11111111 1111111 --> shr --> 0111111 0111111 --> shr --> 0011111 0011111 --> not --> 1100000 1100000 --> shr --> 01100000 01100000 --> not --> 10011111 10011111 --> shr --> 01001111 01001111 --> shr --> 00100111 00100111 --> shr --> 00010011 00010011 --> not --> 11101100 11101100 --> shr --> 01110110 01110110 --> shr --> 00111011

Now let's start developing an exploit to change rax to 59:

The above code creates our rop chain, if we feed it to the key generator and test our exploit we successfully changed rax to 59:

now we can move on to the next registers.‌

RDI/RSI/RDX

There is also a chain of gadgets that allow us to control rsi and, as we'll see later on, also control rdi and rdx.‌

0x000000000040036a: pop rdx; ret;

Let's us easily change the value of rdx.‌

000000000040037b: movss xmm0, dword ptr [rdx];

Copies a dword pointer from rdx to xmm0.‌

0x0000000000400380: cvtss2si esi, xmm0; ret;

Converts a float from xmm0 and stores the result on esi.‌

And finally:‌

0x0000000000400367: mov rdi, rsi; pop rdx; ret;

Copies rsi to rdi‌

cvtss2si expects a float value and movss expects a dword pointer. We want rdi to be "/bin/sh", so we store that value in memory by modifying our padding. Then we need to store a pointer to that address as a float value because it will be converted back afterwards. Finally we just pop rdi with that value's address‌

Let's take it slow and start by checking where "/bin/sh" is written to:

First we consult the vaddr of a memory section out payload overwrote:

And inspect that address (and the next few ones) in gdb:

This reveals we are one byte off, we can try to fix this by adding a sigle byte before our string.

And now our string is perfectly written to 0x6010d0. Now we need to create a convert 0x6010d0 to a floar value and also store it in memory.‌

One simple way of doing this is using: Hexadecimal to Decimal ConverterHexadecimal to decimal converter helps you to calculate decimal value from a hex number up to 16 characters length, and hex to dec conversion table.www.binaryhexconverter.com

And Floating Point to Hex Convertergregstoll.com

We first convert our address and convert it to a integer and feed it to the float to hex converter.

So the float value in hex will be 0x4ac021a0, which we'll store in the memory.

Now we should also see our float value in the data section.

Now we just have to pop rdx with 0x6010d0 and throw in the gadgets that were mentioned before:

At this point we can start writing our array of arguments to the data section. argv[0] is the name of the program, so also "/bin/sh", argv[1] will be "-c", and argv[2] can be any command we want. Our command can be, for example:

With the following code:

We can see our arguments in the data section:

Now all we need to do is assign pointers to each of those arguments in an array.‌

With the following code we can write our array of pointers to each of our arguments:

And here is how the addresses looks the array in the data section:

Now we need to make rsi point to the first adddress of the array so we do the same processes we did to rdi using cvtss2si and movss to make write 0x601170 to rsi.

You know the drill, write the float to memory, then pop rdx with the address of that value and use our special gadgets to convert it back to hex and store it in rsi. Ps: I had to reset the rip before that because the rdx value was being writen to rip.‌ That asa easy to solve, since I was already writing to rip i just had to write the correct address back to it to reajust the execution flow.

Syscall

With the following code we have all of our registers on their marks and can finally call a syscall. If we use it to authenticate to root@attendedgw our exploit works!:

We can generate the payload.‌

And login to attendedgw.

Last updated

Was this helpful?