其他分享
首页 > 其他分享> > XCTF-攻防世界CTF平台-Reverse逆向类——56、tar-tar-binks

XCTF-攻防世界CTF平台-Reverse逆向类——56、tar-tar-binks

作者:互联网

目录标题

题目提供了两个文件flag.tar和libarchive.dylib

一、解压缩

  flag.tar是一个不能直接在windwos下解压缩的文件,直接用tar -xvf flag.tar命令解压缩:
在这里插入图片描述

  提示不是一个tar压缩文件,但是其中包含了一个flag.txt的文件头,解压缩出了一个flag.txt文件。
  再用file flag.tar命令来查看文件的格式:
在这里插入图片描述

  是一个POSIX格式的tar压缩包
关于POSIX格式:
1、压缩
  如果你使用的是GNU tar,那么就有个参数来设置所产生的文件的格式:
-H, --format=FORMAT 创建指定格式的归档
  FORMAT 是以下格式中的一种:

gnuGNU tar 1.13.x 格式
oldgnuGNU 格式 as per tar <= 1.12
paxPOSIX 1003.1-2001 (pax) 格式
posix等同于 pax
ustarPOSIX 1003.1-1988 (ustar) 格式
v7old V7 tar 格式

  所以假如你要把flag目录打包成 posix格式的tar包,就可以用

tar -cf flag.tar --format=posix flag

或者是

tar -cf flag.tar -H posix flag

2、解压:

tar -xvf flag.tar -H posix

二、查看文件

  继续回到题目,打开flag.txt:
在这里插入图片描述

  每4个16进制数字一组,以逗号分割,应该是另一个文件libarchive.dylib使用的输入或者输出文件,查看libarchive.dylib的格式:
在这里插入图片描述

  是Mac平台下的64位动态链接共享库
  用IDA64打开:
在这里插入图片描述

三、分析程序

  根据flag.txt文件的格式,输入输出的时候应该是使用了格式化字符串的%04x、%04X之类的把数据格式化,直接在IDA字符串窗口中搜索%04x、%04X:
在这里插入图片描述

  找到按%04X输出的位置,__int64 __fastcall archive_write_client_write(__int64 a1, __int64 a2, unsigned __int64 a3)函数:
在这里插入图片描述

函数原型:

int __sprintf_chk (char *s, int flags, size_t len, const char *format, ...)
__sprintf_chk(&v9[v3], 0, 0xFFFFFFFFFFFFFFFFLL, "%04X,", sub_101[v8++]);

  把sub_101[v8++]根据格式字符串"%04X,",将格式化输出写入&v9[v3]
函数原型:

void * __memcpy_chk (void *dest, const void *src, size_t len, size_t dstlen)
__memcpy_chk(v10, v9, v18, -1LL);

  把v9的值复制给v10
  所以我们接下来看一下sub_101数组的值是怎么来的,选中sub_101右键Jump to xrefs或者快捷键X查看使用到sub_101数组的地方:
在这里插入图片描述

  第二次就是在这个archive_write_client_write函数中,点击第一次使用sub_101数组的地方应该就是生成它的值的地方:
在这里插入图片描述

  posi是一个全局变量的数组下标,这个sub_1023458函数每次由输入*a1计算得到一个sub_101[posi]的值。
  再找到上一个调用sub_1023458函数的地方:
sub_1023456(int a1)函数:
在这里插入图片描述

  果然sub_1023457函数就是在循环中每次调用sub_1023456函数根据输入的字符返回不同的下标返回值,作为sub_1023458函数的参数,调用sub_1023458函数给sub_101数组赋值。
sub_1023456(int a1)函数:
在这里插入图片描述

  sub_1023458函数的实际参数v9数组有3个元素,刚好对应上面sub_1023458函数的*a1、a1[1]、a1[2]。
  再找到调用sub_1023457函数的地方,发现就是archive_write_client_write函数:
在这里插入图片描述

四、程序主要逻辑:

  所以现在的程序主要逻辑就是在archive_write_client_write函数中,流程图:
在这里插入图片描述

1、 首先archive_write_client_write函数第25行:
__memcpy_chk(v13, a2, a3, -1LL);
从输入的字符串a2中复制字符串的长度a3个字节到v13;

2、之后当字符串的长度a3>0x200时,调用sub_1023457(v13, (unsigned int)a3);对输入的字符串v13进行计算;

