2024年9月

什么是REGadgets

REGadgets,顾名思义,就是一个Reverse中常用的,提供给使用者代码片段的库,可以加快逆向速度,甚至直接爆破出结果。

REGadgets 如何获取

REGadgets目前还在开发中,也就是只在小范围测试中,开发者将用它参加每一场XCTF分站赛,所有的WP都是用它来写的,以保证REGadgets库与时俱进。

REGadgets 项目结构

截至到 2024/09/30 REGadgets项目目录如下。

C:.
│   .gitignore
│   README.md
│   __init__.py
│
├───bases
│       base128.py
│       base2048.pyd
│       base2048.pyi
│       base45.py
│       base58.py
│       base62.py
│       base65536.py
│       base91.py
│       bases.py
│       py3base92.py
│       __init__.py
│
├───bits
│       bits.py
│       cstyle.py
│       __init__.py
│
├───bruteforce
│       bruteforcer.py
│       __init__.py
│
├───crypto
│       aes.py
│       blowfish.py
│       bxors.py
│       rc4.py
│       tea.py
│       xtea.py
│       xxtea.py
│       __init__.py
│
├───trans
│       sbox.py
│       __init__.py
│
└───utils
        z3util.py
        __init__.py

REGadgets库相关介绍及使用方法

regadgets.bases

这个库中,提供了 base 相关的编码解码,而且,regadgets提供了换表的快捷方案。

例题 BaseCTF2024 BasePlus

from regadgets import *
enc = b'lvfzBiZiOw7<lhF8dDOfEbmI]i@bdcZfEc^z>aD!'
k = 0xe
dec = decode_b64(bxor(enc, [k for _ in range(len(enc))]).decode(),
                  '/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC')

print(dec)
# b'BaseCTF{BA5e_DEcoD1N6_sEcr3t}\x00'

在本例中,通过regadgets.bases.decode_b64我们对enc异或后的东西(base64编码后的东西)进行了解码,表是第二个参数。

例题 CDDC2024 Quals Emoji32

regadget的bases,支持换emoji表,因为Python的str对于Unicode非常友好。

from regadgets import *
emoji_table = ['😀','😥','😠','😱','😴','🤢','😇','😈','👋','✊','👌','✌','🤲','🙌','👏','🤝','🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🍎','🍌','🍓','🍑','🍕','🍔','🍟','🍦']
emoji_table = "".join(emoji_table)
encoded = "🤢😥😇😴🐭🦊😥✊✌️🍕🐰😴🍎🐭👌🐰🤢😴"
s = encoded.replace('\ufe0f', '') # Remove Wrong Char
while True:
    try:
        print(decode_b32(s, emoji_table))
        # (LIT)_(LIT)
        break
    except:
        s += '=' # Fix Padding
        continue

regadgets.bits

这个子库中,提供了位与数的相关操作。

# 相关提供的函数有这些
from .bits import rol16, rol32, rol64, rol8, ror16, ror32, ror64, ror8, byte2dword, dword2byte, byte2qword, qword2byte, pack_dword, unpack_dword, b2l, l2b
from .cstyle import cstyle_arr32

rol & ror

这些函数用于在Python下执行相应位数的循环左移和循环右移。

byte2word byte2dword byte2qword

用于将Python的bytes 2个/4个/8个 打包成为 List[int],可选是否进行Padding。其中int是word或dword或qword,小端序,方便后续进行TEA族加密。

word2byte dword2byte qword2byte

用于将List[int] 解包为Python bytes,小端序。用于将外部的数据(程序中提取出来的)快速进行处理。

pack_dword

用于将[1, 2, 3, 4, 5, 6] 这样的List[int],两两打包为形如 [(1, 2), (3, 4), (5, 6)] ,方便进行TEA族加密。

unpack_dword

用于将 [(1, 2), (3, 4), (5, 6)] 解包为 [1, 2, 3, 4, 5, 6] 这样的List[int]

l2b b2l

从Crypto.Util.number里面复制出来的long_to_bytes和bytes_to_long,用于大端序的互相转换。

regadgets.bruteforce

本子库用于解决逆向工程中的一些基础爆破问题。

regadgets.bruteforce.rg_brute_forcer

