记账软件随手记备份文件解密

随手记是一款记账软件,个人比较喜欢其中的记账功能,投资模块我是一点兴趣没有,为了减少广告影响我用华为自带的手机管家ban掉了联网权限,为了支付宝和微信的账单可以正常导入到随手记中,对随手记的备份还原功能进行了一波逆向分析,成功实现了账单数据的写入,还原后可在随手记App中正常查阅。

个人使用的用于分析的Android版本号12.69.0.0.

随手记备份出来的kbf文件本质上是个zip文件,随手记备份时将账本数据mymoney.sqlite和用户信息数据backup_info打包为本质为zip的kbf文件.当然这两个文件都有一定程度的魔改backup_info这个不用管到时候原封不动打包回去就行,主要是mymoney.sqlite这个文件的解密。
使用以下Python代码可正常解密解压kbf文件得到sqlite数据库文件

def unzip_kbf(input_files=None):
    global tp_kbf_file_name
    if input_files is None:
        if global_constant.full_update or global_constant.sdcard_root_path is None:
            input_file_abs_path = os.path.join(tp_path.parent.parent, 'f_input/')
        else:
            input_file_abs_path = os.path.join(global_constant.sdcard_root_path, '.mymoney', 'backup/')
    else:
        if global_constant.full_update or global_constant.sdcard_root_path is None:
            input_file_abs_path = os.path.join(tp_path.parent.parent, 'f_input/', input_files)
        else:
            input_file_abs_path = os.path.join(global_constant.sdcard_root_path, '.mymoney', 'backup/', input_files)
    if not os.path.exists(input_file_abs_path):
        print(f'{input_file_abs_path}不存在')
        raise FileNotFoundError(f'输入文件kbf不存在-->{input_file_abs_path}')
    print(f'输入待解压kbf文件路径:{input_file_abs_path}')
    if os.path.isdir(input_file_abs_path):
        files = os.listdir(input_file_abs_path)
        for f in files:
            if f.endswith('.kbf'):
                global tp_kbf_file_name
                tp_kbf_file_name = f
                zf = zipfile.ZipFile(os.path.join(input_file_abs_path, f))
                zf.extractall(path=os.path.join(tp_path.parent.parent, 'f_input/'))
                break
    elif os.path.isfile(input_file_abs_path):
        if input_files.lower().endswith('.kbf'):
            tp_kbf_file_name = input_files
            zf = zipfile.ZipFile(input_file_abs_path)
            zf.extractall(path=os.path.join(tp_path.parent.parent, 'f_input/'))
        pass
 
 
def ssj_kbf_sqlite_convert(input_file=None, output_file=None, convert=ConvertType.Exchanged):
    """
    convert ssj data, after kbf unzip to sqlite,convert it to normal sqlite database file
    :param convert: 0 means convert it auto, 1 kbf format to sqlite, 2 sqlite to kbf format
    :param input_file: the mymoney.sqlite file path
    :param output_file: the convert mymoney.sqlite file path
    :return:
    """
    if input_file is None:
        input_file = os.path.join(tp_path.parent.parent, 'f_input/mymoney.sqlite')
    if output_file is None:
        output_file = os.path.join(tp_path.parent.parent, 'f_output/mymoney.sqlite')
    sqlite_header = (0x53, 0x51, 0x4C, 0x69,
                     0x74, 0x65, 0x20, 0x66,
                     0x6F, 0x72, 0x6D, 0x61,
                     0x74, 0x20, 0x33, 0x0)
    kbf_header = (0x0, 0x0, 0x0, 0x0,
                  0x0, 0x0, 0x0, 0x0,
                  0x0, 0x0, 0x0, 0x0,
                  0x0, 0x46, 0xFF, 0x0)
    if global_constant.print_repeat_data_info:
        read_file_header(input_file)
    if os.path.exists(output_file):
        os.remove(output_file)
    with open(input_file, mode='rb') as f:
        with open(output_file, mode='wb') as fw:
            data_buffer = f.read()
            if data_buffer[0] == 0x53:
                kbf2sqlite = False
                print("当前为SQLite文件格式")
            if data_buffer[0] == 0x00:
                kbf2sqlite = True
                print("当前为KBF文件格式")
            write_buffer = bytearray(data_buffer)
            index = 0
            while index < len(kbf_header) and index < len(sqlite_header):
                if convert == ConvertType.Exchanged:
                    if kbf2sqlite is True:
                        write_buffer[index] = sqlite_header[index]
                    else:
                        write_buffer[index] = kbf_header[index]
                elif convert == ConvertType.SqliteToKbf and not kbf2sqlite:
                    write_buffer[index] = kbf_header[index]
                elif convert == ConvertType.KbfToSqlite and kbf2sqlite:
                    write_buffer[index] = sqlite_header[index]
 
                index = index + 1
            fw.write(write_buffer)
            pass
    if global_constant.print_repeat_data_info:
        read_file_header(output_file)
    pass

