@@ -339,7 +339,7 @@ WORD Discord::getDiscordRPCPort() {
339339 WORD workingPort = 0 ;
340340 for (WORD port = 6463 ; port <= 6472 ; port++) {
341341 try {
342- std::string out;
342+ secure_string out;
343343 cURL_get (sf () << " http://127.0.0.1:" << port, nullptr , out);
344344 json outJson = json::parse (out);
345345 if (outJson[" code" ].get <int >() == 0 && outJson[" message" ].get <std::string>() == " Not Found" ) {
@@ -366,8 +366,8 @@ bool Discord::AcceptHandoff(const std::string& port, const std::string& key, con
366366 json handoffData;
367367 handoffData[" key" ] = key;
368368
369- std::string handoffToken;
370- cURL_post (" https://discord.com/api/v8/auth/handoff" , chunk, handoffData.dump (), handoffToken);
369+ secure_string handoffToken;
370+ cURL_post (" https://discord.com/api/v8/auth/handoff" , chunk, handoffData.dump (). c_str () , handoffToken);
371371 handoffToken = json::parse (handoffToken)[" handoff_token" ].get <std::string>();
372372
373373 chunk = NULL ;// Gets freed in cURL_post
@@ -381,8 +381,8 @@ bool Discord::AcceptHandoff(const std::string& port, const std::string& key, con
381381 handoffData[" args" ][" handoffToken" ] = handoffToken;
382382 handoffData[" nonce" ] = getRandomUUID ();
383383
384- std::string handoffRPCOut;
385- cURL_post (" http://127.0.0.1:" + port + " /rpc?v=1" , chunk, handoffData.dump (), handoffRPCOut);
384+ secure_string handoffRPCOut;
385+ cURL_post (" http://127.0.0.1:" + port + " /rpc?v=1" , chunk, handoffData.dump (). c_str () , handoffRPCOut);
386386 json rpcResp = json::parse (handoffRPCOut);
387387 if (rpcResp.contains (" code" ) && rpcResp.contains (" message" )) {// Error!
388388 throw std::runtime_error (sf () << " Error " << rpcResp[" code" ].get <int >() << " : " << rpcResp[" message" ].get <std::string>());
@@ -397,30 +397,35 @@ bool Discord::AcceptHandoff(const std::string& port, const std::string& key, con
397397 return true ;
398398}
399399
400- std::string Discord::getUserInfo (const secure_string& token) {
400+ DiscordUserInfo Discord::getUserInfo (const secure_string& token) {
401401 using nlohmann::json;
402402
403403 try {
404404 struct curl_slist * chunk = NULL ;
405405 chunk = curl_slist_append (chunk, (" Authorization: " + token).c_str ());
406406 chunk = curl_slist_append (chunk, " Content-Type: application/json" );
407407
408- std::string userinfo;
408+ secure_string userinfo;
409409 cURL_get (" https://discordapp.com/api/v6/users/@me" , chunk, userinfo);
410410
411411 json userinfoJSON = json::parse (userinfo);
412412
413413 if (userinfoJSON.contains (" message" ))
414414 throw std::runtime_error (userinfoJSON[" message" ].get <std::string>());
415415
416- return sf () << userinfoJSON[" username" ].get <std::string>() << " #" << userinfoJSON[" discriminator" ].get <std::string>()
417- << " (" << userinfoJSON[" id" ].get <std::string>() << " )" ;
416+ return {
417+ userinfoJSON[" username" ].get <std::string>() + " #" + userinfoJSON[" discriminator" ].get <std::string>(),
418+ userinfoJSON[" username" ].get <std::string>(),
419+ userinfoJSON[" discriminator" ].get <std::string>(),
420+ userinfoJSON[" id" ].get <std::string>(),
421+ userinfoJSON[" mfa_enabled" ].get <bool >()
422+ };
418423 }
419424 catch (const std::exception& e) {
420425 g_logger.error (sf () << " Failed getUserInfo : " << e.what ());
421426 }
422427
423- return std::string ();
428+ return DiscordUserInfo ();
424429}
425430
426431secure_string Discord::getStoredToken (bool verify) {
@@ -450,7 +455,7 @@ secure_string Discord::getStoredToken(bool verify) {
450455 secure_string match (matches[1 ].str ());
451456 if (results.find (match) == results.end ()) {// A new token! let's add it to the list
452457 if (std::find (invalids.begin (), invalids.end (), match) == invalids.end ()) {// If not invalid
453- if (verify && getUserInfo (match).empty ())
458+ if (verify && getUserInfo (match).id . empty ())
454459 invalids.push_back (match);
455460 else
456461 results.insert ({ match, 1 });
@@ -480,6 +485,102 @@ secure_string Discord::getStoredToken(bool verify) {
480485 return " " ;
481486}
482487
488+ bool Discord::changePassword (
489+ const secure_string& token,
490+ const secure_string& currentPassword,
491+ const secure_string& newPassword,
492+ const secure_string& mfaCode,
493+ secure_string& error) {
494+
495+ secure_string patchData;// We're avoiding the json lib to not keep the data in memory
496+
497+ auto jsonEscape = [](const secure_string& data) {
498+ secure_string out;
499+ out.reserve (data.size ());
500+
501+ for (const char c : data) {
502+ switch (c) {
503+ case ' \b ' : out += " \\ b" ; break ;
504+ case ' \f ' : out += " \\ f" ; break ;
505+ case ' \n ' : out += " \\ n" ; break ;
506+ case ' \r ' : out += " \\ r" ; break ;
507+ case ' \t ' : out += " \\ t" ; break ;
508+ case ' \" ' : out += " \\\" " ; break ;
509+ case ' \\ ' : out += " \\\\ " ; break ;
510+ default : out += c; break ;
511+ }
512+ }
513+
514+ return out;
515+ };
516+
517+ patchData += " {\" password\" :\" " + jsonEscape (currentPassword) +
518+ " \" ,\" new_password\" :\" " + jsonEscape (newPassword) + " \" " ;
519+
520+ if (!mfaCode.empty ()) {
521+ patchData += " ,\" code\" :\" " + jsonEscape (mfaCode) + " \" " ;
522+ }
523+
524+ patchData += " }" ;
525+
526+ try {
527+ struct curl_slist * chunk = NULL ;
528+ chunk = curl_slist_append (chunk, (" Authorization: " + token).c_str ());
529+ chunk = curl_slist_append (chunk, " Content-Type: application/json" );
530+ chunk = curl_slist_append (chunk, " Accept-Language: en-US" );
531+
532+ secure_string output;
533+ cURL_post (" https://discord.com/api/v9/users/@me" , chunk, patchData, output, " PATCH" );
534+
535+ // To avoid using json or regex (insecure)
536+ auto getStringKey = [](const secure_string& data, secure_string key) {
537+ key = " \" " + key + " \" : \" " ;
538+ size_t pos = data.find (key);
539+ if (pos == secure_string::npos) return secure_string ();
540+
541+ size_t endPos = pos + key.size ();
542+ while (true ) {
543+ endPos = data.find (" \" " , endPos);
544+ if (endPos == secure_string::npos) return secure_string ();
545+ if (data[endPos - 1 ] == ' \\ ' ) {
546+ ++endPos;
547+ continue ;
548+ }
549+ break ;
550+ }
551+
552+ return data.substr (pos + key.size (), endPos - pos - key.size ());
553+ };
554+
555+ // Requires 2FA!
556+ if (auto pos = output.find (" \" code\" : 60008" ); pos != secure_string::npos) {
557+ error = " 2FA" ;
558+ return false ;
559+ }
560+
561+ // Other errors
562+ if (secure_string message = getStringKey (output, " message" ); !message.empty ()) {
563+ error = message;
564+ return false ;
565+ }
566+
567+ // Success!
568+ if (secure_string token = getStringKey (output, " token" ); !token.empty ()) {
569+ error = token;
570+ return true ;
571+ }
572+
573+ // Unknown error
574+ error = " Unknown error" ;
575+ return false ;
576+ }
577+ catch (std::exception& e) {
578+ g_logger.error (sf () << " Failed changePassword : " << e.what ());
579+ error = e.what ();
580+ return false ;
581+ }
582+ }
583+
483584// Credit : https://guidedhacking.com/threads/how-to-pass-multiple-arguments-with-createremotethread-to-injected-dll.15373/
484585
485586uintptr_t GetModuleBaseAddress (DWORD procId, const char * modName)
0 commit comments