C语言中基础数据类型的取值范围——整型溢出问题

1 整型

C++提供好几种整型,以便能够根据程序的具体要求选择最合适的整型。不同的整型使用不同的内存量来储存整数,使用的内存量越大, 可以表示的整数值范围也越大。C++的基本整型(按宽度递增的顺序排列)分别是 char, short, int, long 和C++11新增的 long long ,每种类型都分 有符号版本 和 无符号版本,因此共有10种类型可供选择。下面更详细地介绍这些整数类型。

计算机内存由一些叫做 位 (bit)的单元组成。不同的类型通过使用不同数目的位来存储值。如果所有的系统中,没中类型的宽度都相同,则使用起来将非常方便,不过生活并非那么简单。C++提供了一种灵活的标准,它确保了最小长度(从C语言借鉴而来):

  • short至少16位;
  • int 至少与short一样长;
  • long至少32位,且至少与int一样长;
  • long long 至少64位,且至少与 long 一样
// C++

int main()
{
    using namespace std;
    short sam = SHRT_MAX;        // 32767
    unsigned short sue = sam;    // 32767
    
    sam = sam + 1;        // -32767
    sue = sue + 1;        // 32768

    sam = 0;
    sue = 0;
    
    sam = sam - 1;        // -1
    sue = sue - 1;        // 65535

    return 0;
}

4 实战分析

本题来自 PicoCTF , 基础模块的倒数第三题, roulette

// 来自 picoCTF roulette.c

include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
//#include <unistd.h>
#include <windows.h>
#include <limits.h>

#define MAX_NUM_LEN 12
#define HOTSTREAK 3
#define MAX_WINS 16
#define ONE_BILLION 1000000000
#define ROULETTE_SIZE 36
#define ROULETTE_SPINS 128
#define ROULETTE_SLOWS 16
#define NUM_WIN_MSGS 10
#define NUM_LOSE_MSGS 5

long cash = 0;
long wins = 0;

int is_digit(char c) {
	bool is_true =  '0' <= c && c <= '9';
	return is_true;
}

long get_long() {
	printf("long_max: %lu", LONG_MAX);
	printf("> ");
	uint64_t l = 0;
	char c = 0;
	while (!is_digit(c))
		c = getchar();

	while (is_digit(c)) {
		if (l >= LONG_MAX) {
			l = LONG_MAX;
			break;
		}
		l *= 10;
		l += c - '0';
		c = getchar();
	}
	while (c != '\n')
		c = getchar();
	return l;
}

long get_rand() {
	long seed;
	seed = 3000;
	//FILE *f = fopen("/dev/urandom", "r");
	//fread(&seed, sizeof(seed), 1, f);
	////fclose(f);
	//seed = seed % 5000;
	//if (seed < 0) seed = seed * -1;
	//srand(seed);
	return seed;
}

long get_bet() {
	while (1) {
		puts("How much will you wager?");
		printf("Current Balance: $%lu \t Current Wins: %lu\n", cash, wins);
		long bet = get_long();
		if (bet <= cash) {
			return bet;
		}
		else {
			puts("You can't bet more than you have!");
		}
	}
}

long get_choice() {
	while (1) {
		printf("Choose a number (1-%d)\n", ROULETTE_SIZE);
		long choice = get_long();
		if (1 <= choice && choice <= ROULETTE_SIZE) {
			return choice;
		}
		else {
			puts("Please enter a valid choice.");
		}
	}
}

int print_flag() {
	char flag[48];
	//FILE *file;
	//file = fopen("flag.txt", "r");
	//if (file == NULL) {
	//	printf("Failed to open the flag file\n");
	//	return -1;
	//}
	//fgets(flag, sizeof(flag), file);
	//printf("%s", flag);
	return 0;
}

const char *win_msgs[NUM_WIN_MSGS] = {
	"Wow.. Nice One!",
	"You chose correct!",
	"Winner!",
	"Wow, you won!",
	"Alright, now you're cooking!",
	"Darn.. Here you go",
	"Darn, you got it right.",
	"You.. win.. this round...",
	"Congrats!",
	"You're not cheating are you?",
};

const char *lose_msgs1[NUM_LOSE_MSGS] = {
	"WRONG",
	"Nice try..",
	"YOU LOSE",
	"Not this time..",
	"Better luck next time..."
};

const char *lose_msgs2[NUM_LOSE_MSGS] = {
	"Just give up!",
	"It's over for you.",
	"Stop wasting your time.",
	"You're never gonna win",
	"If you keep it up, maybe you'll get the flag in 100000000000 years"
};