3、sub_1023456(v5)判断输入的字符串的每个字符,是否属于ctable[]数组,根据不同情况置pending标志位,然后设置返回值返回;

4、每执行3次v9[–v6] = sub_1023456(v5),重置了新的v9[3]数组的3个值,就执行一次sub_1023458(v9),sub_1023458函数中主要是计算得到一个sub_101[v2] = *a1 + 40 * a1[1] + 1600 * a1[2]的值;

5、计算结束之后,返回到archive_write_client_write函数:
__sprintf_chk(&__s[v3], 0, 0xFFFFFFFFFFFFFFFFLL, “%04X,”, sub_101[v11++]);
就会把sub_101[v8++]根据格式字符串"%04X,",将格式化输出写入&v9[v3]。

五、逆向思路:

所以我们现在是相当于知道了最后的结果sub_101,要得到最开始的输入v13。

步骤一:

1、首先是sub_1023458函数中解sub_101[v2] = a1 + 40 * a1[1] + 1600 * a1[2]这个三元一次方程组,每个sub_101[v2]的值都可以计算得到一组v9数组的值a1、a1[1]、a1[2]:

*a1=sub_101[v2] % 40;
a1[1]=(sub_101[v2] / 40) % 40;
a1[2]=sub_101[v2] / 1600;

步骤二:

2、之后每一组v9数组的值都对应1个sub_1023456函数输入的字符,也就是根据函数返回值(ctable数组下标),找到输入的字符(ctable数组下标对应的字符)。sub_1023457函数调用sub_1023456(v5)给v9数组的3个数赋值之后,再调用sub_1023458函数。
(1)sub_1023456(v5)函数原理是判断输入的字符,如果属于ctable数组前39个,pending 置0,直接返回对应的ctable数组下标i,并且返回之后会退出给v9[1]赋值的循环;
(2)如果如果属于ctable数组后39个(i+39),就返回39,且下一次的返回值就是i;如果不属于ctable数组就返回37。
  所以我们在知道返回值的时候,要逆推得到输入的字符,就要分为返回值是39也就是属于后39个字符的情况和返回值是小于39也就是属于前39个字符的两种情况。
  这里我们为了方便理解sub_1023457和sub_1023456函数的作用我们直接用C语言复现了它的逻辑:

#include<stdio.h>
#include<stdbool.h>

bool pending;
int sub_1023456_shifted = -1;
int ctable[] = {
0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73,
0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0x0A, 0x00,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x28, 0x21, 0x40, 0x23,
0x2C, 0x2E, 0x3F, 0x2F, 0x2A, 0x29, 0x3C, 0x3E, 0x00};

int main()
{
    char * v3; // rax
    unsigned int v5; // [rsp+4h] [rbp-2Ch]
    int v6; // [rsp+8h] [rbp-28h]
    int v9[3]; // [rsp+1Ch] [rbp-14h] BYREF
    char a1[] = "abcABc";//定义一个字符数组
    int length = sizeof(a1);
    int i;

    v6 = 3;
    v3 = a1;          // 输入的字符串
    while ( v3-a1 < length )
    {
        v5 = *v3++;
        //printf("%c ",v5);
        pending = 1;                                // 这是一个全局bool变量pending,在sub_1023456(v5)中也有改变它的值
        while ( pending )
        {
            v9[--v6] = sub_1023456(v5);               // v9[2]、v9[1]、v9[0]=,一个字符v5最多可以得到3个不同的返回值
            //printf("%d ",v9[v6]);
            if ( !v6 )                                // v6=0时执行
            {
                for(i = 0 ; i<3; i++){
                    printf("%d ",v9[i]);
                }
                printf("\n");
                //sub_1023458(v9);                        // v9数组每得到3个值就调用sub_1023458函数给sub_101数组赋一个值
                v6 = 3;                                 // 重新赋值为3
            }
        }
    }

    if ( v6 != 3 )                                // 如果上面v9数组没有全部赋值,剩下的补足0后继续调用sub_1023458函数给sub_101数组赋最后一个值
    {
        while ( v6 != -1 )
            v9[--v6] = 0;
        for(i = 0 ; i<3; i++){
            printf("%d ",v9[i]);
        }
        printf("\n");
        //sub_1023458(v9);
    }

    return 0;
}

