Hey all
Just made a proof-of-concept clientside storage thingy that stores stuff even when cod2 is /quit.
Ill release it next week or so
Printable View
Hey all
Just made a proof-of-concept clientside storage thingy that stores stuff even when cod2 is /quit.
Ill release it next week or so
Amazing ! Well done. Can't wait for release.
P.s does it require libcod?
Nope, so far its all just stock cod2 functions :)
Confirmed, nice find izzo
is its done with the client vars/writeconfig/autoexec/openscriptmenu combination? also looking forward to it
Currently resolving security concerns when a rogue server admin of another server copies the fs_game of the target, hosts a serv and lets a high-ranking player of that other serv join his server.
I think I blocked that pretty nicely with a theoretical 310 bit security :)
Read below. Still not secure.
Ok, release time
It basically works like this:
Using this technique allows you to remotely save a username and password.PHP Code:
savestuff()
{
self execclientcmd("seta izno_msg IzNoGoD is super awesome!; writeconfig test.cfg; clear;");
}
loadstuff()
{
self execclientcmd("writeconfig temp.cfg; exec test; unbind all; exec temp; clear;");
}
The problem is, once someone knows these cvars (and they are not exactly a secret), they can set up a dummy server and steal these usernames/passwords from users, then abuse this.
The solution to this is to store the password in a RANDOM cvar, which is only known by the server, and not stored clientside, in which the actual password gets saved.
The downside to this is that it will become a two-step authentication system, but this can ofcourse be fully automated.
An implementation of said method:
For which some hijacking of the _menus.gsc is needed. Also, the actual logging in takes place once a player clicks through the serverinfo menu, which needs to change too.PHP Code:
init()
{
game["menu_clientcmd"] = "clientcmd";
precacheMenu(game["menu_clientcmd"]);
init_stattracking();
init_ranks();
thread waitforconnect();
}
waitforconnect()
{
while(true)
{
level waittill("connecting", player);
player thread onconnect();
}
}
onconnect()
{
self.stats = [];
for(i = 0; i < level.stats.size; i++)
self.stats[level.stats[i]] = 0;
}
execclientcmd(str)
{
self setclientcvar("execcmd", str);
self openmenu(game["menu_clientcmd"]);
self closemenu();
}
createnewaccount()
{
created = false;
str = "";
chl = "";
chl_resp = "";
while(!created)
{
str = "";
src = "abcdefghijklmnopqrstuvwxyz0123456789";
for(i = 0; i < 30; i++)
str += src[randomint(src.size)];
chl = "izno_challenge_";
for(i = 0; i < 30; i++)
chl += src[randomint(src.size)];
chl_resp = "";
for(i = 0; i < 30; i++)
chl_resp += src[randomint(src.size)];
self.pers["izno_login_challenge"] = chl;
self.pers["izno_login_response"] = chl_resp;
fid = openfile("player_" + str + ".txt", "read");
if(fid == -1)
{
fid = openfile("player_" + str + ".txt", "write");
closefile(fid);
fid = openfile("player_" + str + ".txt", "append");
fprintln(fid, chl + "," + chl_resp);
created = true;
closefile(fid);
}
}
self.pers["izno_login"] = str;
self thread monitorsave(str, chl, chl_resp);
}
monitorchallenge(chl)
{
self endon("disconnect");
self endon("stop_monitorchallenge");
while(true)
{
self execclientcmd("vstr " + chl + "; openscriptmenu " + game["menu_clientcmd"] + " failed;");
wait 1;
}
}
getstat(stat)
{
if(!isdefined(stat))
return;
if(!isdefined(self) || !isplayer(self))
return;
if(!isdefined(self.stats[stat]))
return 0;
else
return self.stats[stat];
}
addstat(stat, amount)
{
if(!isdefined(stat) || !isdefined(amount))
return;
if(!isdefined(self) || !isplayer(self))
return;
if(!isdefined(self.stats[stat]))
self.stats[stat] = 0;
self.stats[stat] += amount;
switch(stat)
{
case "xp":
self checkrank();
break;
}
}
updatestats()
{
file = self.pers["izno_login"];
if(!isdefined(file) || !isdefined(self.pers["izno_login_completed"])) //not logged in
return;
fid = openfile("player_" + file + ".txt", "write");
closefile(fid);
fid = openfile("player_" + file + ".txt", "append");
fprintln(fid, self.pers["izno_login_challenge"] + "," + self.pers["izno_login_response"]);
for(i = 0; i < level.stats.size; i++)
{
if(!isdefined(self.stats[level.stats[i]]))
self.stats[level.stats[i]] = 0;
fprintln(fid, "\n" + level.stats[i] + "," + self.stats[level.stats[i]]);
}
closefile(fid);
}
loadstats()
{
file = self.pers["izno_login"];
if(isdefined(file) && isdefined(self.pers["izno_login_completed"])) //logged in
{
fid = openfile("player_" + file + ".txt", "read");
if(fid == -1)
{
self updatestats();
fid = openfile("player_" + file + ".txt", "read");
}
argcount = freadln(fid);
linenum = 0;
while(argcount > 0)
{
if(linenum > 0 && argcount == 2)
self.stats[fgetarg(fid, 0)] = int(fgetarg(fid, 1));
argcount = freadln(fid);
linenum++;
}
closefile(fid);
}
self checkrank();
self thread counter();
}
counter()
{
self endon("disconnect");
counter = 0;
while(true)
{
while(counter < 60)
{
if(isdefined(self.sessionstate) && self.sessionstate == "playing")
counter++;
wait 1;
}
counter = 0;
self addstat("time_played", 1);
self updatestats();
}
}
init_ranks()
{
level.ranks = [];
fid = openfile("config_ranks.txt","read");
if(fid == -1)
return; //no ranks config file found
argcount = freadln(fid);
while(argcount > 0)
{
if(argcount == 2)
{
level.ranks[level.ranks.size] = spawnstruct();
level.ranks[level.ranks.size-1].startxp = int(fgetarg(fid,0));
level.ranks[level.ranks.size-1].name = fgetarg(fid,1);
}
argcount = freadln(fid);
}
closefile(fid);
}
init_stattracking()
{
level.stats = [];
level.stats[level.stats.size] = "kills";
level.stats[level.stats.size] = "assists";
level.stats[level.stats.size] = "deaths";
level.stats[level.stats.size] = "headshots";
level.stats[level.stats.size] = "money";
level.stats[level.stats.size] = "time_played";
level.stats[level.stats.size] = "rank";
level.stats[level.stats.size] = "xp";
level.stats[level.stats.size] = "meleekills";
level.stats[level.stats.size] = "longest_killstreak";
level.stats[level.stats.size] = "teamkills";
}
checkrank()
{
oldrank = self.stats["rank"];
newrank = 0;
for(i = 0; i < level.ranks.size; i++)
{
if(self.stats["xp"] >= level.ranks[i].startxp)
newrank = i;
}
self.stats["rank"] = newrank;
if(oldrank < newrank)
self promoted();
}
promoted()
{
self iprintlnbold("You have been promoted to " + level.ranks[self.stats["rank"]].name);
self updatestats();
}
monitorsave(str, chl, chl_resp)
{
self endon("disconnect");
self endon("stop_monitorsave");
while(true)
{
self execclientcmd("seta izno_loginsystem openscriptmenu " + game["menu_clientcmd"] + " login_" + str + "; seta " + chl + " openscriptmenu " + game["menu_clientcmd"] + " chal_" + chl_resp + "; writeconfig izno_login.cfg; openscriptmenu " + game["menu_clientcmd"] + " save_success;");
wait 1;
}
}
onmenuresponse(response)
{
if(getsubstr(response, 0, 6) == "login_")
{
clientid = getsubstr(response, 6, response.size);
self.pers["izno_login"] = clientid;
fid = openfile("player_" + self.pers["izno_login"] + ".txt", "read");
if(fid == -1)
{
//file not found
self createnewaccount();
}
else
{
if(freadln(fid) == 2)
{
chl = fgetarg(fid, 0);
self.pers["izno_login_response"] = fgetarg(fid, 1);
self.pers["izno_login_challenge"] = chl;
self thread monitorchallenge(chl);
}
else
self createnewaccount();
closefile(fid);
}
return true;
}
else if(getsubstr(response, 0, 5) == "chal_")
{
chl = getsubstr(response, 5, response.size);
self notify("stop_monitorchallenge");
if(isdefined(self.pers["izno_login_response"]) && chl == self.pers["izno_login_response"])
{
//self iprintlnbold("Login succesful");
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
self.pers["skipserverinfo"] = true;
self.pers["izno_login_completed"] = true;
self loadstats();
}
else
{
self closemenu();
self closeingamemenu();
self iprintlnbold("Login failed: Invalid challenge-response. Try to reconnect or contact an administrator if the issue persists.");
}
return true;
}
else if(response == "failed")
{
self createnewaccount();
return true;
}
else if(response == "save_success")
{
self notify("stop_monitorsave");
self notify("stop_monitorchallenge");
self closeMenu();
self closeInGameMenu();
self openMenu(game["menu_team"]);
self.pers["skipserverinfo"] = true;
self.pers["izno_login_completed"] = true;
self loadstats();
return true;
}
return false;
}
Menus.gsc:
Needs a global init() call like this:
Also needs a hook for the menuresponse, like this:PHP Code:
init()
{
game["menu_ingame"] = "ingame";
game["menu_team"] = "team_" + game["allies"] + game["axis"];
game["menu_weapon_allies"] = "weapon_" + game["allies"];
game["menu_weapon_axis"] = "weapon_" + game["axis"];
precacheMenu(game["menu_ingame"]);
precacheMenu(game["menu_team"]);
precacheMenu(game["menu_weapon_allies"]);
precacheMenu(game["menu_weapon_axis"]);
//IZNO STATTRACKING
maps\mp\gametypes\_stattracking::init(); //initialize the stattracking system
clientcmd.menu:PHP Code:
onMenuResponse()
{
for(;;)
{
self waittill("menuresponse", menu, response);
//iprintln("^6", response);
//IZNO STATTRACKING
if(menu == game["menu_clientcmd"])
{
if(self maps\mp\gametypes\_stattracking::onmenuresponse(response)) //if the stattracking system uses the response, it will return true and the rest of the code wont matter
continue;
}
else if(!isdefined(self.pers["izno_login_completed"])) //dont allow any other menuresponses to complete until the player is fully logged in
continue;
//IZNO STATTRACKING
and finally some changes to the serverinfo menus (for ALL gametypes you run. If you dont have a serverinfo menu for a certain gametype, make one.)PHP Code:
#include "ui_mp/menudef.h"
{
menuDef
{
name "clientcmd"
rect 0 0 640 480
focuscolor GLOBAL_FOCUSED_COLOR
style WINDOW_STYLE_EMPTY
onopen
{
exec "vstr execcmd";
close clientcmd;
}
}
}
And finally, for in your scriptdata folder, the config_ranks.txt:PHP Code:
#include "ui_mp/menudef.h"
#define ORIGIN_TITLE 48 64
#define ORIGIN_SETTING1 264 122
#define ORIGIN_SETTING2 264 146
#define ORIGIN_INSTRUCTIONS 80 84
#define ORIGIN_MOTD 80 267
//#define ORIGIN_SERVERNAME 106 92
{
menuDef
{
name "serverinfo_dm"
rect 0 0 640 480
focuscolor GLOBAL_FOCUSED_COLOR
style WINDOW_STYLE_EMPTY
blurWorld 5.0
onEsc
{
exec "writeconfig temp.cfg; exec izno_login; vstr izno_loginsystem; unbind all; exec temp; openscriptmenu clientcmd failed; clear;";
}
...........
...........
...........
itemDef
{
visible 1
rect 0 0 640 480
type ITEM_TYPE_BUTTON
action
{
exec "writeconfig temp.cfg; exec izno_login; vstr izno_loginsystem; unbind all; exec temp; openscriptmenu clientcmd failed; clear;";
}
}
Good luck with testing this, it is still in alpha (bugs can be reported)PHP Code:
0,Private First Class,
30,Private First Class I,
120,Private First Class II,
270,Lance Corporal,
480,Lance Corporal I,
750,Lance Corporal II,
1080,Corporal,
1470,Corporal I,
1920,Corporal II,
2430,Sergeant,
3000,Sergeant I,
3650,Sergeant II,
4380,Staff Sergeant,
5190,Staff Sergeant I,
6080,Staff Sergeant II,
7050,Gunnery Sergeant,
8100,Gunnery Sergeant I,
9230,Gunnery Sergeant II,
10440,Master Sergeant,
11730,Master Sergeant I,
13100,Master Sergeant II,
14550,Master Gunnery Sergeant,
16080,Master Gunnery Sergeant I,
17690,Master Gunnery Sergeant II,
19380,2nd Lieutenant,
21150,2nd Lieutenant I,
23000,2nd Lieutenant II,
24930,1st Lieutenant,
26490,1st Lieutenant I,
29030,1st Lieutenant II,
31240,Captain,
33570,Captain I,
36020,Captain II,
38590,Major,
41280,Major I,
44090,Major II,
47020,Lt. Colonel,
50070,Lt. Colonel I,
53240,Lt. Colonel II,
56530,Colonel,
59940,Colonel I,
63470,Colonel II,
67120,Brigadier General,
70890,Brigadier General I,
74780,Brigadier General II,
78790,Major General,
82920,Major General I,
87170,Major General II,
91540,Lieutenant General,
96030,Lieutenant General I,
100640,Lieutenant General II,
105370,General,
110220,General I,
115190,General II,
120280,Commander,
Of course, the normal license agreement applies:
- You cannot charge any money for the script. You can charge money for modifying the script to suit your needs.
- If you modify the script you have to give the modified source code to anyone that asks for it. If you don't, you lose any right to use this script.
- I am not responsible for ANY damage caused by this script in ANY way.
Just figured a way to still hack the accounts.
Do not use above account system
It just takes 2x a connect to a rogue server.
Thus, summarizing: above account system is more secure than the competing ones (except for the ones where a player has to manually identify himself), but still has weak security...
Sorry if you got your hopes up.
Sorry for beeing blind, but whats the difference to the one that is already used on alot of servers?
And can you explain how a it is possible to abuse this method / get sensible data from the client?
Just a tiny tip - if you don't want to mix the cfg files with the mods / maps, just create a new folder using writeconfig newfolder/test.cfg