题目信息

第一次见还要开容器的逆向题,做完也明显感觉到这道题有点pwn的意思
附件名称为server,猜测就是运行在服务器上的程序
查壳

64位的.elf程序,先用ida看看
逆向分析
程序总体逻辑概览
看了一圈没什么头绪,直接从main()函数入手
int __fastcall main(int argc, const char **argv, const char **envp)
{
int packet; // eax
_BYTE v5[1040]; // [rsp+0h] [rbp-1848h] BYREF
_QWORD v6[130]; // [rsp+410h] [rbp-1438h] BYREF
_BYTE v7[4136]; // [rsp+820h] [rbp-1028h] BYREF
memset(v6, 0, sizeof(v6));
strcpy((char *)&v6[1], "player");
game_reset(&v6[9], argv, envp);
if ( !(unsigned int)db_open(v6, "game.db") )
return 1;
if ( !(unsigned int)db_ensure_schema(v6[0]) || !(unsigned int)db_ensure_player(v6[0], &v6[1], 50) )
{
db_close(v6[0]);
return 1;
}
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
while ( 1 )
{
packet = protocol_read_packet(stdin, (__int64)v5);
if ( !packet )
break;
if ( packet < 0 )
{
protocol_write_response(stdout);
}
else
{
handler_handle_packet((const char *)v6, v5, v7, 0x1000u);
if ( !(unsigned int)protocol_write_response(stdout) )
break;
}
}
db_close(v6[0]);
return 0;
}
不说了上mcp
main
├── 初始化阶段
│ ├── game_reset # 重置游戏状态
│ ├── db_open # 打开数据库 (game.db)
│ ├── db_ensure_schema # 确保数据库表存在
│ └── db_ensure_player # 确保玩家记录存在 (初始50分)
│
├── 主循环 (while True)
│ ├── protocol_read_packet # 读取客户端数据包
│ │
│ └── handler_handle_packet # 处理8种命令
│ │
│ ├── CMD 1: 获取Flag
│ │ ├── db_get_top_player # 获取第一名玩家
│ │ └── getenv("GZCTF_FLAG") # 读取环境变量
│ │
│ ├── CMD 2: 获取玩家信息
│ │ └── db_get_player # 查询玩家数据
│ │
│ ├── CMD 3: 获取排行榜
│ │ └── rank_format_overview # 格式化排行榜
│ │
│ ├── CMD 4: 重置挑战
│ │ ├── db_reset_challenge_state # 重置AI对手分数
│ │ │ └── fixed_scores[7] # 固定分数表
│ │ ├── game_reset # 重置棋盘
│ │ └── db_get_player # 读取玩家信息
│ │
│ ├── CMD 5: 开始游戏
│ │ ├── db_pick_default_opponent # 随机匹配AI对手 (score<=50)
│ │ └── game_start # 初始化游戏
│ │
│ ├── CMD 6: 获取棋盘
│ │ └── handle_get_board
│ │ └── game_serialize_board # 序列化棋盘 (15x15)
│ │
│ ├── CMD 7: 落子 (核心游戏逻辑)
│ │ ├── game_parse_move # 解析坐标 (如 "7,7")
│ │ ├── game_apply_player_move # 玩家落子 (value=1)
│ │ ├── game_has_winner # 检查是否获胜 (连成5子)
│ │ │
│ │ ├── if 玩家未获胜:
│ │ │ ├── ai_choose_move # AI选择最佳位置
│ │ │ │ ├── is_winning_move_constprop_0 # 检查是否能赢
│ │ │ │ └── is_winning_move # 检查能否阻止玩家赢
│ │ │ ├── game_apply_ai_move # AI落子 (value=2)
│ │ │ ├── game_has_winner # 检查AI是否获胜
│ │ │ └── game_is_full # 检查棋盘是否满
│ │ │
│ │ └── settle_game # 结算游戏 (关键!)
│ │ ├── db_get_player ×2 # 获取双方信息
│ │ ├── score_apply_buggy_update # 计算玩家分数 [漏洞]
│ │ ├── score_apply_update # 计算AI分数
│ │ ├── score_calculate_delta # 计算delta值
│ │ └── db_update_player_score ×2 # 更新数据库
│ │
│ └── CMD 8: 认输/结束游戏
│ ├── game_is_empty # 检查是否无有效落子
│ ├── db_get_player ×2 # 获取双方
│ ├── game_reset # 重置棋盘
│ └── settle_game # 结算 (平局或判负)
│
└── db_close # 关闭数据库
简单来说这是一个基于客户端-服务器的ai五子棋游戏,服务器为玩家随机匹配ai对手,玩家通过下五子棋战胜对手获得积分,积分的计算类似ELO机制(这也是题目名字的来源),当玩家来到积分榜第一名才能查看flag,下面我们来一步一步分析
数据包格式
客户端与服务器间的交互是通过交换数据包完成的,题目并未提供与服务器建立连接的方式,我们需要自己写脚本发送与接收数据包来与服务器进行交互,才能玩这个游戏,但前提是我们要了解数据包的格式
protocol_read_packet()和 protocol_write_response()两个函数分别负责读取来自客户端的数据包和向客户端发送数据包,从中我们能看出数据包的格式