基础的Reverse下的爆破函数,通过检测解密后文本是否含有flag(可以进行修改),且解密后均为明文,来进行反向爆破,并还原出解密链。
本函数可以进行定制化扩展,由于特殊的模块化设计,可以自己手写一些常见算法,并在这些算法中爆破。
未来的话,这个函数可能会写成Cython实现的,用来加速爆破进程。

使用例

from regadgets import *

raw = list(b'flag{124214782134798217}')

for i in range(len(raw)):
    raw[i] ^= 114
    raw[i] += 514
    raw[i] ^= 810
    raw[i] &= 0xff

rg_brute_forcer(raw, 3)
'''
尝试深度 1 的解密...
[失败] 没有在深度 1 找到解密方案,用时 0.003998994827270508秒
尝试深度 2 的解密...
[失败] 没有在深度 2 找到解密方案,用时 2.940094470977783秒
尝试深度 3 的解密...
找到正确解密链: [('xor_decrypt', 20), ('mod_add_decrypt', 2), ('xor_decrypt', 76)]
解密结果: b'flag{124214782134798217}'
[成功] 在深度 3 找到解密方案,用时 59.33841252326965秒
'''

regadgets.crypto

regadgets.crypto.aes

标准的AES实现,根据比赛可以进行手动魔改。

regadgets.crypto.blowfish

标准的blowfish实现,根据比赛可以进行手动魔改。

regadgets.crypto.rc4

提供rc4_init, rc4_crypt, rc4_keystream三个函数,在init的时候返回S盒,crypt的时候提供s盒和data用于加密,keystream函数可以只返回S盒生成的key长度,可以手动指定box_size,因为在 BaseCTF2024 出了一个 S盒大小128的题目。

regadgets.crypto.bxors

提供了相关的XOR实现,比如 x_0 ^ x_1, x_1 ^ x_2, x_2 ^ x_3, ..... , x_(n-1) ^ x_n, x_n (我们称之为向右异或,bxorr)的加密解密。
除此之外,还提供了bxor(用于将两个等长的bytes | list进行互相异或)。
最常用的是,bxor_cycle,用于将第一个参数进行循环异或第二个参数(bytes | list)
一个常用的例子

# 对于一个数组循环异或 b'my_key'
enc = [...]
raw = bxor_cycle(enc, b'my_key')
# 对于一个数组进行异或 0xEE
raw = bxor_cycle(enc, b'\xee')
# 对于一个数组进行异或循环变量 i
raw = bxor_cycle(enc, range(len(enc)))

regadgets.crypto.tea / xtea / xxtea

用 python ctypes 实现的 TEA族加密。可以自行指定 delta / rounds, 对于xxtea可以指定很多参数,甚至是shift函数,具体请见其他题解。

regadgets.trans

用于S盒之类的变换

regadgets.trans.sbox

提供了 generate_sboxsbox_transform 两个函数。
如例题

from regadgets import *
from hashlib import md5
str1 = [0x28, 0x5F, 0x40, 0x34, 0x36, 0x32, 0x30, 0x21, 0x30, 0x38, 0x21, 0x36, 0x5F, 0x30, 0x2A, 0x30, 0x34, 0x34, 0x32, 0x21, 0x40, 0x31, 0x38, 0x36, 0x25, 0x25, 0x30, 0x40, 0x33, 0x3D,
        0x36, 0x36, 0x21, 0x21, 0x39, 0x37, 0x34, 0x2A, 0x33, 0x32, 0x33, 0x34, 0x3D, 0x26, 0x30, 0x5E, 0x33, 0x26, 0x31, 0x40, 0x3D, 0x26, 0x30, 0x39, 0x30, 0x38, 0x21, 0x36, 0x5F, 0x30, 0x2A, 0x26]
str2 = [0x35, 0x35, 0x35, 0x36, 0x35, 0x36, 0x35, 0x33, 0x32, 0x35, 0x35, 0x35, 0x35, 0x32, 0x32, 0x32, 0x35, 0x35, 0x36, 0x35, 0x35, 0x36, 0x35, 0x35, 0x35, 0x35, 0x32, 0x34, 0x33, 0x34,
        0x36, 0x36, 0x33, 0x33, 0x34, 0x36, 0x35, 0x33, 0x36, 0x36, 0x33, 0x35, 0x34, 0x34, 0x34, 0x32, 0x36, 0x35, 0x36, 0x35, 0x35, 0x35, 0x35, 0x35, 0x32, 0x35, 0x35, 0x35, 0x35, 0x32, 0x32, 0x32]
