Skip to content

Latest commit

 

History

History
26 lines (24 loc) · 3.86 KB

solution.md

File metadata and controls

26 lines (24 loc) · 3.86 KB

Challenge

What more could you ask for than a program that loads the flag for you? Just answer a few simple questions and the flag is yours!

Setup

Host: pwn-2021.duc.tf
Port: 31919
Executable: flag_loader

Solution

After decompiling the executable with Ghidra, we see that the program starts by calling init, which will set an asynchronous signal SIGALRM that will halt execution after 60 seconds. Then, it will call three different "check" functions, take the results of each of them, multiply them together and then sleep for that amount of time before the flag is printed. However, the first check function will return a non-zero value, and the second and third check functions will return numbers greater than or equal to 60, so it's not possible to normally bypass the timer. We must think of something else.
Check 1 will take in 5 characters, XOR their integer values with the characters "DUCTF" respectively, add all the final values up and verify that the sum is 0 (otherwise the program will exit). This seems to indicate that our input should be "DUCTF", however we run into another issue in that the integer values of the characters are also multiplied together along with the numbers from 1 to 5, and if the result is equal to 0, then the program will exit. However, from the assembly code, we see that only a byte is analysed, which means that even if the resulting product is a multiple of 256, it will be interpreted as 0 (which is the case for DUCTF). However, we can bypass this issue by leveraging the same issue of an integer overflow: we can select a set of characters which, when XOR'd with the characters "DUCTF" and added, will result in 256. For the same reason, it will be interpreted as 0. We can choose our input characters to be "\x16\x07\x11\x51\x43" to satisfy this requirement.
Also, this function provides us with the inspiration to solve issue of the alarm: when each of the three functions' values are multiplied together, if the result overflows past 32 bits, we can set the program to sleep for 0 seconds. Thus, for each check, we can "smuggle" binary zeroes into our result in order to have 32 zeroes in total after each check. Check 1 resulted in 0x90, so we have smuggled 4 zeroes here.
Check 2 will check whether or not two user-provided numbers add to a randomly generated 16 bit integer. However, these numbers must be greater than the output, and their product must be greater than or equal to 60. Clearly, we need to perform another overflow. Furthermore, we must smuggle as many zeroes in, however because the "dword" of the product cannot be 0, otherwise it would be less than 60 (i.e. the product is not a multiple of 65536), we can only smuggle at most 15 zeroes. One way to do this is to reset the program until we get a random number which is odd and is greater than 0x7fff (i.e. 1 in most and least significant bits), and input the numbers 0x80008000 and r - 0x8000 + 0x80000000.
Check 3 will verify whether 5 numbers generated by the user add up to a randomly generated number (strictly ascending from a to e) and that (e - d) * (c - b) >= 60. This one is simpler, since we can just set a, b, c, d = 1, 2, 3, 4, and e as r - 10 (where r is the random number). However, once again, we must return as many zeroes as possible back to the main function, and since we're reading only 16 bits, the maximum number of bits we can smuggle is 15. Let's say e - d = 0x100 and c - b = 0x80. An example of a script that achieves this is shown below:

def check3(n):
	k = n - 642
	a = k % 4
	b = (k - a) // 4
  	c = b + 0x80
 	d = c + 1
 	e = d + 0x100
	return a, b, c, d, e

In total, we have 4 + 15 + 15 = 34 zeroes when each of the returned values are multiplied, resulting in an overflow that outputs 0. Sleep should now call with 0 as its parameter, and the flag should be immediately printed.
Flag: DUCTF{y0u_sur3_kn0w_y0ur_int3gr4l_d4t4_typ3s!}