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

Posted 大灬白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了XCTF-攻防世界CTF平台-Reverse逆向类——56tar-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 以上是关于XCTF-攻防世界CTF平台-Reverse逆向类——56tar-tar-binks的主要内容,如果未能解决你的问题,请参考以下文章

XCTF-攻防世界CTF平台-Reverse逆向类——53easyCpp

XCTF-攻防世界CTF平台-Reverse逆向类——65reverse-box

XCTF-攻防世界CTF平台-Reverse逆向类——59mfc逆向-200

XCTF-攻防世界CTF平台-Reverse逆向类——52handcrafted-pyc

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

XCTF-攻防世界CTF平台-Reverse逆向类——54echo-server