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
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
0040060b MOV dword ptr [RBP + local_c],param_1
0040060e MOV qword ptr [RBP + local_18],param_2
00400612 MOV qword ptr [RBP + local_20],RDX
00400616 MOV param_1=>s_PATH=/fuckyouverymuch_00400768,s_PA = "PATH=/fuckyouverymuch"
0040061b MOV EAX,0x0
00400620 CALL putenv int putenv(char * __string)
00400625 MOV RAX,qword ptr [RBP + local_18]
00400629 ADD RAX,0x8
0040062d MOV RAX,qword ptr [RAX]
00400630 MOV param_1,RAX
00400633 CALL filter undefined filter()
00400638 TEST EAX,EAX
0040063a JZ LAB_00400643
0040063c MOV EAX,0x0
00400641 JMP LAB_00400660
LAB_00400643 XREF[1]: 0040063a(j)
00400643 MOV RAX,qword ptr [RBP + local_18]
00400647 ADD RAX,0x8
0040064b MOV RAX,qword ptr [RAX]
0040064e MOV param_1,RAX
00400651 MOV EAX,0x0
00400656 CALL system int system(char * __command)
0040065b MOV EAX,0x0
LAB_00400660 XREF[1]: 00400641(j)
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
0040059c MOV qword ptr [RBP + local_20],RDI
004005a0 MOV dword ptr [RBP + local_c],0x0
004005a7 MOV RAX,qword ptr [RBP + local_20]
004005ab MOV ESI=>DAT_0040075c,DAT_0040075c = 66h f
004005b0 MOV RDI,RAX
004005b3 CALL strstr char * strstr(char * __haystack,
004005b8 TEST RAX,RAX
004005bb SETNZ AL
004005be MOVZX EAX,AL
004005c1 ADD dword ptr [RBP + local_c],EAX
004005c4 MOV RAX,qword ptr [RBP + local_20]
004005c8 MOV ESI=>DAT_00400761,DAT_00400761 = 73h s
004005cd MOV RDI,RAX
004005d0 CALL strstr char * strstr(char * __haystack,
004005d5 TEST RAX,RAX
004005d8 SETNZ AL
004005db MOVZX EAX,AL
004005de ADD dword ptr [RBP + local_c],EAX
004005e1 MOV RAX,qword ptr [RBP + local_20]
004005e5 MOV ESI=>DAT_00400764,DAT_00400764 = 74h t
004005ea MOV RDI,RAX
004005ed CALL strstr char * strstr(char * __haystack,
004005f2 TEST RAX,RAX
004005f5 SETNZ AL
004005f8 MOVZX EAX,AL
004005fb ADD dword ptr [RBP + local_c],EAX
004005fe MOV EAX,dword ptr [RBP + local_c]
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
Now, we can’t use the flag word… But the check isn’t that sophisticated. Why not just use a non-existinging variable?
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