The second exercise on pwnable.kr is the col one. It seems to suggest we can generate a hash without knowing the original phrase. But let us see what is on the server provided for us.
col@pwnable:~$ ls -ls
total 16
8 -r-sr-x--- 1 col_pwn col 7341 Jun 11 2014 col
4 -rw-r--r-- 1 root root 555 Jun 12 2014 col.c
4 -r--r----- 1 col_pwn col_pwn 52 Jun 11 2014 flag
Again we have an executable with the S flag, a flag file and the source code. Now I am not going to look in the source code yet, I want to disassemble col first without any knowledge.
We copy the col file locally and open it up with Ghidra. We navigate to the main function and do a quick diagonal look over it. Here it seems that the main function will call a function named check_password which seems to return a hashcode. The next line will compare the hashcode to one saved in the program.
Out of the strings defined in the program we can see that the program expects one argument and that argument should be 20 chars long. Of course this could be a lie.
We skip the function prologue and notice the next opcodes
080484da MOV EAX,dword ptr [EBP + param_2]
080484dd MOV dword ptr [ESP + local_84],EAX
080484e1 MOV EAX,GS:[0x14]
080484e7 MOV dword ptr [ESP + local_14],EAX
080484ee XOR EAX,EAX
Now, what is this weird GS:[0x14]
and what is it doing?
If we look through the code we will notice nothing is accessing this
local_14 until the very end.
...
08048592 MOV EDX,dword ptr [ESP + local_14]
08048599 XOR EDX,dword ptr GS:[0x14]
080485a0 JZ LAB_080485a7
080485a2 CALL __stack_chk_fail
So just before the function epilogue we suddenly do these instructions.
GS:[0x14]
from the start (we saved it
into local_14) into EDX.GS:[0x14]
GS:[0x14]
it will call the __stack_chk_fail function. So the purposes of this
was to check if someone tried to be naughty and had caused a stack
overflow! (The overflow has happened, this just tells the programmer
that it happened.)This is something we can ignore now but do know that a lot of programs have these stack checks. These are put in place to try and prevent an attacker from changing for example the return address or enter other malicious code.
Back to the top after our stack_check
080484f0 CMP dword ptr [EBP + param_1],0x1
080484f4 JG LAB_08048514
080484f6 MOV EAX,dword ptr [ESP + local_84]
080484fa MOV EDX,dword ptr [EAX]
080484fc MOV EAX,s_usage_:_%s_[passcode]_08048680 = "usage : %s [passcode]\n"
08048501 MOV dword ptr [ESP + local_9c],EDX
08048505 MOV dword ptr [ESP]=>local_a0,EAX=>s_usage_:_%s_[p = "usage : %s [passcode]\n"
08048508 CALL printf
0804850d MOV EAX,0x0
08048512 JMP LAB_08048592
Just like previous function, this is a simple if structure to check if there is at least one parameter passed to the function. If not it will pass a string with helpful text into the stack and call printf, after that we set EAX to zero and go the the function epilogue (with the stack-check we described above).
08048514 MOV EAX,dword ptr [ESP + local_84]
08048518 ADD EAX,0x4
0804851b MOV EAX,dword ptr [EAX]
0804851d MOV dword ptr [ESP + local_88],0xffffffff
08048525 MOV EDX,EAX
08048527 MOV EAX,0x0
0804852c MOV ECX,dword ptr [ESP + local_88]
08048530 MOV EDI,EDX
08048532 SCASB.REPNE ES:EDI
08048534 MOV EAX,ECX
08048536 NOT EAX
08048538 SUB EAX,0x1
0804853b CMP EAX,0x14
0804853e JZ LAB_08048553
08048540 MOV dword ptr [ESP]=>local_a0,s_passcode_length_sh = "passcode length should be 20
08048547 CALL puts
0804854c MOV EAX,0x0
08048551 JMP LAB_08048592
The next block is a bit bigger but I copied until again
LAB_08048592
which we now have seen many times as the “if
this code is executed we will jump to the function epilogue”. So this
code probably does another check. The pre-defined string in this section
shows us that this will probably check if our input is of length 20.
local_84
into EAX (in the
first code block we saw that the char array pointer was saved into
local_84
)0x4
to have the pointer to the second item in
argv (which is the first argument as the first element of argv is the
program name)argv[1]
) into EAX0xffffffff
into
local_88
argv[1]
) into EDXlocal_88
into ECX ( which was
0xffffffff
)argv[1]
) into EDICMP 0x14
(20 decimal) it seems this is a
strlen function. But let us continue to see how it works. REPNE is
Repeat while not equal so it will continue going further in the string
until 0x0 is found (in combination with SCASB)0xffffffff
and went -1 every iteration of REPNE so if our
string was 20 chars long it would be 0xffffffea
0xffffffea
to 0x00000015
(which is… decimal
21)0x1
is minus 1, but why does it do
this? Simple, the SCASB went 21 times for it to find 0x0
so
we still need to reduce it with one if we don’t want to count the
0x0
if(strlen(argv[1]) == 20)
argv[1]
was not equal to strlen = 20 then an error
message would be shown and a JMP to the function epilogue would happen.
But now we skip over this fatal jump thanks to our JZ into the next
code!08048553 MOV EAX,dword ptr [ESP + local_84]
08048557 ADD EAX,0x4
0804855a MOV EAX,dword ptr [EAX]
0804855c MOV dword ptr [ESP]=>local_a0,EAX
0804855f CALL check_password
When we have passed the check if our argv[1]
is a string
of length 20 we come here. Now here is something I made a mistake first
and only after a closer look got it right. The pointer to the char array
is added to EAX, we add 0x4
to it so we get the pointer to
the second item of this array and that ADDRESS is passed to
check_password
, not the value! argv is actually a
char[][]
and I was treating it as a char[]
08048494 PUSH EBP
08048495 MOV EBP,ESP
08048497 SUB ESP,0x10
0804849a MOV EAX,dword ptr [EBP + param_1]
0804849d MOV dword ptr [EBP + local_8],EAX
080484a0 MOV dword ptr [EBP + local_c],0x0
080484a7 MOV dword ptr [EBP + local_10],0x0
080484ae JMP LAB_080484c2
LAB_080484b0
080484b0 MOV EAX,dword ptr [EBP + local_10]
080484b3 SHL EAX,0x2
080484b6 ADD EAX,dword ptr [EBP + local_8]
080484b9 MOV EAX,dword ptr [EAX]
080484bb ADD dword ptr [EBP + local_c],EAX
080484be ADD dword ptr [EBP + local_10],0x1
LAB_080484c2
080484c2 CMP dword ptr [EBP + local_10],0x4
080484c6 JLE LAB_080484b0
080484c8 MOV EAX,dword ptr [EBP + local_c]
080484cb LEAVE
080484cc RET
Ok, I’ve just copied the whole check_password
function
as it doesn’t seem that big. We have again first the function prologue
and we reserve a stack of only 10 bytes this time for this function
stackframe (SUB ESP, 0x10
)
char* local_8 = param1
local_c, local_10 = 0
while(local_10 <= 4) {
local_c += *(local_8 + local_10*4) // or maybe local_8[local_10]
}
So now we return to our main function were immediately after we see
This puts a pre-defined hashcode into EDX, does a CMP with the return
value of check_password and if they are the same will execute the
/bin/cat flag
as our previous post explained. So the only
thing to do now is find the string we must input were the first 5 values
summed up are equal to a hashcode of 0x21DD09EC
Because the value ends in 0xC
we can already know that
this value isn’t divisble by 5 without a remainder so our passcode will
be 4 times the same dword char and a fifth
First four dwords: 0x21DD09EC / 5 = 0x06C5CEC8
Fifth dword: 0x21DD09EC – 0x06C5CEC8 * 4 = 0x06C5CECC
Our passcode is 0x06C5CEC8 * 4 + 0x06C5CECC
Now I must confess, with the next part I was stuck myself and started looking up on the internet how to do it. Apparently the easiest way to pass hex values to a program is to use python. Another thing they said was that you must pass them as little endians. This is something easily missed especially as a beginner myself. The best way to see this is also by doing a live analyse. When stuck I’ll do that next time!
Now why is this exercise called collissions? Because the hashcode has multiple answers. We could also do
First three dwords: 0x06C5CEC8
Last two dwords: (21DD09EC – 0x06C5CEC8 * 3) / 2 = 0x06C5CECA
Our passcode 0x06C5CEC8 * 3 + 0x06C5CECA * 2
Multiple values can give the correct password.
< Home