The clue for this one is again brief,
accesscontrol
It's all about who you know and what you want. access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me:17069
With the clue is a binary file which you can find archived here.
First up I just connected to the server with netcat to see what it looked like:
root@mankrik:~/defcon/access# nc access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me 17069 connection ID: ZP]j[OD6|t9IMa *** Welcome to the ACME data retrieval service *** what version is your client?
Ok neat, no idea what it wants yet but check out that "Connection ID". I bet that's useful later right?
Let's examine the binary:
root@mankrik:~/defcon/access# file client_197010ce28dffd35bf00ffc56e3aeb9f client_197010ce28dffd35bf00ffc56e3aeb9f: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xcf260fd5e12b4ccf789d77ac706a049d83df4f05, stripped root@mankrik:~/defcon/access# strings client_197010ce28dffd35bf00ffc56e3aeb9f /lib/ld-linux.so.2 __gmon_start__ libc.so.6 _IO_stdin_used socket exit htons sprintf perror connect strncpy puts __stack_chk_fail stdin fgets send strstr recv inet_addr __libc_start_main GLIBC_2.4 GLIBC_2.0 PTRhP D$tf [^_] VUUU UWVS [^_] need IP Could not create socket Socket created connect failed. Error Enter message : hack the world nope...%s what version is your client? version 3.11.54 hello...who is this? grumpy grumpy enter user password hello %s, what would you like to do? list users deadwood print key the key is: challenge: answer? recv failed << %s connection ID: connection ID: challenge: Send failed ;*2$",
Standard stuff. There's a version string "version 3.11.54" so that's needed to answer the question to the server I bet.
Let's try running the client...
root@mankrik:~/defcon/access# ./client_197010ce28dffd35bf00ffc56e3aeb9f need IP root@mankrik:~/defcon/access# host access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me has address 54.84.39.118 root@mankrik:~/defcon/access# ./client_197010ce28dffd35bf00ffc56e3aeb9f 54.84.39.118 Socket created Enter message : go? nope...go?
Ok time for more reversing... In IDA we find this tidbit quickly:
printf("Enter message : "); fgets(byte_804B080, 1000, stdin); v6 = 16; v7 = byte_804B080; v8 = "hack the world\n"; do { if ( !v6 ) break; v4 = *v7 < *v8; v5 = *v7++ == *v8++; --v6; } while ( v5 ); if ( (!v4 && !v5) != v4 ) { printf("nope...%s\n", byte_804B080); result = -1; goto LABEL_54; }
So the "message" is probably "hack the world"? Back to the client:
root@mankrik:~/defcon/access# ./client_197010ce28dffd35bf00ffc56e3aeb9f 54.84.39.118
Socket created
Enter message : hack the world
<< connection ID: m:to/#$w'x6DYy
*** Welcome to the ACME data retrieval service ***
what version is your client?
<< hello...who is this?
<<
<< enter user password
<< hello grumpy, what would you like to do?
<< grumpy
<<
mrvito
gynophage
selir
jymbolia
sirgoon
duchess
deadwood
hello grumpy, what would you like to do?
<< the key is not accessible from this account. your administrator has been notified.
<<
hello grumpy, what would you like to do?
hello
^C
Alrighty, thats cool. We get logged in, we see a user list, and we see theres some kind of key although we don't have privs to get the key yet. The client is not interactive either, so we can't try more stuff. It's looking like we need to build our own client... So we need to reverse how this one works so we can do that.
Let's look at the network layer to watch what it sends:
Ok all normal, we got:
- the version number string (as expected)
- the username
- the two commands "list users" and "print key".
- What's up with the password though. Is that the user password?
I capture a few connections and the response to the password prompt changes each time. So this is somehow ciphered. So if we're going to write a client, we need to encrypt the password correctly...
Back to IDA! We find this code naming two functions "sub_8048EAB" and "sub_8048F67":
sub_8048CFA("enter user password"); if ( sub_8048CFA("enter user password") ) { v28 = 0; v29 = 0; sub_8048EAB("grumpy", &v28); sub_8048F67(&v28); HIBYTE(v29) = 0; sprintf(&v28, "%s\n", &v28); sub_8048E3E(&v28); }
sub_8048EAB XORs the first 5 bytes of the given plaintext password with a key. The key is based on the connection ID (dword_804B04C) and an offset into the connection ID string is chosen based on (dword_804BC80 % 3):
char *__cdecl sub_8048EAB(int a1, int a2) { char *result; // eax@1 int v3; // ebx@4 signed int i; // [sp+2Ch] [bp-1Ch]@1 char dest[5]; // [sp+37h] [bp-11h]@1 int v6; // [sp+3Ch] [bp-Ch]@1 v6 = *MK_FP(__GS__, 20); result = strncpy(dest, &::dest[dword_804B04C] + dword_804BC80 % 3, 5u); for ( i = 0; i <= 4; ++i ) { result = (a2 + i); *(a2 + i) = dest[i] ^ *(a1 + i); } v3 = *MK_FP(__GS__, 20) ^ v6; return result; }
So what do we know, in plain language:
- The ciphered password string really can only be 5 characters long
- The key (connection ID) is public and given to us at the start of the connection
- The offset of the connection ID where the key begins is possible to calculate by deriving how dword_804BC80 is generated then computing it modulo 3.
The first two are easy, the third item requires more reversing. When you think about it, what is the point of reversing more here? So we know exactly the offset for the key? Does it matter? Let's try something in the server with netcat:
root@mankrik:~/defcon/access# nc access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me 17069 connection ID: )Y<AH{"5mgnZU8 *** Welcome to the ACME data retrieval service *** what version is your client? version 3.11.54 hello...who is this? grumpy enter user password nottherightpassword wrong password, fat fingers hello...who is this? grumpy enter user password grumpy wrong password, fat fingers hello...who is this?
So the server, for 1 connection ID gives us multiple attempts at the password. So since the correct offset modulo 3 can only ever be 0,1,2,3 we have only a maximum of 4 password attempts before we will get the right password.
Long story short, I didn't bother reversing that bit and moved on to the second function that operates on the password before sending it:
This function is simpler, it just parses the output of the first function looking for characters <= 31 and if found, adds 32 to them. If the character == 127 it does other things.
int __cdecl sub_8048F67(int a1) { int result; // eax@4 signed int i; // [sp+Ch] [bp-4h]@1 for ( i = 0; i <= 4; ++i ) { if ( *(a1 + i) <= 31 ) *(a1 + i) += 32; result = *(a1 + i); if ( result == 127 ) { *(a1 + i) -= 126; result = a1 + i; *(a1 + i) += 32; } } return result;
Again I got lazy here, I don't care about the edge case single character == 127 so I completely ignored implementing that!
For both of these "optimizations" I made up front, I paid a price though. We'll see that later :)
So now I have the makings of a client that can login, let's try it:
root@mankrik:~/defcon/access# ./m1.py [+] Opening connection to access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me on port 17069: Done [+] Connection ID: /Sn]9;INS1Y~*P [+] Sending username: grumpy [+] Sending password attempt 0... [+] Retrying... [+] Sending username: grumpy [+] Sending password attempt 1... [+] Login success with offset 1 [*] Switching to interactive mode $ list users grumpy mrvito gynophage selir jymbolia sirgoon duchess deadwood hello grumpy, what would you like to do? $
Cool it works! And now I'm interactive I can try more commands. Except there aren't any! Poop. Well let's try these other users eh? Hope they all picked silly passwords like Mr Grumpy did.
root@mankrik:~/defcon/access# ./m1.py [+] Opening connection to access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me on port 17069: Done [+] Connection ID: vB_Ydfnq}/m|"L [+] Sending username: sirgoon [+] Sending password attempt 0... [+] Retrying... [+] Sending username: sirgoon [+] Sending password attempt 1... [+] Retrying... [+] Sending username: sirgoon [+] Sending password attempt 2... [+] Retrying... [+] Sending username: sirgoon [+] Sending password attempt 3... [+] Login success with offset 3 [*] Switching to interactive mode $ print key the key is not accessible from this account. your administrator has been notified. hello sirgoon, what would you like to do?
Ok fine. We go on this way for a while. Finally we get to the user "duchess":
[+] Opening connection to access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me on port 17069: Done [+] Connection ID: Xo/Wug;}erP'PQ [+] Sending username: duchess [+] Sending password attempt 0... [+] Retrying... [+] Sending username: duchess [+] Sending password attempt 1... [+] Retrying... [+] Sending username: duchess [+] Sending password attempt 2... [+] Retrying... [+] Sending username: duchess [+] Sending password attempt 3... [+] Login success with offset 3 [*] Switching to interactive mode $ print key challenge: _(&:5 answer? $ nope you are not worthy hello...who is this?
Ok neat. The user duchess can get the key, but first they must answer the challenge!
The challenge looks to be a 5 byte string, encrypted in the same way our password was when it was sent over the wire.
If we get the answer wrong we get dropped, but we don't lose our connection. We just have to relogin. That's pretty important because we keep our connection ID.
So this is the code I came up with as a "test" client to solve the challenge. I didn't continue coding past this point because my first attempt got me the flag:
#!/usr/bin/python from pwn import * HOST='access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me' PORT=17069 username = 'duchess' password = username[:5] version = 'version 3.11.54' def cipherpw(connectionid, password, offset): connectionid = connectionid[offset:] pl = list(password) cl = list(connectionid) result = "" for p in range(len(pl)): result += chr(ord(pl[p]) ^ ord(cl[p])) a1 = list(result) r2 = "" for i in range(5): if ord(a1[i]) <= 31: a1[i] = chr(ord(a1[i]) + 32) r2 += a1[i] return r2 conn = remote(HOST,PORT) connectionid = conn.recvline().split(" ")[2] print "[+] Connection ID: " + connectionid, conn.recvlines(4) # banner conn.sendline(version) o = 0 while True: conn.recvline() # login prompt print "[+] Sending username: " + username conn.sendline(username) conn.recvline() # password prompt print "[+] Sending password attempt " + str(o) + "..." conn.sendline(cipherpw(connectionid,password,o)) result = conn.recvline() if 'what would you like to do' in result: print "[+] Login success with offset " + str(o) break else: print "[+] Retrying..." o += 1 conn.sendline('print key') challenge = conn.recvline().split(" ")[1] print "[+] Challenge received: " + challenge conn.recvline() # answer prompt for i in range(8): answer = cipherpw(connectionid, challenge, i) print "[+] Possible answer: " + answer print "[+] Challenge response: " + answer conn.sendline(answer) print "[+] Result: " + conn.recvline() conn.close()
Here's what it looks like when run.
[+] Opening connection to access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me on port 17069: Done
[+] Connection ID: J2*mO()uQAMp7d
[+] Sending username: duchess
[+] Sending password attempt 0...
[+] Retrying...
[+] Sending username: duchess
[+] Sending password attempt 1...
[+] Login success with offset 1
[+] Challenge received: +]J=4
[+] Possible answer: ao`P{
[+] Possible answer: 9w'r<
[+] Possible answer: !0%5=
[+] Possible answer: F2b4A
[+] Possible answer: ducHe
[+] Possible answer: #t?lu
[+] Possible answer: "(;|y
[+] Possible answer: ^,+pD
[+] Challenge response: ^,+pD
[+] Result: the key is: The only easy day was yesterday. 44564
[*] Closed connection to access_control_server_f380fcad6e9b2cdb3c73c651824222dc.quals.shallweplayaga.me port 17069
To be fair, this client is not complete. The logic which selects the real correct answer is not in place. It was only by luck that my solution finds the correct answer some times. I have rerun the exploit about 10 times now and it solved it correctly 3 times out of those 10 attempts.
Since we're not about perfect execution, just about CTF, we leave it open ended but happy we solved it in time.
Great, Thanks !
ReplyDeleteI visited your blog for the first time and just been your fan and get many informative information about the def con 24.
ReplyDeleteDEF CON 24 - Chris Rock
This blog have a lot of knowledge.we will take necessary information from this article.
ReplyDeleteHow to Overthrow a Government
file access control
ReplyDeleteAdd file access control and file IOs monitor to your windows application with Windows file system mini filter driver component in C#, C++ demo source code to implement your file security solution
http://easefilter.com/