(9)
Sign in Please 請登入
500 Points / 1 Solves
Blackb6a
T0022 Member 3
I have implemented a secure authentication system. You can't eavesdrop the passwords, can you?
我發明了一個安全的身份驗證系統。反正沒有人能竊聽密碼吧。
nc tertiary.pwnable.hk 50001
Basically, the challenge is asking for the answer (auth
) to the hash given the pbox
and the salt
, which we do not know in advance.
Also, we are given the function (spy
) to test out the hash calculated using our input (pbox
and salt
) and the unknown password
.
The whole string to test is 20-byte long, which composes of the random password
(16 bytes) generated at the beginning of the connection and the salt
(4 bytes), and the string gets shuffled by the given pbox
.
Simply speaking, it is returning [payload[pbox[0]], payload[pbox[1]], payload[pbox[2]], ..., payload[pbox[len(pbox) - 1]]]
in the form of bytes.
Notice that the permutate
process only retrieves the characters with the index given in the pbox
, it does not check for the length of the pbox
, or any repetition in the pbox
.
So initially I would think that it would be useful using a rainbow table to recover the password
.
The 16-byte password
is generated by encoding a random 12-byte integer using base64, so only the 64 characters used in base64 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
) are possible to appear in the password
part.
For example, if we send as the pbox
, it is supposed to permutate the payload
to a 2-distinct(1-if-duplicated)-character-only string (), which generated hash can be checked against our rainbow table of every "2-character combination repeated 10 times" (only combinations) to obtain the first 2 characters of the password.
We have only 10 iterations available to test, so at least 2 characters have to be recovered per iteration to recover the password.
A program is created to test this type of pbox
, but 'no way!' in the exception part appears.
To find out the issues, we can see that there is a verification check to the pbox
.
It means that we can only use 4 characters for the salt
, and pbox
should contain exactly 20 different integers as indices.
I was wondering if I can use different integers to obtain the same element from the payload
array. The only possible integers I can think of is negative indices for counting from the end (e.g. 2 and -18 corresponding to the same element in a 20-byte payload)
However, we only have control over the 4-byte salt. So at most we can use 8 bytes for the known characters in the payload, and the remaining 12 bytes are in the password, thus at least 6 elements have to be loaded from the password part.
(e.g. pbox
= )
To create a rainbow table for 6 characters, we have to store values which takes so much time which makes this method impractical.
So the idea of negative indices is abandoned.
So it seems that our last resort is the SHA256 algorithm.
SHA256, which generates a 256-bit hash as indicated in the name, algorithm breaks the payload
into 512-bit (64-byte) blocks. The initial hash array is a 256-bit constant split into 8 parts for calculation. The initial hash array for the next block is the hash calculated from the previous block. As it seems that the default hashlib
library does not provide the feature to use custom initial hash values, so I crafted my SHA256
library according to the SHA-2 documentation.
Before the calculation, the payload
is first padded to the length of a multiple of 512 bits (64 bytes). A '1'
bit is appended to the payload
first, then '0'
bits are appended until there are still 64 bits to a multiple of 512 (), then the 64-bit representation of the length of the payload (in bits) is added at the end.
For the default 20-byte payload, the padded payload should appear as below:
where , and 128 is equivalent to a '1'
bit and 7 '0'
bits.
Therefore, if a length extension attack is carried out to calculate the hash, we have to use the second block.
Also, the content of the first block should not be modified in order to have a known initial hash.
which means, we need chr(128), chr(0) and chr(160) in the salt
in order to have the pbox-permutated result containing these bytes.
The order is not important for the pbox
, so by default we can use
as after pbox-permuatation the order of the payload
remains the same (and so the first 16-byte is the password
we want, which is convenient to us).
Therefore, we carry out our first test using
as the pbox
, and the salt
explained below (which is not important in this stage).
(Notice that the assert
check only checks for 20 distinct values in pbox
, but not repeating elements, so we can definitely send a pbox
of more than 20 elements as the input.)
The hash returned is the initial hash array of the 2nd block in SHA256.
To construct the 2nd block that contains our desired value, we would like to use 2 bytes from the password as the start of this block, so each time we can recover 2 bytes of the password after comparing the returned hash from the system to our rainbow table.
The structure of the 2nd block:
where and are the tested part of the password.
The padded block should appear as below: (Notice that in total we have to send a bits string).
The block size is
Therefore, the rainbow table is generated using the tested hash received from the server as the initial hash, and
, where and are substituted by each possible pairs of characters in base64, as the padded payload.
Notice that to achieve that, we have to disable the padding procedure of the SHA256 function, which also seems to be impossible with the default python hashlib
library.
After generating the hash table, the next step is to send the pbox
and salt
.
The supposed content of the payload
after permutated by the pbox
is and appended just after the first block.
Also, as mentioned before, the pbox
can be much longer as long as exactly 20 different elements are found in it.
Besides, the salt
has to be crafted carefully so we can have chr(128), chr(0) and chr(160) in the permutated payload
.
My chosen salt
is (2 is not used as the input actually)
The custom pbox
is created as follows to construct desired pre-padded payload
:
+ (corresponding to 128) + (corresponding to null bytes) + (corresponding to 160) + (the two characters in password we crack)
in these 8 iterations, we have to test all the 16 characters in password
, so n corresponds to in these 8 iterations.
After receiving the 8 hashes and comparing them to the rainbow table, the password
is recovered, and we have only 1 allowed iteration left.
Therefore, we have to do the authentication this time.
The system provides a random pbox
and salt
. We can directly copy the functions (parse_pbox
and permutate
) in the given source code to calculate the permutated payload
and the required hash value, and send it to the server.
After that, the flag should be shown if nothing is wrong. Solved.
The source code is given in the Walkthrough Part.
sign in.py (the comments are used to test whether my library works properly)
hkcert20{d0_y0u_feel_s3cur3_wh3n_s19nin9_in?}
Do you feel secure when signing in?