以上代码先将正常解压kbf文件得到mymoney.sqlite,再修改前16个字节

sqlite_header = (0x53, 0x51, 0x4C, 0x69,
                 0x74, 0x65, 0x20, 0x66,
                 0x6F, 0x72, 0x6D, 0x61,
                 0x74, 0x20, 0x33, 0x0)
kbf_header = (0x0, 0x0, 0x0, 0x0,
              0x0, 0x0, 0x0, 0x0,
              0x0, 0x0, 0x0, 0x0,
              0x0, 0x46, 0xFF, 0x0)

此时得到就是正常可以写入数据的sqlite数据库了。

下面再说随手记中的数据库结构
1. t_transaction 记录了所有交易信息,主要为支出,收入,转账三类
2. t_account 记录账户信息譬如银行卡,支付宝,微信支付等都属于资金流入流出的账户
3. t_category 记录的支出,收入的分类信息

以下是t_transaction的表结构

CREATE TABLE "t_transaction" (
    "transactionPOID"   LONG NOT NULL,
    "createdTime"   LONG NOT NULL,
    "modifiedTime"  LONG NOT NULL,
    "tradeTime" LONG NOT NULL,
    "memo"  varchar(100),
    "type"  integer NOT NULL,
    "creatorTradingEntityPOID"  LONG,
    "modifierTradingEntityPOID" LONG,
    "buyerAccountPOID"  LONG,
    "buyerCategoryPOID" LONG DEFAULT 0,
    "buyerMoney"    decimal(12 , 2),
    "sellerAccountPOID" LONG,
    "sellerCategoryPOID"    LONG DEFAULT 0,
    "sellerMoney"   decimal(12 , 2),
    "lastUpdateTime"    LONG,
    "photoName" VARCHAR(100),
    "photoNeedUpload"   integer DEFAULT 0,
    "relation"  varchar(200) DEFAULT '',
    "relationUnitPOID"  LONG,
    "ffrom" varchar(250) DEFAULT '',
    "clientID"  LONG DEFAULT 0,
    "FSourceKey"    varchar(100) DEFAULT NULL,
    "photos"    TEXT,
    "transaction_number"    TEXT,
    "merchant_order_number" TEXT,
    "import_data_source"    TEXT,
    PRIMARY KEY("transactionPOID")
);

需要注意的是
支出时 buyerAccountID 为支付账号ID buyerCategory留空 sellerAccountID留空 sellerCategory为具体支出类别 type =0
收入时 buyerAccountID 留空 buyerCategory为收入类别 sellerAccountID为账户ID sellerCategory留空 type = 1
转账时 buyerAccountID为资金来源方 sellerAccountID为资金流向方 category均为空,而且会插入两条记录一个type=2一个type=3,这两条记录relation应该一致而FSourceKey不同,否则还原数据时可能导致app闪退

通过支付宝App的账单开具个人流水记录可以导出支付宝账单,同理可导出微信账单。两者都是csv文件自己解析下即可。

最后正常写入数据后,调用上面的ssj_kbf_sqlite_convert函数将sqlite数据库转化为kbf需要的格式,最后导入手机上随手记的备份目录,通过随手记的还原功能可以导入数据

你可能感兴趣的:(jvm,python)