void spin_roulette(long spin) {
	int n;
	puts("");
	printf("Roulette  :  ");
	int i, j;
	int s = 12500 / 1000000;
	for (i = 0; i < ROULETTE_SPINS; i++) {
		n = printf("%d", (i%ROULETTE_SIZE) + 1);
		Sleep(s);
		for (j = 0; j < n; j++) {
			printf("\b \b");
		}
	}
	for (i = ROULETTE_SPINS; i < (ROULETTE_SPINS + ROULETTE_SIZE); i++) {
		n = printf("%d", (i%ROULETTE_SIZE) + 1);
		if (((i%ROULETTE_SIZE) + 1) == spin) {
			for (j = 0; j < n; j++) {
				printf("\b \b");
			}
			break;
		}
		Sleep(s);
		for (j = 0; j < n; j++) {
			printf("\b \b");
		}
	}
	for (int k = 0; k < ROULETTE_SIZE; k++) {
		n = printf("%d", ((i + k) % ROULETTE_SIZE) + 1);
		s = 1.1*s;
		Sleep(s);
		for (j = 0; j < n; j++) {
			printf("\b \b");
		}
	}
	printf("%ld", spin);
	Sleep(s);
	puts("");
	puts("");
}

void play_roulette(long choice, long bet) {

	printf("Spinning the Roulette for a chance to win $%lu!\n", 2 * bet);
	long spin = (rand() % ROULETTE_SIZE) + 1;

	spin_roulette(spin);

	if (spin == choice) {
		cash += 2 * bet;
		puts(win_msgs[rand() % NUM_WIN_MSGS]);
		wins += 1;
	}
	else {
		puts(lose_msgs1[rand() % NUM_LOSE_MSGS]);
		puts(lose_msgs2[rand() % NUM_LOSE_MSGS]);
	}
	puts("");
}

int main(int argc, char *argv[]) {
	setvbuf(stdout, NULL, _IONBF, 0);

	cash = get_rand();

	puts("Welcome to ONLINE ROULETTE!");
	printf("Here, have $%ld to start on the house! You'll lose it all anyways >:)\n", cash);
	puts("");

	long bet;
	long choice;
	while (cash > 0) {
		bet = get_bet();
		cash -= bet;
		choice = get_choice();
		puts("");

		play_roulette(choice, bet);

		if (wins >= MAX_WINS) {
			printf("Wow you won %lu times? Looks like its time for you cash you out.\n", wins);
			printf("Congrats you made $%lu. See you next time!\n", cash);
			exit(-1);
		}

		if (cash > ONE_BILLION) {
			printf("*** Current Balance: $%lu ***\n", cash);
			if (wins >= HOTSTREAK) {
				puts("Wow, I can't believe you did it.. You deserve this flag!");
				print_flag();
				exit(0);
			}
			else {
				puts("Wait a second... You're not even on a hotstreak! Get out of here cheater!");
				exit(-1);
			}
		}
	}
	puts("Haha, lost all the money I gave you already? See ya later!");
	return 0;
}

代码挺长,重要的没几段。现在的目的是要拿到 1000000000 ,光靠运气肯定是不行的。

这段代码里有2个 bug ,一个是 rand() 函数,这个函数在使用之前先调用 srand(seed) 来进行 随机数种子值的确定,这个 seed 一旦确定之后,再调用 rand() 则将得到固定的 “随机数”。代码中使用 确定的 cash 值作为 种子值,因此,可以在另一个程序中使用同样的 种子值 从而 预测出 轮盘的结果。这就是 bug 之一。

虽然上述 bug 能够保证百分百的胜率了,但是想要赢一个亿显然需要很长的时间。因此,一定存在第二个 bug,而且,程序中设置了 连续赢的次数 这一常量,说明:这个程序时可以在3次之内得到一个亿的。该怎么做呢?分析如下,边写边缕清思路。

get_long() 函数设计的不错,能够过滤掉字符,并且只接受 第一个数字和最后一个非数字之间的 数字。 他使用 uint64_t L = 0;(在 vs 2015 中 为 long long 类型)来存储接受到的数字,并且在大于 long 的最大值时被赋予 long 的最大值:(win10 vs2015 中 long_max 为 2147483647)。

cash 的类型为 long,我们想要使得 cash 变为 一个亿。 整个程序中 只有 在 main() 函数中的 cash -= bet; 处有机会得到,而且 bet一定要小于cash (get_bet() 函数),虽然这个函数进行了 bet < cash 的判断,但是并没有判断 bet 是否小于0 ,因此,这是第二个 bug,只要 bet 是一个 比较大的负数,就可以实现 cash 一个亿。

cash 的类型为long, bet 的类型为long。 bet是通过 类型为 uint64_t 的 L 赋予的。因此,只要将 L 赋予的值超过 long 的最大值即可将 bet 变为负数。而 get_long() 中 做了 最大值的限定,因此,只要我们输入一个大于 2147483647 的值 并且 数值的位数一定要和 2147483647 相同,即10个数字,才能将这个值传到 bet 中。

一开始我试了 2147483648,后来发现 cash + 2147483648 又超了最大值,所以再加了5000,即 2147488648,这样,当 cash 为 3000 时,我得到了 2147481648,大于了1个亿,并成功拿到了 FLAG.

发布了30 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/tiancailx/article/details/94153585