int sub_1023456(int a1)
{
    int v2; // [rsp+0h] [rbp-Ch]
    int i; // [rsp+0h] [rbp-Ch]
    int v4; // [rsp+4h] [rbp-8h]
    unsigned int v5; // [rsp+8h] [rbp-4h]

    v4 = a1;                                      // 输入的字符
    if ( sub_1023456_shifted == -1 )
    {
        if ( a1 == 126 )                            // ASCII值等于126(‘~’)
            v4 = 0;
        for ( i = 0; i < 39; ++i )
        {
            if ( ctable[i] == v4 )                    // ctable数组前39个包括小写字母和数字,如果v4在这里面
            {
                pending = 0;                            // pending 置0,会退出给v9赋值的循环
                return (unsigned int)i;                 // 就返回下标
            }
            if ( ctable[i + 39] == v4 )               // ctable数组后39个包括大写字母和符号,如果v4在这里面
            {
                pending = 1;                            // pending 置1,会继续给v9[1]赋值
                sub_1023456_shifted = i;                // 这个值改为当前i,下次调用的时候就会执行else部分
                return 39;                              // 返回39,下一次的返回值就直接为这次的i
            }
        }
        pending = 0;                                // 如果v4的值不在数组里
        v5 = 37;                                    // ctable[37]是空格
    }
    else
    {
        v2 = sub_1023456_shifted;                   // 获得上一次执行返回的i
        sub_1023456_shifted = -1;                   // 值重新置为-1,下次又执行if部分
        pending = 0;                                // pending 置0
        v5 = v2;
    }
    return v5;                                    // 返回上一次执行的i
}

运行结果:
在这里插入图片描述

  可以看到当输入的字符串是:"abcABc"时,对应的输出是:
3 2 1
39 1 39
0 3 2
其中字符’a’在ctable数组中的下标是1,所以返回值是1赋值给v9[2];
其中字符’b’在ctable数组中的下标是2,所以返回值是2赋值给v9[1];
其中字符’c’在ctable数组中的下标是3,所以返回值是3赋值给v9[0];
字符’A’在ctable数组中的下标是40,所以返回值是39赋值给v9[2],以及下一次的返回值是1赋值给v9[1];
字符’B’在ctable数组中的下标是41,所以返回值是39赋值给v9[0],以及下一次的返回值是2赋值给v9[2];
其中字符’c’在ctable数组中的下标是3,所以返回值是3赋值给v9[1];
最后这次v9[0]没有被赋值,所以最后补0。

六、解密代码:

  到这,我们就完全明白了加密的流程和逆向的思路,接下来用Python编写解密代码:

# py -3
# -*- coding: utf-8 -*-
# coding:utf-8