xxx = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F,
       0x50, 0x7B, 0x7D, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F]
r = []
for i in range(62):
    a = xxx.index(str1[i])
    b = xxx.index(str2[i])
    r.append(b * 23 + a)
# private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)
print(bytes(r))

# 手动转换一下 查表
r = b'?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z'

###### 从这里开始是S盒变换
data_from = b'abcdefghijklmnopqrstuvwxyz12345'
data_to = b'pqhrsidtujvwkebxylz1mf23n45ogca'
_, ibox = generate_sbox(data_from, data_to)
r = sbox_transform(r, ibox)

print(bytes(r))
print(f"flag{{{md5(bytes(r)).hexdigest()}}}")
# flag{63b148e750fed3a33419168ac58083f5}

regadgets.utils

regadgets.utils.z3util

目前只有z3_get_models,用于获取Z3的Solver的所有解。

ez_cython

from regadgets import *
ans = [4108944556, 3404732701, 1466956825, 788072761, 1482427973, 782926647, 
       1635740553, 4115935911, 2820454423, 3206473923, 1700989382, 2460803532,
       2399057278, 968884411, 1298467094, 1786305447, 3953508515, 2466099443,
       4105559714, 779131097, 288224004, 3322844775, 4122289132, 2089726849, 
       656452727, 3096682206, 2217255962, 680183044, 3394288893, 697481839, 
       1109578150, 2272036063]

def xxtea_sctf2024_shift(z, y, sum, k, p, debug = False):
    e = (sum.value >> 3) & 3
    PE = (p & 2) ^ e
    Ly = y.value << 3
    Ry = y.value >> 4
    Lz = z.value << 2
    Rz = z.value >> 3 

    LzRy = Rz ^ Ly
    LyRz = Ry ^ Lz
    SY = sum.value ^ y.value
    K = k[PE].value
    KZ = K ^ z.value
    result = (LzRy + LyRz) ^ (KZ + SY)
    return result
        
key = [0x53, 0x79, 0x43, 0x31] # Syc1

dec = xxtea_decrypt(ans, key, delta=0x9e3779b9, round_base=4, round_addi=60, shift_func=xxtea_sctf2024_shift)
print(bytes(dec))
# b'SCTF{w0w_y0U_wE1_kNOw_of_cYtH0N}'

BBox

Android
超绝一键apk混淆捏

const char *__fastcall Java_com_example_bbandroid_MainActivity_checkFlag(_JNIEnv *a1, __int64 a2, __int64 a3)
{
  time_t v5; // w22
  const char *result; // x0
  const char *v7; // x21
  unsigned int v8; // w23
  signed int v9; // w22
  __int64 v10; // x19
  __int64 v11; // x20
  char *v12; // x22
  char v13; // w0
  int v14; // w9
  signed int v15; // w8
  unsigned __int64 v16; // x10
  unsigned __int64 v17; // x11
  int v18; // w12
  int v19; // w13
  char flag[256]; // [xsp+8h] [xbp-108h] BYREF
  __int64 v21; // [xsp+108h] [xbp-8h]

  v21 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v5 = time(0LL);
  result = (const char *)((__int64 (__fastcall *)(_JNIEnv *, __int64, _QWORD))a1->functions->GetStringUTFChars)(
                           a1,
                           a3,
                           0LL);
  if ( result )
  {
    v7 = result;
    v8 = v5 / 1000000 / 100;
    strncpy(flag, result, 0xFFu);
    flag[255] = 0;
    v9 = __strlen_chk(flag, 0x100u);
    ((void (__fastcall *)(_JNIEnv *, __int64, const char *))a1->functions->ReleaseStringUTFChars)(a1, a3, v7);
    srand(v8);
    if ( v9 >= 4 )
    {
      v10 = 0LL;
      v11 = (unsigned int)v9 >> 2;
      do
      {
        v12 = &flag[4 * v10];
        *v12 ^= rand();
        v12[1] ^= rand();
        v12[2] ^= rand();
        v13 = rand();
        v14 = 32;
        v12[3] ^= v13;
        v15 = *(_DWORD *)v12;
        do
        {
          if ( v15 >= 0 )
            v15 *= 2;
          else
            v15 = (2 * v15) ^ 0x85B6874F;
          --v14;
        }
        while ( v14 );
        *(_DWORD *)&flag[4 * v10++] = v15;
      }
      while ( v10 != v11 );
    }
    if ( flag[0] == '3' )
    {
      v16 = 0LL;
      do
      {
        v17 = v16;
        if ( v16 == 0x27 )
          break;
        v18 = (unsigned __int8)flag[v16 + 1];
        v19 = dword_B14[++v16];
      }
      while ( v18 == v19 );
      return (const char *)(v17 > 0x26);
    }
    else
    {
      return 0LL;
    }
  }
  return result;
}

