GreHack-2013/150-1337
You are here | 150 - 1337
|
Description
GreHack CTF 2013 reverse engineering challenge (150 points).
Running the binary
Patching
The binary seems to be an ELF but with an incorrect format, as shown below:
$ file 1337 1337: ERROR: ELF 32-bit LSB executable, Intel 80386, error reading (Invalid argument)
Having a look at the header confirms that something's wrong:
$ hd 1337 | head 00000000 7f 45 4c 46 01 01 01 13 37 13 37 13 37 13 37 00 |.ELF....7.7.7.7.| 00000010 02 00 03 00 01 13 37 00 90 84 04 08 34 13 37 00 |......7.....4.7.| 00000020 d0 0b 13 37 13 37 13 37 34 00 20 00 08 00 28 00 |...7.7.74. ...(.| 00000030 1d 00 1c 00 06 13 37 00 34 13 37 00 34 80 04 08 |......7.4.7.4...| 00000040 34 80 04 08 00 01 13 37 00 01 13 37 05 13 37 00 |4......7...7..7.| 00000050 04 13 37 00 03 13 37 00 34 01 13 37 34 81 04 08 |..7...7.4..74...| 00000060 34 81 04 08 13 13 37 00 13 13 37 00 04 13 37 00 |4.....7...7...7.| 00000070 01 13 37 00 01 13 37 13 37 13 37 13 37 80 04 08 |..7...7.7.7.7...| 00000080 00 80 04 08 68 09 13 37 68 09 13 37 05 13 37 13 |....h..7h..7..7.| 00000090 37 10 13 37 01 13 37 00 68 09 13 37 68 99 04 08 |7..7..7.h..7h...|
We notice the presence of many \x13\x37 instead of \x00\x00. Let's fix it:
$ cp 1337 1337-patched $ sed -i 's/\x13\x37/\x00\x00/g' 1337-patched
Now, it seems that the format is correct:
$ file 1337-patched 1337-patched: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.26, BuildID[sha1]=c7ce472025fcc2fda6ed2ced6f7851e05e9c981e, stripped
First run
Let's run it:
$ ./1337-patched Ready to become 1337? yes fail
The executable returns "fail".
Analysis
Let's analyze it in IDA-Pro:
There are several tests, leading to different error messages:
- Sum test: will display "fail" in case of invalid input
- 1337 test: will display "f41l" in case of invalid input
- Double XOR test: will display "0xPH41L" in case of invalid input
- Bit equality test: will also display "0xPH41L" in case of invalid input
Initialization
The program is prompting a key and saves it to [ESP+0x18]. It also computes the length of the user input at offset 0x80485D2 and saves it to [ESP+0x10].
.text:0804857C sub_804857C proc near
.text:0804857C push ebp
.text:0804857D mov ebp, esp
.text:0804857F and esp, 0FFFFFFF0h
.text:08048582 sub esp, 20h
.text:08048585 mov dword ptr [esp], 539h ; size
.text:0804858C call _malloc
.text:08048591 mov [esp+14h], eax ; ESP+0x14 = mypass
.text:08048595 mov dword ptr [esp+18h], 0 ; ESP+0x18 = 0
.text:0804859D mov dword ptr [esp], offset s ; "Ready to become 1337?"
.text:080485A4 call _puts
.text:080485A9 mov eax, ds:stdin
.text:080485AE mov [esp+8], eax ; stream
.text:080485B2 mov dword ptr [esp+4], 539h ; n
.text:080485BA mov eax, [esp+14h]
.text:080485BE mov [esp], eax ; s
.text:080485C1 call _fgets
.text:080485C6 mov eax, [esp+14h]
.text:080485CA mov [esp], eax ; s
.text:080485CD call _strlen
.text:080485D2 mov [esp+10h], eax ; ESP+0x10 = strlen(mypass)
.text:080485D6 mov dword ptr [esp+1Ch], 0 ; ESP+0x1C = 0
.text:080485DE jmp short loc_80485F9
Sum test
Starting from offset 0x80485E0, there is a first check on the sum of all characters of the user input:
.text:080485E0 loc_80485E0:
.text:080485E0 mov edx, [esp+1Ch]
.text:080485E4 mov eax, [esp+14h]
.text:080485E8 add eax, edx
.text:080485EA movzx eax, byte ptr [eax]
.text:080485ED movsx eax, al
.text:080485F0 add [esp+18h], eax
.text:080485F4 add dword ptr [esp+1Ch], 1
.text:080485F9
.text:080485F9 loc_80485F9:
.text:080485F9 mov eax, [esp+1Ch]
.text:080485FD cmp eax, [esp+10h] ; loop until all chars of mypass are read
.text:08048601 jl short loc_80485E0
.text:08048603 cmp dword ptr [esp+18h], 539h ; Sum of all chars of mypass
.text:0804860B jz short loc_8048625 ; should be equal to 1337
.text:0804860D mov dword ptr [esp], offset format ; If not, display "fail"
.text:08048614 call _printf
.text:08048619 mov dword ptr [esp], 1 ; status
.text:08048620 call _exit
The program loops through all characters of the user input and cumulates the value of the characters. If the sum (including 0xA for the trailing \n) is equal to 1337.
1337 test
Starting from offset 0x8048625, the code is checking that bit #1 of the user input is equal to 1, bit #3 to 3 and bit #7 to 7:
.text:08048625 loc_8048625:
.text:08048625 mov eax, [esp+14h] ; eax = mypass
.text:08048629 add eax, 1
.text:0804862C movzx eax, byte ptr [eax]
.text:0804862F cmp al, 31h ; mypass[1] = '1'?
.text:08048631 jnz short f41l
.text:08048633 mov eax, [esp+14h]
.text:08048637 add eax, 3
.text:0804863A movzx eax, byte ptr [eax]
.text:0804863D cmp al, 33h ; mypass[3] = '3'?
.text:0804863F jnz short f41l
.text:08048641 mov eax, [esp+14h] ; same test as previously
.text:08048645 add eax, 3
.text:08048648 movzx eax, byte ptr [eax]
.text:0804864B cmp al, 33h
.text:0804864D jnz short f41l
.text:0804864F mov eax, [esp+14h]
.text:08048653 add eax, 7
.text:08048656 movzx eax, byte ptr [eax]
.text:08048659 cmp al, 37h ; mypass[7] = '7'?
.text:0804865B jz short loc_8048675
.text:0804865D
.text:0804865D f41l:
.text:0804865D mov dword ptr [esp], offset aF41l ; "f41l"
.text:08048664 call _printf
.text:08048669 mov dword ptr [esp], 3 ; status
.text:08048670 call _exit
Double XOR test
Starting from offset 0x8048675, there is a double XOR (1st XOR by 0x13 and 2nd one by 0x37) that modifies each of the characters of the user input:
.text:08048675 loc_8048675:
.text:08048675 mov dword ptr [esp+1Ch], 0 ; i = 0
.text:0804867D jmp short loc_80486BC
.text:0804867F ; ---------------------------------------------------------------------------
.text:0804867F
.text:0804867F loc_804867F:
.text:0804867F mov edx, [esp+1Ch]
.text:08048683 mov eax, [esp+14h]
.text:08048687 add edx, eax
.text:08048689 mov ecx, [esp+1Ch]
.text:0804868D mov eax, [esp+14h]
.text:08048691 add eax, ecx
.text:08048693 movzx eax, byte ptr [eax]
.text:08048696 xor eax, 13h ; mypass[i] ^= 0x13
.text:08048699 mov [edx], al
.text:0804869B mov edx, [esp+1Ch]
.text:0804869F mov eax, [esp+14h]
.text:080486A3 add edx, eax
.text:080486A5 mov ecx, [esp+1Ch]
.text:080486A9 mov eax, [esp+14h]
.text:080486AD add eax, ecx
.text:080486AF movzx eax, byte ptr [eax]
.text:080486B2 xor eax, 37h ; mypass[i] ^= 0x37
.text:080486B5 mov [edx], al
.text:080486B7 add dword ptr [esp+1Ch], 1 ; i += 1
.text:080486BC
.text:080486BC loc_80486BC:
.text:080486BC mov eax, [esp+1Ch]
.text:080486C0 cmp eax, [esp+10h] ; while (i < len(mypass))
.text:080486C4 jl short loc_804867F
.text:080486C6 mov eax, [esp+14h]
.text:080486CA movzx edx, byte ptr [eax] ; edx = xor_mypass[0]
.text:080486CD mov eax, [esp+14h]
.text:080486D1 add eax, 2
.text:080486D4 movzx eax, byte ptr [eax] ; eax = xor_mypass[2]
.text:080486D7 cmp dl, al ; \ if xor_mypass[0] != xor_mypass[2]
.text:080486D9 jnz PH41L ; / go to PH41L
The loop can actually be simplified with a simple XOR by 0x24. The code XORs each character of the user input with 0x24 and ensures that bit #0 and bit #2 of the resulting string are equal.
Bit equality test
Starting from offset 0x80486DF, there are tests on bits to ensure equalities:
.text:080486DF mov eax, [esp+14h]
.text:080486E3 add eax, 2
.text:080486E6 movzx edx, byte ptr [eax]
.text:080486E9 mov eax, [esp+14h]
.text:080486ED add eax, 4
.text:080486F0 movzx eax, byte ptr [eax]
.text:080486F3 cmp dl, al ; xor_mypass[2] = xor_mypass[4]
.text:080486F5 jnz PH41L
.text:080486FB mov eax, [esp+14h]
.text:080486FF add eax, 4
.text:08048702 movzx edx, byte ptr [eax]
.text:08048705 mov eax, [esp+14h]
.text:08048709 add eax, 5
.text:0804870C movzx eax, byte ptr [eax]
.text:0804870F cmp dl, al ; xor_mypass[4] = xor_mypass[5]
.text:08048711 jnz PH41L
.text:08048717 mov eax, [esp+14h]
.text:0804871B add eax, 5
.text:0804871E movzx edx, byte ptr [eax]
.text:08048721 mov eax, [esp+14h]
.text:08048725 add eax, 6
.text:08048728 movzx eax, byte ptr [eax]
.text:0804872B cmp dl, al ; xor_mypass[5] = xor_mypass[6]
.text:0804872D jnz PH41L
.text:08048733 mov eax, [esp+14h]
.text:08048737 add eax, 6
.text:0804873A movzx edx, byte ptr [eax]
.text:0804873D mov eax, [esp+14h]
.text:08048741 add eax, 8
.text:08048744 movzx eax, byte ptr [eax]
.text:08048747 cmp dl, al ; xor_mypass[6] = xor_mypass[8]
.text:08048749 jnz PH41L
.text:0804874F mov eax, [esp+14h]
.text:08048753 add eax, 8
.text:08048756 movzx edx, byte ptr [eax]
.text:08048759 mov eax, [esp+14h]
.text:0804875D add eax, 9
.text:08048760 movzx eax, byte ptr [eax]
.text:08048763 cmp dl, al ; xor_mypass[8] = xor_mypass[9]
.text:08048765 jnz short PH41L
.text:08048767 mov eax, [esp+14h]
.text:0804876B add eax, 9
.text:0804876E movzx edx, byte ptr [eax]
.text:08048771 mov eax, [esp+14h]
.text:08048775 add eax, 0Ah
.text:08048778 movzx eax, byte ptr [eax]
.text:0804877B cmp dl, al ; xor_mypass[9] = xor_mypass[10]
.text:0804877D jnz short PH41L
.text:0804877F mov eax, [esp+14h]
.text:08048783 add eax, 0Ah
.text:08048786 movzx edx, byte ptr [eax]
.text:08048789 mov eax, [esp+14h]
.text:0804878D add eax, 0Bh
.text:08048790 movzx eax, byte ptr [eax]
.text:08048793 cmp dl, al ; xor_mypass[10] = xor_mypass[11]
.text:08048795 jnz short PH41L
.text:08048797 mov eax, [esp+14h]
.text:0804879B add eax, 0Bh
.text:0804879E movzx edx, byte ptr [eax]
.text:080487A1 mov eax, [esp+14h]
.text:080487A5 add eax, 0Ch
.text:080487A8 movzx eax, byte ptr [eax]
.text:080487AB cmp dl, al ; xor_mypass[11] = xor_mypass[12]
.text:080487AD jnz short PH41L
.text:080487AF mov eax, [esp+14h]
.text:080487B3 add eax, 0Ch
.text:080487B6 movzx edx, byte ptr [eax]
.text:080487B9 mov eax, [esp+14h]
.text:080487BD add eax, 0Dh
.text:080487C0 movzx eax, byte ptr [eax]
.text:080487C3 cmp dl, al ; xor_mypass[12] = xor_mypass[13]
.text:080487C5 jnz short PH41L
.text:080487C7 mov eax, [esp+14h]
.text:080487CB add eax, 0Eh
.text:080487CE movzx eax, byte ptr [eax]
.text:080487D1 cmp al, 76h ; xor_mypass[14] = 0x76
.text:080487D3 jz short SUCCESS
.text:080487D5
.text:080487D5 PH41L:
.text:080487D5 mov dword ptr [esp], offset a0xph41l ; "0xPh41L"
.text:080487DC call _printf
.text:080487E1 mov dword ptr [esp], 7 ; status
.text:080487E8 call _exit
.text:080487ED ; ---------------------------------------------------------------------------
.text:080487ED
.text:080487ED SUCCESS:
.text:080487ED mov dword ptr [esp], offset aW3llD03Y0uR50L ; "W3LL d0|\\|3, y0u'r 50 L337 |\\|0W..."
.text:080487F4 call _printf
.text:080487F9 mov eax, [esp+14h]
.text:080487FD mov [esp], eax ; ptr
.text:08048800 call _free
.text:08048805 leave
.text:08048806 retn
.text:08048806 sub_804857C endp
It ensures the following conditions are met (xor_mypass designates the user input after the previous XOR transformation) :
xor_mypass[2] = xor_mypass[4] = xor_mypass[5] = xor_mypass[6] = xor_mypass[8] = xor_mypass[9] = xor_mypass[10] = xor_mypass[11] = xor_mypass[12] = xor_mypass[13]
xor_mypass[14] = 0x76
Sum up and solution
Sum up
Here is a sum up of the conditions:
Test | Condition |
---|---|
test 1 |
mypass[0] + mypass[1] + ... + mypass[n] + 0xA = 1337 |
test 2 |
mypass[1] = '1' (0x31) mypass[3] = '3' (0x33) mypass[7] = '7' (0x37) |
test 3 |
mypass[0] = mypass[2] |
test 4 |
xor_mypass[2] = xor_mypass[4] = xor_mypass[5] = xor_mypass[6] = xor_mypass[8] = xor_mypass[9] = xor_mypass[10] = xor_mypass[11] = xor_mypass[12] = xor_mypass[13] xor_mypass[14] = 0x76 |
Regarding the last test, we know that bit #14 should be equal to 0x76 after the double XOR transformation. As a consequence, it shoud be equal to 0x76 ^ 0x24 = 0x52 ('R') before the transformation.
With all that in mind, here is how the user input should look like:
┌─────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │ bit │ 00 │ 01 │ 02 │ 03 │ 04 │ 05 │ 06 │ 07 │ 08 │ 09 │ 10 │ 11 │ 12 │ 13 │ 14 │ ... │ n │ ├─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │ hex │ 0x31 │ * │ * │ 0x33 │ * │ * │ * │ 0x37 │ * │ * │ * │ * │ * │ * │ 0x52 │ ... │ ... │ => SUM = 1337 - 0xA ├─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │ ord │ 1 │ * │ * │ 3 │ * │ * │ * │ 7 │ * │ * │ * │ * │ * │ * │ R │ ... │ ... │ └─────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
If the user input is 15 characters long, let's guess the unknown character (*) as follows:
>>> (1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52) / 11 99 >>> (1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52) % 11 1
99 is in the range of the printable characters but as there is a remainder to the division, we need another unknown character to the user input and it should be in the range of the printable characters ([0x21-0x7E]). Let's start by guessing bit #15 to 0x21:
>>> (1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52 - 0x21) / 11 96 >>> (1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52 - 0x21) % 11 1
96 is in the range of the printable characters but there is a remainder to the division, which could not fit for an additional character.
Let's try with 0x22:
>>> (1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52 - 0x22) / 11 96 >>> hex((1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52 - 0x22) / 11) '0x60' >>> (1337 - 0xA - 0x31 - 0x33 - 0x37 - 0x52 - 0x22) % 11 0
This time, it's perfect because the division has no remainder. Hence, here are the values we have found:
┌─────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │ bit │ 00 │ 01 │ 02 │ 03 │ 04 │ 05 │ 06 │ 07 │ 08 │ 09 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ ├─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │ hex │ 0x31 │ 0x60 │ 0x60 │ 0x33 │ 0x60 │ 0x60 │ 0x60 │ 0x37 │ 0x60 │ 0x60 │ 0x60 │ 0x60 │ 0x60 │ 0x60 │ 0x52 │ 0x22 │ => SUM = 1337 - 0xA ├─────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │ ord │ 1 │ ` │ ` │ 3 │ ` │ ` │ ` │ 7 │ ` │ ` │ ` │ ` │ ` │ ` │ R │ " │ └─────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
Solution
$ ./1337-patched Ready to become 1337? `1`3```7``````R" W3LL d0|\|3, y0u'r 50 L337 |\|0W...
Comments
Keywords: grehack-2013 assembly x86 reverse-engineering crackme