欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

【SCTF2020】signin WriteUp

程序员文章站 2022-04-19 12:37:10
一道SCTF的逆向题,做了很久很久才做起,唉…下载附件,打开发现是一个GUI程序IDA打开,根据经验分析出这是一个pyqt程序,于是用解包脚本处理这个exe脚本Github:pyinstxtractor执行脚本后得到了一个解包后的文件夹在这里我们重点关注main和struct文件用WinHex打开,发现main其实是一个没有pyc文件头的pyc文件,而struct中有文件头于是我们把struct的前16个字节添加到main的最前面,并改名为main.pyc,得到如下文...

一道SCTF的逆向题,做了很久很久才做起,唉…
【SCTF2020】signin WriteUp
下载附件,打开发现是一个GUI程序
【SCTF2020】signin WriteUp
IDA打开,根据经验分析出这是一个pyqt程序,于是用解包脚本处理这个exe
脚本Github:pyinstxtractor
执行脚本后得到了一个解包后的文件夹
【SCTF2020】signin WriteUp
在这里我们重点关注main和struct文件
【SCTF2020】signin WriteUp
【SCTF2020】signin WriteUp
用WinHex打开,发现main其实是一个没有pyc文件头的pyc文件,而struct中有文件头
【SCTF2020】signin WriteUp
【SCTF2020】signin WriteUp
于是我们把struct的前16个字节添加到main的最前面,并改名为main.pyc,得到如下文件
【SCTF2020】signin WriteUp
然后命令行执行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打开分析一波~
【SCTF2020】signin WriteUp
【SCTF2020】signin WriteUp
直接就能在函数窗口中找到enc函数,内部是调用的enc_0函数,这里用n更改了几个变量名方便理解加密时的算法
大概逻辑是,把32位的密码分成4组,每组8字节,memcpy给一个__int64,然后对这个数进行某种操作,再把这个数memcpy到输出的对应位置,4组全部处理完后,把输出和用户名进行异或,最后得出真正的输出
【SCTF2020】signin WriteUp
具体的对数的操作逻辑是,循环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_!!}

本文地址:https://blog.csdn.net/tqydyqt/article/details/107152507