zoukankan      html  css  js  c++  java
  • 2020 SCTF 部分WriteUp

    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'x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
    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
    ' + 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'x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
            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'oxf2x96xfdx82x9cxdexb52vx86yK3xe6x1fx06xd8xb7=x13Jxb8xe3xb52xb3xd38x86x10x02x00'

     

    加密操作

    每次传入了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

    代码分析

    这是实际就是在说明输入字符串长度不大于6,且通过sub_401DF0返回1

    返回1,实际就是满足加密后的Dst与32c1d123c193aecc4280a5d7925a2504相同,实际是MD5加密,得到输入为sycsyc

     

    代码解析

    接下来,程序查找.reioc段,并将.reioc段数据与sycsyc循环异或,写出IDAPython脚本

    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]))

    然后跟去混淆方法类似,先转换为Data,再强制分析数据,转换为函数。

     

    打开sub_4027F0函数

     

    这里实际和上面差不多,输入长度为30的flag,取前5个字符,与.ebata段的部分循环异或。(实际就是得到下面sub_404000函数的代码)。这里可以合理猜测flag的前五个字符为SCTF{,写出脚本

    beg_adr = 0x404000
    dst = "SCTF{"
    for i in range(16,96):
        PatchByte(beg_adr+i,Byte(beg_adr+i)^ord(dst[i%5]))

    跟上面一样将.ebata段转换为data,再分析代码,转换为函数,开头如果提示栈不平衡,重新单独分析一下就行。

     

    解密

    这里一个函数实际就是RC4生成S-BOX,另一个函数就是加密函数,key值为syclover

    #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
    ", pData);
        printf("key=%s,length=%d
    
    ", key, strlen(key));
        rc4_init(s, (unsigned char*)key, strlen(key)); // 已经完成了初始化
        printf("完成对S[i]的初始化,如下:
    
    ");
        for (i = 0; i < 256; i++)
        {
            printf("%02X", s[i]);
            if (i && (i + 1) % 16 == 0)putchar('
    ');
        }
        printf("
    
    ");
        for (i = 0; i < 256; i++) // 用s2[i]暂时保留经过初始化的s[i],很重要的!!!
        {
            s2[i] = s[i];
        }
    
        printf("已经初始化,现在解密:
    
    ");
        printf("len = %d
    ", len);
        rc4_crypt(s, (unsigned char*)pData, len); 
        printf("pData=%s
    
    ", 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

  • 相关阅读:
    20180925-5 代码规范,结对要求
    20180925-6 四则运算试题生成
    20180925-7 规格说明书-吉林市2日游
    第二周例行报告
    第二周博客作业
    【杨老师粉丝群】第一周立会报告第四次
    20180925-1 每周例行报告
    规格说明书——吉林市两日游
    效能分析
    四则运算试题生成
  • 原文地址:https://www.cnblogs.com/Mayfly-nymph/p/13253179.html
Copyright © 2011-2022 走看看