# 最后flag.txt中的密文
ciphertext=[0xF5D1,0x4D6B,0xED6A,0x08A6,0x38DD,0xF7FA,0x609E,0xEBC4,0xE55F,0xE6D1,0x7C89,0xED5B,0x0871,0x1A69,0x5D58,0x72DE,0x224B,0x3AA6,0x0845,0x7DD6,0x58FB,0xE9CC,0x0A2D,0x76B8,0xED60,0x251A,0x1F6B,0x32CC,0xE78D,0x12FA,0x201A,0xE889,0x2D25,0x922A,0x4BC5,0xF5FF,0xF8E5,0xC79B,0x3A77,0x4BDB,0xEA11,0x5941,0x58BD,0x3A95,0xF5C9,0xA225,0xAD40,0xF8BD,0x095D,0x70B6,0x458C,0xE7A9,0xEA68,0x252F,0x094B,0x5E41,0x0969,0x6015,0x5ED5,0xF6E5,0x59B9,0x7CAF,0x66DF,0x265B,0x7837,0x57B4,0x7CAF,0xAED9,0xF707,0x6A3C,0xF8E5,0xF509,0x7C8B,0x0915,0x2235,0x336F,0x33E9,0x2D14,0x7C91,0x5804,0x83E5,0xE78D,0xF4EA,0x0874,0xED6B,0x4B35,0xE839,0x57B4,0xE77C,0xEA68,0x2525,0xAD41,0xED6F,0x3A4A,0x4BCC,0x6015,0xF440,0x0858,0x3AA6,0x7809,0x671D,0x0874,0xEA77,0x63AF,0x2E91,0x5845,0xF6C4,0x086D,0x7795,0x3939,0x57B4,0x7C89,0x82DC,0x32ED,0xB994,0xC7AF,0x9135,0x0E65,0x1B66,0xED5B,0x3235,0x6577,0x5A80,0x3AD3,0xE776,0x1EE5,0xAD41,0xED59,0x864C,0x70B4,0x3876,0xED67,0x64D6,0xF8E5,0xF505,0xEAD9,0x7C9C,0x32ED,0xB994,0xB4EF,0x0C6C,0xF665,0xF5F5,0x9047,0x521A,0xE99E,0xEA68,0x252F,0x9D09,0x76B7,0xE776,0x1ED0,0x095D,0x0D4D,0x5D5A,0x087B,0x2005,0x1526,0x7E76,0x85AD,0x78B9,0xE8B6,0x782C,0x251C,0x32ED,0x7F68,0xEBE3,0xEA41,0x57FD,0xED59,0x846D,0x7A05,0xB994,0xBB78,0xED6A,0x08A6,0x38DD,0x3B5D,0x7E45,0xE839,0x738C,0xE9CC,0x0A2D,0x764A,0x609E,0xE8B6,0xEA68,0x2524,0xE6BB,0x7C9C,0x639F,0x3A95,0x0895,0xF40F,0x8328,0xEA69,0x7EE5,0xF8BD,0x7F7D,0x0D6D,0x70B6,0x458C,0xE8B6,0xEA68,0x251C,0x6065,0xB35F,0xC789,0x5845,0x7F7D,0x6D89,0x4C6E,0xA20E,0x60B5,0x7E45,0xED59,0xF707,0x69EF,0x922A,0x4BC5,0xF6EF,0x8635,0xF4B9,0x57B4,0x7CF8,0xED60,0x2510,0x095D,0x20AF,0x3545,0xF40F,0x8328,0xEA41,0x58A4,0x225D,0x7E7C,0x4BDB,0xF8BD,0x082C,0xEAE7,0x5D57,0x5D50,0x0914,0xE7C7,0x8624,0x7CF8,0xED60,0x2511,0x7C8E,0x7159,0x8416,0x7EF9,0xE7E5,0x774A,0x3895,0x1EC9,0x7C90,0x09B9,0x58BD,0x5FF5,0xE99E,0xEA68,0x250A,0x224C,0xEA3D,0x73F5,0x7C89,0x53A6,0x3190,0x3B5D,0x1526,0x7DD5,0x666A,0x0919,0x225F,0xCDEF,0x79E1,0x7E7B,0x7E6B,0x082C,0xA277,0xE885,0xE8BB,0xE775,0x5FF7,0xEA68,0x251B,0x7FDF,0x589D,0x7A05,0x779A,0x8A5A,0x7C91,0x5D5C,0x32ED,0xF628,0x2195,0xF49A,0x0C77,0xEAE1,0x59B9,0x58BD,0xE570,0xE99E,0xEA3D,0x73F9,0x13AD,0x2BF5,0x225D,0x7F7D,0x70B6,0x4A9C,0x337A,0x1EC9,0x4D05,0x7E75,0x2578,0xED59,0x38E5,0x1ECA,0xA210,0x3B5D,0x779A,0x8A6F,0xC790,0x2518,0x4B41,0x7C89,0x5D49,0x4D05,0x152D,0x73C5,0x79F9,0x4BED,0x913C,0x37C9,0x5D4D,0x53C8,0x0941,0x7C97,0x5D5B,0x346A,0x82D8,0x5F36,0x801F,0xC800]
# 所有的v9数组
v9 = []
# 每个v9数组逆推得到的字符下标数组
v5 = []
# 输入的flag
flag = ""
# ctable字符常量数组
ctable = [
0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73,
0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0x0A, 0x00,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x28, 0x21, 0x40, 0x23,
0x2C, 0x2E, 0x3F, 0x2F, 0x2A, 0x29, 0x3C, 0x3E, 0x00]