Exp

from regadgets import *
from z3 import *
from copy import deepcopy
enc =  [0x33, 0xC0, 0xC8, 0xA3, 0xF3, 0xBF, 0x1D, 0x1A, 0x3B, 0x41, 
  0xB7, 0xC6, 0xF1, 0x5E, 0x86, 0x52, 0x52, 0xCF, 0x6B, 0x1E, 
  0xC5, 0xF9, 0xCB, 0xBF, 0xED, 0x7B, 0x62, 0xF1, 0xF7, 0x43, 
  0x48, 0x54, 0xFB, 0x85, 0x4C, 0xD9, 0x35, 0x30, 0xF2, 0x6E]
print(bytes(enc))
dw = byte2dword(enc)

print(dw)
s = Solver()
x = [BitVec(f"x{i}", 32) for i in range(len(dw))]
y = deepcopy(x)
for i in range(len(dw)):
    for j in range(32):
        x[i] = If(LShR(x[i], 31) == 1, 2*x[i] ^ 0x85b6874f, 2*x[i])
    s.add(x[i] == dw[i])
print(s.check())
m = s.model()
r = []
for i in y:
    r.append(m[i].as_long())
print(r)
randv = [0x49308bb9,0x3cb3ad,0xfb4e87f,0x75655103,0x6d505b9f,0x1d20580f,
         0xdcf4af1,0x3e381967,0x54bcf579,0x73c09db7,0x501b2039,0x1b8950dd,
         0x23e73393,0x2b480a88,0x6818cdae,0x61d009ea,0x44c0c5b0,0x385aff3d,
         0x5cfb2a7a,0x587f9c07,0x158172f2,0x4d334c89,0x302b76e5,0x5e17f434,
         0x692de923,0x806d155,0x3d2c61d8,0x1d09ef4e,0x7c3d83b7,0x1d7621da,
         0x2dc0a3ec,0x456e0f71,0x1db2d588,0x3d758c6c,0x3ad36074,0xb033127,0x5a95e47b,0x48a2ab65,0x493b4a8e,0x2f52d9f5 ]
randv = [i & 0xff for i in randv]

v1 = bxor(dword2byte(r)[:len(enc)], bytes(randv)[:len(enc)])
v2 = bxor_cycle(v1, b'\x1e').decode()
print(decode_b64(v2, "nopqrstDEFGHIJKLhijklUVQRST/WXYZabABCcdefgmuv6789+wxyz012345MNOP"))
# b'Y0u_@re_r1ght_r3ver53_is_easy!'

其中 randv 是通过hook得到的rand()的结果。中间是一个不安全的CRC。

easyMCU

我自认为这是一个逆向题目,所以把它放到了Reverse下。
首先,使用010Editor,打开mcu.s19,发现它直接自动给我转hex了,我们Ctrl+S保存到文件即可。
隔壁队伍使用的是bincopy convert mcu.s19 -o binary out.bin进行的转换,也是一种方法。

通过图片中的TriCore,我们可以知道固件是Tri
经过翻找,我们找到关键代码


/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */

undefined4 FUN_80000690(void)

