pwnable.kr—blackjack
解题思路
万万没想到这个题突破点在这里,我本想不看别人的writeup 独立做出这个题目的,但还是败给了自己,加油!
1.先在服务器玩一下这个游戏,然后去看游戏的源代码;是一个不难理解的21点游戏
2.查看游戏的源代码,了解游戏的实现
3.关键代码所在:line721—line734
int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);
if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function
这个函数是游戏的过程中,保证玩家压的赌注是小于他所拥有的本钱的。可以看到,如果bet(赌注)>cash(玩家此时的本钱),那么要求玩家重新输入。但是!!!玩家再次输入的时候,没有循环判断!!!直接将第二次输入的bet值return了。那么我们就可以将押金压上1000,000,然后赢一次就赢了1000,000,然后就可以得到flag了。(当然输了也没关系啊,大不了从头再来)
so easy………………………………………………………………..
我遇到的问题
1.我的第一个思路
首先我在看游戏源码的时候,感觉这个游戏的规则很乱,就怀疑会不会存在一些逻辑漏洞,我可以根据这个判断逻辑更好的赢钱。
游戏的大致逻辑如下:
判断玩家==21:玩家赢
判断玩家>21:玩家输
判断玩家\<21&&玩家选择Hit:玩家加上随机数;dealer加上随机数;接着如果dealer==21,玩家输;如果dealer>21,则玩家赢,这个时候其实玩家也可能>21了,只不过先判断了dealer输;
判断玩家\<21&&玩家选择Stay:这个时候相当于玩家半等死;不断执行dealer()(当然dealer函数中表示如果total>17就不再add了);这时先比较玩家和dealer的大小,然后再判断dealer是否超过了21…..(这个游戏规则);
然而并没有什么卵用…………..只是想吐槽一下这个游戏规则,虽然这个规则在看似不合理中却也还是比较合理的,玩家和dealer都有占便宜的机会…….
2.我的第二个思路:
玩了很久的这个游戏,我突然想到这里用到的是随机数,于是着重看了源代码中的srand()和rand(),并试图从这上边找到突破点。
令我开心的是,我写了下边一段代码来寻找伪随机数中的问题:
#include<time.h>
#include<stdio.h>
void func();
int main()
{
int i=0;
func();
printf("simple seed:\n");
srand((unsigned)time(NULL));
for(i=0;i<200;i++)
{
printf("%d ",(rand()%13)+1);
}
printf("\n");
for(i=0;i<20;i++)
{
if(i%4==0)
{
func();
}
}
return 0;
}
void func()
{
int i=0;
printf("func random:\n");
srand((unsigned)time(NULL));
for(i=0;i<200;i++)
{
printf("%d ",(rand()%13+1));
}
printf("\n");
}
我发现,当我多次重复下边这部分时,产生相同的伪随机数
srand((unsigned)time(NULL));
rand()%13+1;
于是我开心的以为这个题目中,如果我能预知双方下一张牌是什么的话,1000,000当然不在话下,可以一夜暴富了。
但是我对游戏代码进行输出测试的时候,发现并不是我想象的那样啊。它们为什么产生的伪随机数列不同呢?可是我的测试代码却相同呢?于是查阅资料并且重新对我的代码进行修改测试后得到这样结论:
srand((unsigned)time(NULL))是取当前时间作为时间种子的,但是cpu执行一段简单的代码或者一个for循环的时间太短了,以至于time(NULL)获得的当前时间是一样的,所以才会产生相同的伪随机数列。然而,由于游戏当中,玩家的操作以及选择都需要时间,所以每次的时间种子都是不一样的。out
我在测试代码中加了几个sleep(),发现事实确实如此….经过一段时间后,它们不再产生相同的随机数列了。
收获
1.sleep()的用法:执行挂起一段时间;在VC中使用头文件(windows.h),sleep的首字母大写,参数单位是毫秒;在gcc编译器中,linux环境下(unistd.h),首字母不大写,参数单位是秒。
2.常见的srand()的用法:srand((unsigned)time(NULL));srand(time(0))(前一种的缩写);srand((int)getpid())(使用当前程序的ID来作为初始化种子,因此在同一个程序中这个种子是固定的)
3.别打我……英语中spade(黑桃)heart(桃心)club(梅花)diamond(方块)
4.system(“cls”)和system(“pause”):system()作用是调用系统命令;声明与stdlib.h,形式为int system(char* cmd),执行cmd中的命令;cmd中”cls”的作用清除屏幕的输出;”pause”是暂停。
疑惑
1.在asktitile函数中玩家输入N,程序有友好的退出界面;但是在askover中的N,程序闪退,没有输出期望的内容?
2.发现如果不输入bet值的话,在enter bet:后边直接输入H或S,赌注是我上一次的赌注,而且程序是可以继续运行的?