## 1、解3元一次方程,得到a0、a1、a2一组三个数
for cipher in ciphertext:
    a0 = cipher%40
    a1 = (cipher//40)%40
    a2 = cipher//1600
    v9 += [a2,a1,a0]

# 2、v9数组中的数可以得到字符下标
i=0
while i < len(v9):
    # 数字等于39,加上下一个数,组成一个后40个字符的下标
    if v9[i] == 39:
        v5 += [v9[i]+v9[i+1]]
        i += 2
    # 数字在39以内的直接当前成前40个字符的下标
    else :
        v5 += [v9[i]]
        i += 1

# 每个字符下标都对应一个字符,这时候字符下标数组里
for lab in v5:
    # 去掉最后填一堆的0
    if ctable[lab] != 0x0:
    	flag += chr(ctable[lab])
print(flag)

运行结果:
在这里插入图片描述

  得到这段文本:

Milos Raonic (born 1990) is a Canadian professional tennis player. He reached a career high world No. 4 singles ranking in May 2015, as ranked by the Association of Tennis Professionals (ATP). His career highlights include a Grand Slam final at the 2016 Wimbledon Championships and two Grand Slam semifinals at the 2014 Wimbledon Championships and 2016 Australian Open. He was the 2011 ATP Newcomer of the Year, and has been ranked continuously inside the top 20 since August 2012. Raonic is the first player born in the 1990s to win an ATP title, to be ranked in the top 10, and to qualify for the ATP World Tour Finals. He has eight ATP singles titles, all won on hard courts. He is frequently described as having one of the best serves among his contemporaries. Statistically, he is among the strongest servers in the Open Era, winning 91p of service games to rank third all time. Aided by his serve, he plays an all court style with an emphasis on short points.

  它的MD5值:2c8cd31daeba8753815851f13e6370b3
  但是代码在windwos下的PyCharm客户端执行的时候需要加一个换行,因为在linux终端输出的时候它是自带了一个换行,但是在这我们要给它加上,不然md5值就会不一样:

# py -3
# -*- coding: utf-8 -*-
# coding:utf-8

from hashlib import md5

# 最后flag.txt中的密文
ciphertext=[0xF5D1,0x4D6B,0xED6A,0x08A6,0x38DD,0xF7FA,0x609E,0xEBC4,0xE55F,0xE6D1,0x7C89,0xED5B,0x0871,0x1A69,0x5D58,0x72DE,0x224B,0x3AA6,0x0845,0x7DD6,0x58FB,0xE9CC,0x0A2D,0x76B8,0xED60,0x251A,0x1F6B,0x32CC,0xE78D,0x12FA,0x201A,0xE889,0x2D25,0x922A,0x4BC5,0xF5FF,0xF8E5,0xC79B,0x3A77,0x4BDB,0xEA11,0x5941,0x58BD,0x3A95,0xF5C9,0xA225,0xAD40,0xF8BD,0x095D,0x70B6,0x458C,0xE7A9,0xEA68,0x252F,0x094B,0x5E41,0x0969,0x6015,0x5ED5,0xF6E5,0x59B9,0x7CAF,0x66DF,0x265B,0x7837,0x57B4,0x7CAF,0xAED9,0xF707,0x6A3C,0xF8E5,0xF509,0x7C8B,0x0915,0x2235,0x336F,0x33E9,0x2D14,0x7C91,0x5804,0x83E5,0xE78D,0xF4EA,0x0874,0xED6B,0x4B35,0xE839,0x57B4,0xE77C,0xEA68,0x2525,0xAD41,0xED6F,0x3A4A,0x4BCC,0x6015,0xF440,0x0858,0x3AA6,0x7809,0x671D,0x0874,0xEA77,0x63AF,0x2E91,0x5845,0xF6C4,0x086D,0x7795,0x3939,0x57B4,0x7C89,0x82DC,0x32ED,0xB994,0xC7AF,0x9135,0x0E65,0x1B66,0xED5B,0x3235,0x6577,0x5A80,0x3AD3,0xE776,0x1EE5,0xAD41,0xED59,0x864C,0x70B4,0x3876,0xED67,0x64D6,0xF8E5,0xF505,0xEAD9,0x7C9C,0x32ED,0xB994,0xB4EF,0x0C6C,0xF665,0xF5F5,0x9047,0x521A,0xE99E,0xEA68,0x252F,0x9D09,0x76B7,0xE776,0x1ED0,0x095D,0x0D4D,0x5D5A,0x087B,0x2005,0x1526,0x7E76,0x85AD,0x78B9,0xE8B6,0x782C,0x251C,0x32ED,0x7F68,0xEBE3,0xEA41,0x57FD,0xED59,0x846D,0x7A05,0xB994,0xBB78,0xED6A,0x08A6,0x38DD,0x3B5D,0x7E45,0xE839,0x738C,0xE9CC,0x0A2D,0x764A,0x609E,0xE8B6,0xEA68,0x2524,0xE6BB,0x7C9C,0x639F,0x3A95,0x0895,0xF40F,0x8328,0xEA69,0x7EE5,0xF8BD,0x7F7D,0x0D6D,0x70B6,0x458C,0xE8B6,0xEA68,0x251C,0x6065,0xB35F,0xC789,0x5845,0x7F7D,0x6D89,0x4C6E,0xA20E,0x60B5,0x7E45,0xED59,0xF707,0x69EF,0x922A,0x4BC5,0xF6EF,0x8635,0xF4B9,0x57B4,0x7CF8,0xED60,0x2510,0x095D,0x20AF,0x3545,0xF40F,0x8328,0xEA41,0x58A4,0x225D,0x7E7C,0x4BDB,0xF8BD,0x082C,0xEAE7,0x5D57,0x5D50,0x0914,0xE7C7,0x8624,0x7CF8,0xED60,0x2511,0x7C8E,0x7159,0x8416,0x7EF9,0xE7E5,0x774A,0x3895,0x1EC9,0x7C90,0x09B9,0x58BD,0x5FF5,0xE99E,0xEA68,0x250A,0x224C,0xEA3D,0x73F5,0x7C89,0x53A6,0x3190,0x3B5D,0x1526,0x7DD5,0x666A,0x0919,0x225F,0xCDEF,0x79E1,0x7E7B,0x7E6B,0x082C,0xA277,0xE885,0xE8BB,0xE775,0x5FF7,0xEA68,0x251B,0x7FDF,0x589D,0x7A05,0x779A,0x8A5A,0x7C91,0x5D5C,0x32ED,0xF628,0x2195,0xF49A,0x0C77,0xEAE1,0x59B9,0x58BD,0xE570,0xE99E,0xEA3D,0x73F9,0x13AD,0x2BF5,0x225D,0x7F7D,0x70B6,0x4A9C,0x337A,0x1EC9,0x4D05,0x7E75,0x2578,0xED59,0x38E5,0x1ECA,0xA210,0x3B5D,0x779A,0x8A6F,0xC790,0x2518,0x4B41,0x7C89,0x5D49,0x4D05,0x152D,0x73C5,0x79F9,0x4BED,0x913C,0x37C9,0x5D4D,0x53C8,0x0941,0x7C97,0x5D5B,0x346A,0x82D8,0x5F36,0x801F,0xC800]
# 所有的v9数组
v9 = []
# 每个v9数组逆推得到的字符下标数组
v5 = []
# 输入的flag
flag = ""
# ctable字符常量数组
ctable = [
0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73,
0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0x0A, 0x00,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x28, 0x21, 0x40, 0x23,
0x2C, 0x2E, 0x3F, 0x2F, 0x2A, 0x29, 0x3C, 0x3E, 0x00]

## 1、解3元一次方程,得到a0、a1、a2一组三个数
for cipher in ciphertext:
    a0 = cipher%40
    a1 = (cipher//40)%40
    a2 = cipher//1600
    v9 += [a2,a1,a0]

# 2、v9数组中的数可以得到字符下标
i=0
while i < len(v9):
    # 数字等于39,加上下一个数,组成一个后40个字符的下标
    if v9[i] == 39:
        v5 += [v9[i]+v9[i+1]]
        i += 2
    # 数字在39以内的直接当前成前40个字符的下标
    else :
        v5 += [v9[i]]
        i += 1

# 每个字符下标都对应一个字符,这时候字符下标数组里
for lab in v5:
    # 去掉最后填一堆的0
    if ctable[lab] != 0x0:
       flag += chr(ctable[lab])
# 这段话最后还有一个换行
flag += '\n'
print(flag)
# 输出小写16进制的MD5值
print("md5值为:",md5(flag.encode()).hexdigest())

运行结果:
在这里插入图片描述

  也可以得到它的MD5值:2c8cd31daeba8753815851f13e6370b3

标签:Reverse,tar,ctable,56,a1,flag,数组,v9,sub
来源: https://blog.csdn.net/Onlyone_1314/article/details/121055013