一道SCTF的逆向題,做了很久很久才做起,唉…
下載附件,打開發現是一個GUI程序
IDA打開,根據經驗分析出這是一個pyqt程序,於是用解包腳本處理這個exe
腳本Github:pyinstxtractor
執行腳本後得到了一個解包後的文件夾
在這裏我們重點關注main和struct文件
用WinHex打開,發現main其實是一個沒有pyc文件頭的pyc文件,而struct中有文件頭
於是我們把struct的前16個字節添加到main的最前面,並改名爲main.pyc,得到如下文件
然後命令行執行pip3 install uncompyle6、uncompyle6.exe main.pyc > main.py,得到python源碼
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from signin import *
from mydata import strBase64
from ctypes import *
import _ctypes
from base64 import b64decode
import os
class AccountChecker:
def __init__(self):
self.dllname = './tmp.dll'
self.dll = self._AccountChecker__release_dll()
self.enc = self.dll.enc
self.enc.argtypes = (c_char_p, c_char_p, c_char_p, c_int)
self.enc.restype = c_int
self.accounts = {b'SCTFer': b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')}
self.try_times = 0
def __release_dll(self):
with open(self.dllname, 'wb') as (f):
f.write(b64decode(strBase64.encode('ascii')))
return WinDLL(self.dllname)
def clean(self):
_ctypes.FreeLibrary(self.dll._handle)
if os.path.exists(self.dllname):
os.remove(self.dllname)
def _error(self, error_code):
errormsg = {0:'Unknown Error',
1:'Memory Error'}
QMessageBox.information(None, 'Error', errormsg[error_code], QMessageBox.Abort, QMessageBox.Abort)
sys.exit(1)
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
class SignInWnd(QMainWindow, Ui_QWidget):
def __init__(self, checker, parent=None):
super().__init__(parent)
self.checker = checker
self.setupUi(self)
self.PB_signin.clicked.connect(self.on_confirm_button_clicked)
@pyqtSlot()
def on_confirm_button_clicked(self):
username = bytes((self.LE_usrname.text()), encoding='ascii')
password = bytes((self.LE_pwd.text()), encoding='ascii')
if username == b'' or password == b'':
self.check_input_msgbox()
else:
self.msgbox(self.checker.check(username, password))
def check_input_msgbox(self):
QMessageBox.information(None, 'Error', 'Check Your Input!', QMessageBox.Ok, QMessageBox.Ok)
def msgbox(self, status):
msg_ex = {0:'',
1:'',
2:"It's no big deal, try again!",
3:'Useful information is in the binary, guess what?'}
msg = 'Succeeded! Flag is your password' if status else 'Failed to sign in\n' + msg_ex[(self.checker.try_times % 4)]
QMessageBox.information(None, 'SCTF2020', msg, QMessageBox.Ok, QMessageBox.Ok)
if __name__ == '__main__':
app = QApplication(sys.argv)
checker = AccountChecker()
sign_in_wnd = SignInWnd(checker)
sign_in_wnd.show()
app.exec()
checker.clean()
sys.exit()
從源碼中可以讀出來,用戶名爲SCTFer,密碼的計算方式是釋放一個tmp.dll並調用其中的enc函數,通過傳入用戶名、密碼來計算一個輸出,並把輸出和base64解碼以後的數據對比
我們打開程序就能在相同目錄下找到tmp.dll,用IDA打開分析一波~
直接就能在函數窗口中找到enc函數,內部是調用的enc_0函數,這裏用n更改了幾個變量名方便理解加密時的算法
大概邏輯是,把32位的密碼分成4組,每組8字節,memcpy給一個__int64,然後對這個數進行某種操作,再把這個數memcpy到輸出的對應位置,4組全部處理完後,把輸出和用戶名進行異或,最後得出真正的輸出
具體的對數的操作邏輯是,循環64次,每次都把這個數乘以2,如果之前這個數是負數的話,還要再異或一個數
理清加密邏輯倒是沒什麼難度,但是對我來說寫逆向算法卡了老久時間。。。
最後用c++寫出的逆向腳本如下:
#include <iostream>
using namespace std;
int main()
{
unsigned char flag[] = { 0x6f,0xf2,0x96,0xfd,0x82,0x9c,0xde,0xb5,0x32,0x76,0x86,0x79,0x4b,0x33,0xe6,0x1f,0x6,0xd8,0xb7,0x3d,0x13,0x4a,0xb8,0xe3,0xb5,0x32,0xb3,0xd3,0x38,0x86,0x10,0x2,0 };
__int64 Dst;
for (size_t j = 0; j < 4; j++) {
memcpy_s(&Dst, 8, flag + j * 8, 8);
for (size_t i = 0; i < 64; i++) {
if (Dst & 1)
if (Dst < 0) {
Dst ^= 0xB0004B7679FA26B3ui64;
Dst /= 2;
Dst += 0x8000000000000000;
}
else {
Dst ^= 0xB0004B7679FA26B3ui64;
Dst /= 2;
}
else
if (Dst < 0) {
Dst /= 2;
Dst += 0x8000000000000000;
}
else
Dst /= 2;
}
memcpy(flag + j * 8, &Dst, 8);
}
cout << flag;
}
成功計算出flag!
SCTF{We1c0m3_To_Sctf_2020_re_!!}