{
  byte bVar1;
  bool bVar2;
  uint uVar3;
  undefined4 uVar4;
  int i;
  
  bVar2 = FUN_8000125a(0x6000009c,(undefined *)0x60000004,(short *)0x60000000,_DAT_80003990,
                       iRam80003994);
  if (bVar2) {
    AES_ENCRYPT(0x60000004,-0x7fffc65d,0x6000007c,0x20);
    for (i = 0; i < 0x20; i += 1) {
      uVar3 = rol((uint)*(byte *)(i + 0x6000007c),3);
      *(char *)(i + 0x6000007c) = (char)uVar3;
      bVar1 = bRam6000007c;
      if (i < 0x1f) {
        bVar1 = *(byte *)(i + 0x6000007d);
      }
      *(byte *)(i + 0x6000007c) = bVar1 ^ *(byte *)(i + 0x6000007c);
      *(byte *)(i + 0x6000007c) = *(byte *)(i + 0x6000007c) ^ 0xff;
    }
    FUN_80001278((int *)0x6000009c,(undefined *)0x6000007c,(short *)0x60000000,_DAT_80003990,
                 iRam80003994);
    uVar4 = 0;
  }
  else {
    uVar4 = 0xffffffff;
  }
  return uVar4;
}


void AES_ENCRYPT(int param_1,int param_2,int param_3,uint param_4)

{
  uint uVar1;
  undefined8 auStack_c0 [2];
  undefined key [176];
  
  FUN_800002b0(param_2,(int)key);
  for (uVar1 = 0; uVar1 < param_4; uVar1 += 0x10) {
    FUN_80003782(auStack_c0,(undefined8 *)(param_1 + uVar1),0x10);
    aes((int)auStack_c0,(int)key);
    FUN_80003782((undefined8 *)(param_3 + uVar1),auStack_c0,0x10);
  }
  return;
}


void AES_ENCRYPT(int param_1,int param_2,int param_3,uint param_4)

{
  uint uVar1;
  undefined8 auStack_c0 [2];
  undefined key [176];
  
  FUN_800002b0(param_2,(int)key);
  for (uVar1 = 0; uVar1 < param_4; uVar1 += 0x10) {
    FUN_80003782(auStack_c0,(undefined8 *)(param_1 + uVar1),0x10);
    aes((int)auStack_c0,(int)key);
    FUN_80003782((undefined8 *)(param_3 + uVar1),auStack_c0,0x10);
  }
  return;
}

写出EXP。

from regadgets import *
enc = bytes.fromhex('63 D4 DD 72 B0 8C AE 31 8C 33 03 22 03 1C E4 D3 C3 E3 54 B2 1D EB EB 9D 45 B1 BE 86 CD E9 93 D8')
print(len(enc))
key = [ 0x2e, 0x35, 0x7d, 0x6a, 0xed, 0x44, 0xf3, 0x4d, 0xad, 0xb9, 0x11, 0x34, 0x13, 0xea, 0x32, 0x4e ]
enc = list(enc)
for i in range(len(enc)):
    enc[31-i] ^= 0xff
    enc[31-i] ^= enc[(32-i) % 32]
    enc[31-i] = ror8(enc[31-i], 3)

aes = AES(key)
dec = aes.decrypt_ecb_block(enc[:16]) + aes.decrypt_ecb_block(enc[16:])
print(dec) # b'SCTF{Wlc_t0_the_wd_oF_IOT_s3cur}'

Reverse

Simple_encryption

enc = list(bytes.fromhex("47953448A41C358864168807146A3912A20A375C075A5660127625128E28"))
for i in range(len(enc)):
    if i % 3 == 2:
        enc[i] ^= 0x55
    if i % 3 == 1:
        enc[i] -= 41
    if i % 3 == 0:
        enc[i] += 31

print(bytes(enc))

ez_debug

调试后直接把enc抄出来,用CyberChef xor 0x17然后再xor_cycle ATRI
flag{y0u_ar3_g0od_@_Debu9}

ezAndroidStudy

按照题目里面的提示慢慢找就行,很有趣的一个题。

drink_TEA

标准TEA,找到key和delta即可。

Ptrace

father程序的这里修改了son程序进行ROR8的参数(4->3),我是直接全部都爆破了一遍,就看到3解出来了。
2024-10-06T18:07:50.png
顺便:
2024-10-07T09:22:07.png

ezencrypt

android题,native里面有个rc4和xor,然后可以解密出来base64(aes加密后的),然后在java里面找decrypt,开一个idea,然后运行一下就秒了。

PangBai 泰拉记(1)

密文异或了两部分,有个反调试,不看反调试检测出来后的逻辑(看下面的else for)

Dirty_flowers

直接看汇编,循环异或dirty_flower,然后随便翻一下找到密文对比
2024-10-06T18:11:42.png
抄出来解密就行

后面没写了,找个时间补上