游戏逻辑
命令集
handler_handle_packet()函数负责处理玩家的各种命令
int __fastcall handler_handle_packet(const char *a1, _BYTE *a2, void *a3, size_t a4)
{
const char *v6; // rdx
__int64 v7; // rdx
int result; // eax
char *v9; // rax
const char *v10; // rcx
__int64 v11; // rdx
int v12; // r9d
int v13; // r8d
const char *v14; // rcx
_BYTE v15[68]; // [rsp+0h] [rbp-C8h] BYREF
int v16; // [rsp+44h] [rbp-84h]
int v17; // [rsp+48h] [rbp-80h]
char v18[4]; // [rsp+50h] [rbp-78h] BYREF
char s1[64]; // [rsp+54h] [rbp-74h] BYREF
int v20; // [rsp+94h] [rbp-34h]
int v21; // [rsp+98h] [rbp-30h]
memset(a3, 0, a4);
switch ( *a2 )
{
case 1:
if ( !(unsigned int)db_get_top_player(*(_QWORD *)a1, v18) )
return snprintf((char *)a3, a4, "DB error");
if ( strcmp(s1, a1 + 8) )
return snprintf((char *)a3, a4, "Permission denied");
v9 = getenv("GZCTF_FLAG");
v10 = v9;
if ( v9 )
{
if ( !*v9 )
v10 = "Fail to get flag from environment variable";
}
else
{
v10 = "Fail to get flag from environment variable";
}
return snprintf((char *)a3, a4, "%s", v10);
case 2:
if ( !(unsigned int)db_get_player(*(_QWORD *)a1, a1 + 8, v15) )
goto LABEL_25;
v12 = v17;
v13 = v16;
v14 = "OK";
goto LABEL_23;
case 3:
return rank_format_overview(a1, a3, a4);
case 4:
if ( !(unsigned int)db_reset_challenge_state(*(_QWORD *)a1, a1 + 8, 50) )
goto LABEL_25;
game_reset(a1 + 72, a1 + 8, v11);
if ( !(unsigned int)db_get_player(*(_QWORD *)a1, a1 + 8, v18) )
goto LABEL_25;
v12 = v21;
v13 = v20;
v14 = "Reset";
LABEL_23:
result = snprintf((char *)a3, a4, "%s Score=%u Rank=%d", v14, v13, v12);
break;
case 5:
if ( *((_DWORD *)a1 + 243) )
return snprintf((char *)a3, a4, "Game already in progress");
if ( !(unsigned int)db_pick_default_opponent(*(_QWORD *)a1, a1 + 8, v18) )
return snprintf((char *)a3, a4, "No opponent available");
game_start(a1 + 72, s1);
return snprintf((char *)a3, a4, "Matched %s Score=%u You=Black", s1, v20);
case 6:
return handle_get_board(a1, a3, a4);
case 7:
return handle_move(a1, a2, a3, a4);
case 8:
v6 = "No active game";
if ( !*((_DWORD *)a1 + 243) )
goto LABEL_27;
if ( (unsigned int)game_is_empty(a1 + 72, 0, "No active game") )
{
if ( (unsigned int)db_get_player(*(_QWORD *)a1, a1 + 8, v15)
&& (unsigned int)db_get_player(*(_QWORD *)a1, a1 + 976, v18) )
{
game_reset(a1 + 72, a1 + 976, v7);
result = snprintf((char *)a3, a4, "Draw Delta=0 Score=%u Rank=%d OppDelta=0 OppScore=%u", v16, v17, v20);
}
else
{
LABEL_25:
result = snprintf((char *)a3, a4, "DB error");
}
}
else
{
result = settle_game(a1, "Resign", a3, a4, 0.0);
}
break;
default:
v6 = "Unknown command";
LABEL_27:
result = snprintf((char *)a3, a4, v6);
break;
}
return result;
}
通过上面的函数我们可以分析出各个命令的功能
| CMD | 功能 |
|---|---|
| 1 | 获取flag(需取得第一名) |
| 2 | 获取玩家信息 |
| 3 | 获取当前排行榜 |
| 4 | 重置挑战 |
| 5 | 匹配对手,开始游戏 |
| 6 | 查看棋盘 |
| 7 | 落子 |
| 8 | 认输 |
积分机制
积分机制在score_apply_buggy_update()函数中
__int64 __fastcall score_apply_buggy_update(unsigned int a1, int a2, int *a3, double a4)
{
double v5; // xmm2_8
int v6; // eax
v5 = (a4 - 1.0 / (pow(10.0, ((double)a2 - (double)(int)a1) / 50.0) + 1.0)) * 20.0;
v6 = lround(v5);
if ( a3 )
*a3 = v6;
if ( a1 > 9 || v6 >= 0 )
return a1 + v6;
else
return a1;
}
计分公式很简单
v5 = (a4 - 1/(10^((a2-a1)/50) + 1)) * 20.0;
v6 = lround(v5);#取整

