signin
准備
signin.exe:https://wwa.lanzous.com/inIQdec11zi
程序分析
可以判斷出,這個程序實際上是由Python打包成的可執行文件,且在運行這個程序時,在同目錄下產生了一個tmp.dll文件,猜測是程序調用某些函數的接口。
反編譯
使用archive_viewer.py反編譯為字節碼文件
python archive_viewer.py signin.exe
修補文件
55 0D 0D 0A 00 00 00 00 70 79 69 30 10 01 00 00
程序是在Python3.8環境下打包,因此我們需要在Python3.8下使用uncompyle6
uncompyle6 main.pyc > main.py
得到py文件
1 # uncompyle6 version 3.7.2 2 # Python bytecode 3.8 (3413) 3 # Decompiled from: Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)] 4 # Embedded file name: main.py 5 # Compiled at: 1995-09-28 00:18:56 6 # Size of source mod 2**32: 272 bytes 7 import sys 8 from PyQt5.QtCore import * 9 from PyQt5.QtWidgets import * 10 from signin import * 11 from mydata import strBase64 12 from ctypes import * 13 import _ctypes 14 from base64 import b64decode 15 import os 16 17 class AccountChecker: 18 19 def __init__(self): 20 self.dllname = './tmp.dll' 21 self.dll = self._AccountChecker__release_dll() 22 self.enc = self.dll.enc 23 self.enc.argtypes = (c_char_p, c_char_p, c_char_p, c_int) 24 self.enc.restype = c_int 25 self.accounts = {b'SCTFer': b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')} 26 self.try_times = 0 27 28 def __release_dll(self): 29 with open(self.dllname, 'wb') as (f): 30 f.write(b64decode(strBase64.encode('ascii'))) 31 return WinDLL(self.dllname) 32 33 def clean(self): 34 _ctypes.FreeLibrary(self.dll._handle) 35 if os.path.exists(self.dllname): 36 os.remove(self.dllname) 37 38 def _error(self, error_code): 39 errormsg = {0:'Unknown Error', 40 1:'Memory Error'} 41 QMessageBox.information(None, 'Error', errormsg[error_code], QMessageBox.Abort, QMessageBox.Abort) 42 sys.exit(1) 43 44 def __safe(self, username: bytes, password: bytes): 45 pwd_safe = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 46 status = self.enc(username, password, pwd_safe, len(pwd_safe)) 47 return (pwd_safe, status) 48 49 def check(self, username, password): 50 self.try_times += 1 51 if username not in self.accounts: 52 return False 53 encrypted_pwd, status = self._AccountChecker__safe(username, password) 54 if status == 1: 55 self._AccountChecker__error(1) 56 if encrypted_pwd != self.accounts[username]: 57 return False 58 self.try_times -= 1 59 return True 60 61 62 class SignInWnd(QMainWindow, Ui_QWidget): 63 64 def __init__(self, checker, parent=None): 65 super().__init__(parent) 66 self.checker = checker 67 self.setupUi(self) 68 self.PB_signin.clicked.connect(self.on_confirm_button_clicked) 69 70 @pyqtSlot() 71 def on_confirm_button_clicked(self): 72 username = bytes((self.LE_usrname.text()), encoding='ascii') 73 password = bytes((self.LE_pwd.text()), encoding='ascii') 74 if username == b'' or password == b'': 75 self.check_input_msgbox() 76 else: 77 self.msgbox(self.checker.check(username, password)) 78 79 def check_input_msgbox(self): 80 QMessageBox.information(None, 'Error', 'Check Your Input!', QMessageBox.Ok, QMessageBox.Ok) 81 82 def msgbox(self, status): 83 msg_ex = {0:'', 84 1:'', 85 2:"It's no big deal, try again!", 86 3:'Useful information is in the binary, guess what?'} 87 msg = 'Succeeded! Flag is your password' if status else 'Failed to sign in\n' + msg_ex[(self.checker.try_times % 4)] 88 QMessageBox.information(None, 'SCTF2020', msg, QMessageBox.Ok, QMessageBox.Ok) 89 90 91 if __name__ == '__main__': 92 app = QApplication(sys.argv) 93 checker = AccountChecker() 94 sign_in_wnd = SignInWnd(checker) 95 sign_in_wnd.show() 96 app.exec() 97 checker.clean() 98 sys.exit() 99 # okay decompiling main.pyc
代碼分析
通過代碼我們能夠了解到這些信息
1.
elf.dllname = './tmp.dll'
調用了tmp.dll文件作為接口。
2.
def __safe(self, username: bytes, password: bytes): pwd_safe = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' status = self.enc(username, password, pwd_safe, len(pwd_safe)) return (pwd_safe, status) def check(self, username, password): self.try_times += 1 if username not in self.accounts: return False encrypted_pwd, status = self._AccountChecker__safe(username, password) if status == 1: self._AccountChecker__error(1) if encrypted_pwd != self.accounts[username]: return False self.try_times -= 1 return True
self.accounts = {b'SCTFer': b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')}
調用tmp.dll文件中的enc函數,傳入username, password, pwd_safe, len(pwd_safe),實際就是將password加密后存儲到pwd_safe字節碼中。最后用pwd_safe與b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')比較,且我們能夠了解到用戶名應該是SCTFer,且最后返回的status一個為非1。
打開tmp.dll文件,找到enc函數
觀察代碼,實際操作可以分為兩部分,逆向分析
異或操作
第47~54行代碼實際上就是將Dst與用戶名循環異或,最后得到b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA'),因此我們只需要逆向異或就能得到加密后的Dst
from base64 import * username = "SCTFer" pwd_safe = b64decode('PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA') # print (len(pwd_safe)) num = ["%02x" % x for x in pwd_safe] hex_num = [int(x, 16) for x in num] print(num) # print (len(num)) for i in range(32): hex_num[i] ^= ord(username[i % len(username)]) # print (hex_num) hex_nums = bytes.fromhex(''.join([hex(x)[2:].rjust(2, '0') for x in hex_num])) print (hex_nums)
得到
b'o\xf2\x96\xfd\x82\x9c\xde\xb52v\x86yK3\xe6\x1f\x06\xd8\xb7=\x13J\xb8\xe3\xb52\xb3\xd38\x86\x10\x02\x00'
加密操作
每次傳入了8字節數據進行加密(總共64字節),打開sub_180011311函數
仔細觀察代碼,實際上這部分代碼是使用CRC32的查表法,對數據進行加密。
加密原理實際上就是CRC32算法---輸入一組長度32的字符串,每8個字節分為1組,共4組。對每一組取首位,判斷正負。正值,左移一位;負值,左移一位,再異或0xB0004B7679FA26B3。重復判斷操作64次,得到查表法所用的表。
因此我們只需要將整個加密過程逆向操作得到查表法的表,再進行CRC32計算,就能得到輸入。
secret = [] # for i in range(4): # secret.append(int(hex_nums[i*8:(i + 1) * 8][::-1].hex(),16)) for i in range(4): secret.append(int.from_bytes(hex_nums[i*8:(i + 1) * 8], byteorder="little")) print (secret) key = 0xB0004B7679FA26B3 flag = "" for s in secret: for i in range(64): sign = s & 1 if sign == 1: s ^= key s //= 2 if sign == 1: s |= 0x8000000000000000 print(hex(s)) j = 0 while j < 8: flag += chr(s&0xFF) s >>= 8 j += 1 print(flag)
因為計算機中采用小端排序,因此需要注意分組倒序。得到
腳本
from base64 import * username = "SCTFer" pwd_safe = b64decode('PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA') # print (len(pwd_safe)) num = ["%02x" % x for x in pwd_safe] hex_num = [int(x, 16) for x in num] print(num) # print (len(num)) for i in range(32): hex_num[i] ^= ord(username[i % len(username)]) # print (hex_num) hex_nums = bytes.fromhex(''.join([hex(x)[2:].rjust(2, '0') for x in hex_num])) print (hex_nums) secret = [] # for i in range(4): # secret.append(int(hex_nums[i*8:(i + 1) * 8][::-1].hex(),16)) for i in range(4): secret.append(int.from_bytes(hex_nums[i*8:(i + 1) * 8], byteorder="little")) print (secret) key = 0xB0004B7679FA26B3 flag = "" for s in secret: for i in range(64): sign = s & 1 if sign == 1: s ^= key s //= 2 if sign == 1: s |= 0x8000000000000000 print(hex(s)) j = 0 while j < 8: flag += chr(s&0xFF) s >>= 8 j += 1 print(flag)
get flag!
username:SCTFer
password:SCTF{We1c0m3_To_Sctf_2020_re_!!}
get_up
下載:https://wwa.lanzous.com/i6fDyedivgh
代碼分析
代碼解析
beg_adr = 0x405000 dst = "sycsyc" for i in range(0,0x200,16): for j in range(16): PatchByte(beg_adr+i+j,Byte(beg_adr+i+j)^ord(dst[j%6]))
beg_adr = 0x404000 dst = "SCTF{" for i in range(16,96): PatchByte(beg_adr+i,Byte(beg_adr+i)^ord(dst[i%5]))
解密
#include<stdio.h> #include<stdlib.h> #include<string.h> typedef unsigned longULONG; #pragma warning(disable:4996) /*初始化函數*/ void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len) { int i = 0, j = 0; char k[256] = { 0 }; unsigned char tmp = 0; for (i = 0; i < 256; i++) { s[i] = i; k[i] = key[i % Len]; } for (i = 0; i < 256; i++) { j = (j + s[i] + k[i]) % 256; tmp = s[i]; s[i] = s[j]; // 交換s[i]和s[j] s[j] = tmp; } } /*加解密*/ void rc4_crypt(unsigned char* s, unsigned char* Data, unsigned long Len) { int i = 0, j = 0, t = 0; unsigned long k = 0; unsigned char tmp; for (k = 0; k < Len; k++) { i = (i + 1) % 256; j = (j + s[i]) % 256; tmp = s[i]; s[i] = s[j]; // 交換s[x]和s[y] s[j] = tmp; t = (s[i] + s[j]) % 256; Data[k] ^= s[t]; } } int main() { unsigned char s[256] = { 0 }, s2[256] = { 0 }; // S-box char key[256] = { "syclover" }; char pData[512] = { 0 }; int v12[] = { 128,85,126,45,209,9,37,171,60,86,149,196,54,19,237,114,36,147,178,200,69,236,22,107,103,29,249,163,150,217 }; int i; //char m[32] = { 0 }; for (int i = 0; i < 30; ++i) { pData[i] = (char)v12[i]; } unsigned long len = strlen(pData); printf("pData=%s\n", pData); printf("key=%s,length=%d\n\n", key, strlen(key)); rc4_init(s, (unsigned char*)key, strlen(key)); // 已經完成了初始化 printf("完成對S[i]的初始化,如下:\n\n"); for (i = 0; i < 256; i++) { printf("%02X", s[i]); if (i && (i + 1) % 16 == 0)putchar('\n'); } printf("\n\n"); for (i = 0; i < 256; i++) // 用s2[i]暫時保留經過初始化的s[i],很重要的!!! { s2[i] = s[i]; } printf("已經初始化,現在解密:\n\n"); printf("len = %d\n", len); rc4_crypt(s, (unsigned char*)pData, len); printf("pData=%s\n\n", pData); system("PAUSE"); return 0; }
get flag!
SCTF{zzz~(|3[___]_rc4_5o_e4sy}
Can you hear
robot36 app:https://wwa.lanzous.com/it7Zsedix5i
音頻是SSTV,用軟件Robot36接受音頻中的數據
doudizhu
只要將手里牌出完,就能獲得flag