分数变化的具体值取决于玩家双方的分数差

这里有个关键的地方
if ( a1 > 9 || v6 >= 0 )
return a1 + v6;
else
return a1;
当玩家分数小于等于9分时,无论输赢分数将保持不变
交互脚本
弄清楚了数据包格式和客户端与服务器的交互逻辑,就可以写脚本连接服务器玩游戏了
#game_client.py
#用法:python game_client.py <server_ip> <port>
#!/usr/bin/env python3
import socket
import struct
import sys
import re
MAGIC = b'GAME'
COLOR_EMPTY = '\033[90m'
COLOR_PLAYER = '\033[91m'
COLOR_AI = '\033[94m'
COLOR_COORD = '\033[92m'
COLOR_RESET = '\033[0m'
def visualize_board(response):
match = re.search(r'Board=([012/]+)', response)
if not match:
print(response)
return
board_str = match.group(1)
rows = board_str.split('/')
if len(rows) != 15:
print(response)
return
print(f"\n{COLOR_COORD} " + " ".join(f"{i:2}" for i in range(15)) + COLOR_RESET)
print(f"{COLOR_COORD} " + "── " * 15 + COLOR_RESET)
for y in range(15):
row = rows[y]
row_content = []
for x in range(15):
cell = row[x]
if cell == '0':
row_content.append(f"{COLOR_EMPTY} ·{COLOR_RESET}")
elif cell == '1':
row_content.append(f"{COLOR_PLAYER} X{COLOR_RESET}")
elif cell == '2':
row_content.append(f"{COLOR_AI} O{COLOR_RESET}")
else:
row_content.append(f" {cell}")
print(f"{COLOR_COORD}{y:2}│{COLOR_RESET}" + "".join(row_content))
print(f"{COLOR_COORD} " + "── " * 15 + COLOR_RESET)
print(f"{COLOR_COORD} " + " ".join(f"{i:2}" for i in range(15)) + COLOR_RESET)
print(f"\n{COLOR_PLAYER}X = 你(黑){COLOR_RESET} | {COLOR_AI}O = AI(白){COLOR_RESET} | {COLOR_EMPTY}· = 空{COLOR_RESET}")
class GameClient:
def __init__(self, host, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, port))
def send_packet(self, cmd, data=b''):
packet = bytes([cmd]) + data
length = 9 + len(data)
msg = MAGIC + struct.pack('>I', length) + packet
self.sock.sendall(msg)
def recv_packet(self):
magic = self.sock.recv(4)
if magic != MAGIC:
return None, None
length_bytes = self.sock.recv(4)
length = struct.unpack('>I', length_bytes)[0]
remaining = length - 8
data = b''
while len(data) < remaining:
chunk = self.sock.recv(remaining - len(data))
if not chunk:
break
data += chunk
return data[0], data[1:].decode()
def cmd1_get_flag(self, player_name):
self.send_packet(1, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def cmd2_get_player(self, player_name):
self.send_packet(2, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def cmd3_get_rank(self):
self.send_packet(3, b'')
cmd, resp = self.recv_packet()
return resp
def cmd4_reset(self, player_name):
self.send_packet(4, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def cmd5_start_game(self, player_name):
self.send_packet(5, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def cmd6_get_board(self):
self.send_packet(6, b'')
cmd, resp = self.recv_packet()
return resp
def cmd7_move(self, x, y):
self.send_packet(7, f"{x},{y}".encode())
cmd, resp = self.recv_packet()
return resp
def cmd8_resign(self):
self.send_packet(8, b'')
cmd, resp = self.recv_packet()
return resp
def close(self):
self.sock.close()
def main():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <host> <port>")
sys.exit(1)
host, port = sys.argv[1], int(sys.argv[2])
client = GameClient(host, port)
player = "player"
while True:
print("\n=== Menu ===")
print("1. Get Flag")
print("2. Get Player Info")
print("3. Get Rank")
print("4. Reset Challenge")
print("5. Start Game")
print("6. Get Board")
print("7. Make Move")
print("8. Resign")
print("q. Quit")
choice = input("Choice: ").strip()
if choice == 'q':
break
try:
if choice == '1':
print(client.cmd1_get_flag(player))
elif choice == '2':
print(client.cmd2_get_player(player))
elif choice == '3':
print(client.cmd3_get_rank())
elif choice == '4':
print(client.cmd4_reset(player))
elif choice == '5':
print(client.cmd5_start_game(player))
elif choice == '6':
resp = client.cmd6_get_board()
visualize_board(resp)
elif choice == '7':
move = input("Enter move (x,y): ").strip()
x, y = move.split(',')
resp = client.cmd7_move(int(x), int(y))
visualize_board(resp)
print(resp)
elif choice == '8':
print(client.cmd8_resign())
except Exception as e:
print(f"Error: {e}")
client.close()
if __name__ == "__main__":
main()
取胜策略
因为ai玩家有上千个,让我们不断地下五子棋战胜取得第一是不可能的
但是积分机制中存在一个下溢出漏洞,可以让我们轻松获得高分
在score_apply_buggy_update()函数中
__int64 __fastcall score_apply_buggy_update(unsigned int a1, int a2, int *a3, double a4)
a1为玩家分数,是一个无符号整数,当我们不断让自己扣分直到分数变为负数,a1就会下溢出为一个超大的数,从而取得第一
但是我们不能无脑输,因为当我们分数小于等于9分时,分数将会锁定,上面的分析我们得知,游戏失败时我们最少扣10分,因此我们可以制定策略,让自己在最后一局前扣到9分,然后再输一局扣10分,刚好得到-1分
由此我们可以制定下面的策略
| 步骤 | 操作 | 赛前分数 | delta | 赛后分数 |
|---|---|---|---|---|
| 1 | reset | 50 | - | 50 |
| 2 | 赢10分对手 | 50 | +3 | 53 |
| 3 | 输20分对手 | 53 | -16 | 37 |
| 4 | 输20分对手 | 37 | -14 | 23 |
| 5 | 输30分对手 | 23 | -8 | 15 |
| 6 | 输38分对手 | 15 | -5 | 10 |
| 7 | 输7分对手 | 10 | -11 | -1 -> 4294967295 |
我们每一轮都需要挑选特定分数的对手,如果没有匹配到想要的对手就直接投降然后重新匹配
接下来我们需要确保每一轮必赢或必输,这个很简单,因为本题的ai对手其实挺蠢的,而且我们还是执黑的先手
- 必赢:(5,5), (5,6), (5,7), (5,8), (5,9)
- 必输:(0,0), (0,2), (0,4), (0,6), (0,8), (0,10)
这样就可以写脚本利用漏洞取胜了
#exploit.py
#用法:python exploit.py <server_ip> <port>
#!/usr/bin/env python3
import socket
import struct
import sys
import re
import time
MAGIC = b'GAME'
win_moves = [(5,5), (5,6), (5,7), (5,8), (5,9)]
lose_moves = [(0,0), (0,2), (0,4), (0,6), (0,8), (0,10)]
class GameClient:
def __init__(self, host, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(30)
self.sock.connect((host, port))
def send_packet(self, cmd, data=b''):
packet = bytes([cmd]) + data
length = 9 + len(data)
msg = MAGIC + struct.pack('>I', length) + packet
self.sock.sendall(msg)
def recv_packet(self):
magic = self.sock.recv(4)
if magic != MAGIC:
return None, None
length_bytes = self.sock.recv(4)
length = struct.unpack('>I', length_bytes)[0]
remaining = length - 8
data = b''
while len(data) < remaining:
chunk = self.sock.recv(remaining - len(data))
if not chunk:
break
data += chunk
return data[0], data[1:].decode()
def cmd1_get_flag(self, player_name):
self.send_packet(1, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def cmd4_reset(self, player_name):
self.send_packet(4, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def cmd5_start_game(self, player_name):
self.send_packet(5, player_name.encode())
cmd, resp = self.recv_packet()
return cmd, resp
def cmd7_move(self, x, y):
self.send_packet(7, f"{x},{y}".encode())
cmd, resp = self.recv_packet()
return cmd, resp
def cmd8_resign(self):
self.send_packet(8, b'')
cmd, resp = self.recv_packet()
return cmd, resp
def get_player_info(self, player_name):
self.send_packet(2, player_name.encode())
cmd, resp = self.recv_packet()
return resp
def get_rank(self):
self.send_packet(3, b'')
cmd, resp = self.recv_packet()
return resp
def close(self):
self.sock.close()
def wait_for_match(client, player_name, target_score, max_retries=50):
for i in range(max_retries):
cmd, resp = client.cmd5_start_game(player_name)
match = re.search(r'Score=(\d+)', resp)
if match:
opp_score = int(match.group(1))
if opp_score == target_score:
print(f" [+] 找到目标对手,分数={target_score}")
return resp
else:
print(f" [-] 对手分数={opp_score},不匹配目标={target_score},重试...")
client.cmd8_resign()
time.sleep(0.3)
else:
print(f" [-] 匹配失败: {resp}")
print(f" [!] 达到最大重试次数({max_retries})")
return None
def win_game(client, moves):
for x, y in moves:
cmd, resp = client.cmd7_move(x, y)
if "Win" in resp or "Lose" in resp or "Draw" in resp:
return resp
return None
def lose_game(client, moves):
for x, y in moves:
cmd, resp = client.cmd7_move(x, y)
if "Win" in resp or "Lose" in resp or "Draw" in resp:
return resp
return None
def main():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <host> <port>")
sys.exit(1)
host, port = sys.argv[1], int(sys.argv[2])
player = "player"
client = GameClient(host, port)
print("[*] Step 1: Reset 挑战")
print(client.cmd4_reset(player))
time.sleep(0.5)
print("\n[*] Step 2: 赢10分对手")
wait_for_match(client, player, 10)
resp = win_game(client, win_moves)
print(f" 结果: {resp}")
print(client.get_player_info(player))
time.sleep(0.5)
print("\n[*] Step 3: 输20分对手")
wait_for_match(client, player, 20)
resp = lose_game(client, lose_moves)
print(f" 结果: {resp}")
print(client.get_player_info(player))
time.sleep(0.5)
print("\n[*] Step 4: 再输20分对手")
wait_for_match(client, player, 20)
resp = lose_game(client, lose_moves)
print(f" 结果: {resp}")
print(client.get_player_info(player))
time.sleep(0.5)
print("\n[*] Step 5: 输30分对手")
wait_for_match(client, player, 30)
resp = lose_game(client, lose_moves)
print(f" 结果: {resp}")
print(client.get_player_info(player))
time.sleep(0.5)
print("\n[*] Step 6: 输38分对手")
wait_for_match(client, player, 38)
resp = lose_game(client, lose_moves)
print(f" 结果: {resp}")
print(client.get_player_info(player))
time.sleep(0.5)
print("\n[*] Step 7: 输7分对手")
wait_for_match(client, player, 7)
resp = lose_game(client, lose_moves)
print(f" 结果: {resp}")
print(client.get_player_info(player))
print("\n[*] 查看排行榜")
print(client.get_rank())
print("\n[*] 获取FLAG")
print(client.cmd1_get_flag(player))
client.close()
if __name__ == "__main__":
main()
'''
输出示例:
[*] Step 1: Reset 挑战
Reset Score=50 Rank=1002
[*] Step 2: 赢10分对手
[-] 对手分数=30,不匹配目标=10,重试...
[-] 对手分数=40,不匹配目标=10,重试...
[-] 对手分数=30,不匹配目标=10,重试...
[+] 找到目标对手,分数=10
结果: Win Delta=3 Score=53 Rank=1002 OppDelta=-3 OppScore=7
OK Score=53 Rank=1002
[*] Step 3: 输20分对手
[+] 找到目标对手,分数=20
结果: Lose Delta=-16 Score=37 Rank=1004 OppDelta=16 OppScore=36
OK Score=37 Rank=1004
[*] Step 4: 再输20分对手
[-] 对手分数=50,不匹配目标=20,重试...
[-] 对手分数=36,不匹配目标=20,重试...
[-] 对手分数=50,不匹配目标=20,重试...
[-] 对手分数=40,不匹配目标=20,重试...
[-] 对手分数=30,不匹配目标=20,重试...
[-] 对手分数=30,不匹配目标=20,重试...
[-] 对手分数=36,不匹配目标=20,重试...
[-] 对手分数=7,不匹配目标=20,重试...
[-] 对手分数=7,不匹配目标=20,重试...
[-] 对手分数=50,不匹配目标=20,重试...
[-] 对手分数=30,不匹配目标=20,重试...
[+] 找到目标对手,分数=20
结果: Lose Delta=-14 Score=23 Rank=1007 OppDelta=14 OppScore=34
OK Score=23 Rank=1007
[*] Step 5: 输30分对手
[-] 对手分数=34,不匹配目标=30,重试...
[-] 对手分数=40,不匹配目标=30,重试...
[-] 对手分数=50,不匹配目标=30,重试...
[-] 对手分数=7,不匹配目标=30,重试...
[-] 对手分数=34,不匹配目标=30,重试...
[-] 对手分数=40,不匹配目标=30,重试...
[-] 对手分数=50,不匹配目标=30,重试...
[-] 对手分数=7,不匹配目标=30,重试...
[-] 对手分数=7,不匹配目标=30,重试...
[+] 找到目标对手,分数=30
结果: Lose Delta=-8 Score=15 Rank=1007 OppDelta=8 OppScore=38
OK Score=15 Rank=1007
[*] Step 6: 输38分对手
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=40,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=40,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=7,不匹配目标=38,重试...
[-] 对手分数=7,不匹配目标=38,重试...
[-] 对手分数=7,不匹配目标=38,重试...
[-] 对手分数=50,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=40,不匹配目标=38,重试...
[-] 对手分数=40,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=36,不匹配目标=38,重试...
[-] 对手分数=40,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[-] 对手分数=40,不匹配目标=38,重试...
[-] 对手分数=7,不匹配目标=38,重试...
[-] 对手分数=34,不匹配目标=38,重试...
[+] 找到目标对手,分数=38
结果: Lose Delta=-5 Score=10 Rank=1007 OppDelta=5 OppScore=43
OK Score=10 Rank=1007
[*] Step 7: 输7分对手
[-] 对手分数=43,不匹配目标=7,重试...
[-] 对手分数=36,不匹配目标=7,重试...
[-] 对手分数=40,不匹配目标=7,重试...
[-] 对手分数=40,不匹配目标=7,重试...
[-] 对手分数=43,不匹配目标=7,重试...
[-] 对手分数=50,不匹配目标=7,重试...
[-] 对手分数=34,不匹配目标=7,重试...
[-] 对手分数=50,不匹配目标=7,重试...
[+] 找到目标对手,分数=7
结果: Lose Delta=-11 Score=4294967295 Rank=1 OppDelta=11 OppScore=18
OK Score=4294967295 Rank=1
[*] 查看排行榜
Score=4294967295 Rank=1 Total=1008 Top=4294967295 Nearby: #1 player(4294967295) #2 星辰冰封(2000000) #3 星辰不语(4996) #4 浅笑听雨(4989) #5 北离堆叠(4989) #6 陌上眷恋(4972) #7 微茫交织(4971)
[*] 获取FLAG
NCTF{256cfe60-965d-4388-aff5-b95a1fc331ac}
'''
获得flag:
NCTF{256cfe60-965d-4388-aff5-b95a1fc331ac}