一些题目和踩坑记录

有的题目似乎还没解出来,以后慢慢补

周四 11月 07 2024
5871 字 · 40 分钟

主站题目

babyRE2

动调一下,顺利看见encrypt函数内容。

C
int __cdecl encrypt(char *a1)
{
  int v2[19]; // [esp+1Ch] [ebp-6Ch] BYREF
  int v3; // [esp+68h] [ebp-20h]
  int i; // [esp+6Ch] [ebp-1Ch]

  v3 = 1;
  qmemcpy(v2, &unk_403040, sizeof(v2));
  for ( i = 0; i <= 18; ++i )
  {
    if ( (char)(a1[i] ^ Buffer[i]) != v2[i] )
    {
      puts("wrong ~");
      v3 = 0;
      exit(0);
    }
  }
  puts("come here");
  return v3;
}

encrypt对比完毕后会执行

C
int __cdecl sub_40159A(char *a1)
{
  unsigned int v1; // eax
  char v3[9]; // [esp+13h] [ebp-15h] BYREF
  int v4; // [esp+1Ch] [ebp-Ch]

  strcpy(v3, "%tp&:");
  v1 = time(0);
  srand(v1);
  v4 = rand() % 100;
  v3[6] = 0;
  *(_WORD *)&v3[7] = 0;
  if ( (v3[(unsigned __int8)v3[5]] != a1[(unsigned __int8)v3[5]]) == v4 )
    return puts("Really??? Did you find it?OMG!!!");
  else
    return puts("I hide the last part, you will not succeed!!!");
}

大体分析一下逻辑,先进wrong加密,再进omg里比对。这是一个假的flag。

flag{fak3_alw35_sp_me!!}

然后进入encrypt里加密一次。与v2比对。

解密脚本:

PYTHON
key = "hahahaha_do_you_find_me?"
arr = [ 14,  13,   9, 
    6,  19, 
    5,  88,  86, 
   62,   6, 
   12,  60,  31, 
   87,  20, 
  107,  87,  89, 
   13]
for i in range(len(arr)):
    print(chr(arr[i]^ord(key[i])),end='')

flag{d07abccf8a410c

最后进入sub_40159A。还差五位flag,正好v3就是五位,猜测v3解密后就是flag。猜测是固定异或一个数。猜测后几位是b37a}

flag{d07abccf8a410cb37a}。然后对了

mod

根据提示,有花指令。输入数据似乎经过base64编码。

PLAINTEXT
2aYcdfL2fS1BTMMF1RSeMTTASS1OJ8RHTJdBYJ2STJfNMSMAYcKUJddp

用String发现的编码表不行。

image-20241226142926272

去除花指令后发现关键部分。

C
*(_BYTE *)(a4 + 4 * (a3 / 3)) = byte_405018[((4 * (*(_BYTE *)(a3 + a2 + 2) & 3)) | *(_BYTE *)(a3 + a2 + 1) & 0x30 | *(_BYTE *)(a3 + a2) & 0xC0) >> 2];
  *(_BYTE *)(a4 + 4 * (a3 / 3) + 1) = byte_405018[((4 * (*(_BYTE *)(a3 + a2) & 3)) | *(_BYTE *)(a3 + a2 + 2) & 0x30 | *(_BYTE *)(a3 + a2 + 1) & 0xC0) >> 2];
  *(_BYTE *)(a4 + 4 * (a3 / 3) + 2) = byte_405018[((4 * (*(_BYTE *)(a3 + a2 + 1) & 3)) | *(_BYTE *)(a3 + a2) & 0x30 | *(_BYTE *)(a3 + a2 + 2) & 0xC0) >> 2];
  *(_BYTE *)(a4 + 4 * (a3 / 3) + 3) = byte_405018[(*(_BYTE *)(a3 + a2 + 2) & 0xC | (4 * *(_BYTE *)(a3 + a2 + 1)) & 0x30 | (16 * *(_BYTE *)(a3 + a2)) & 0xC0) >> 2];

似乎是魔改base64编码算法。

网上找的爆破脚本:

PYTHON
charset = 'ABCDFEGH1JKLRSTMNP0VWQUXY2a8cdefijklmnopghwxyqrstuvzOIZ34567b9+/'
enc = [charset.index(
    i) for i in '2aYcdfL2fS1BTMMF1RSeMTTASS1OJ8RHTJdBYJ2STJfNMSMAYcKUJddp']
res = []


def f(i):
    for a1 in range(32, 128):
        for a2 in range(32, 128):
            for a3 in range(32, 128):
                a = [a1, a2, a3]
                if enc[4 * i] == (((a[2] & 3) << 2) | a[1] & 0x30 | a[0] & 0xC0) >> 2 \
                        and enc[4 * i + 1] == (((a[0] & 3) << 2) | a[2] & 0x30 | a[1] & 0xC0) >> 2 \
                        and enc[4 * i + 2] == (((a[1] & 3) << 2) | a[0] & 0x30 | a[2] & 0xC0) >> 2 \
                        and enc[4 * i + 3] == (a[2] & 0xC | (a[1] << 2) & 0x30 | (a[0] << 4) & 0xC0) >> 2:
                    print(''.join(map(chr, a)), end='')
                    return


for i in range(0, len(enc) // 4):
    f(i)
print()

PaperPlease

简单的异或加密

PYTHON
b = "5c715207e3abed7dfb7c8ea9c82d0e29"
arr = [ 86,   5,  83,  82,   4,   3,  83,  84,   4,  11,
   83,  81,   6,   6,  15,  85,   5,  91,   3,  86,
   14,   7,  87,  14,   1,  13,  86,   0,   4,   6,
   10,  93]
c=0
for i in b:
    print(chr(ord(i)^arr[c]),end='')
    c+=1

ea5ycpp

这是一个brainfuck。

PLAINTEXT
>>>>>>>>>>>>>>>>>>>>>>>>++[<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<<<<<<<<<

就是逐字母移位,可以多输入几个测试一下。

PYTHON
a = [  104, 111, 101, 108, 129, 105, 122,  61,  59, 121,
  107, 115,  56,  57, 123, 112, 123,  72, 115, 124,
  133,  71, 124, 150]
for i in range(0, 24):
    print(chr(a[i]-i-2),end='')

What

大致看了一下,先输入str,再md5加密,统计0的个数×10和出现位置相加等于403.然后v4的四个元素分别是md5中的28,29,30,31下标。尝试爆破。

脚本如下:

PYTHON
import hashlib
import itertools
import string

for combo in itertools.product(string.ascii_lowercase, repeat=6):
    s = ''.join(combo)
    m = hashlib.md5(s.encode()).hexdigest()
    zeros_indices = [i for i, c in enumerate(m) if c == '0']
    v14 = len(zeros_indices)
    v13 = sum(zeros_indices)
    if 10 * v14 + v13 == 403:
        print(s)

爆破出来是ozulmt

check函数被SMC加密。需要知道字符才能解密。

C
void __cdecl check(int a1)
{
  int v1; // eax
  _BYTE v2[33]; // [esp+1Dh] [ebp-6Bh] BYREF
  char Str[50]; // [esp+3Eh] [ebp-4Ah] BYREF
  int k; // [esp+70h] [ebp-18h]
  int j; // [esp+74h] [ebp-14h]
  int i; // [esp+78h] [ebp-10h]
  unsigned int v7; // [esp+7Ch] [ebp-Ch]

  v7 = 0;
  for ( i = 0; i <= 3; ++i )
    v7 += *(unsigned __int8 *)(i + a1);
  srands(v7);
  *(_DWORD *)v2 = 0;
  *(_DWORD *)&v2[29] = 0;
  memset(&v2[3], 0, 4 * (((v2 - &v2[3] + 33) & 0xFFFFFFFC) >> 2));
  printfs(1);
  scanfs(Str);
  checkht(Str);
  for ( j = 0; j <= 31; ++j )
    v2[j] = ASCII[rands(16)];
  v7 = 0;
  for ( k = 0; ; ++k )
  {
    v1 = strlens(Str);
    if ( v1 <= k )
      break;
    if ( Str[k] == v2[k] )
      ++v7;
    else
      v7 += 100;
  }
  if ( v7 == 32 )
    v7 = 2;
  else
    v7 = 3;
  printfs(v7);
}

checkht对格式进行了一些限定,把v2提出来按照给的格式就能还原flag。

flag{a197b847709253a47c41bc7d6d52e69d}

easy_py.cpython-38

注意线程1执行的时候对flag的修改不会似乎同步到线程2,线程2异或的下一个值仍为修改前的值。

PYTHON
Happy = [44, 100, 3, 50, 106, 90, 5, 102, 10, 112]
result = [0]*10
for i in range(9,-1,-2):
    result[i]=Happy[i] ^ i

for i in range(8,-2,-2):
    result[i]=Happy[i] ^ Happy[i+1]

for i in range(0,10):
    print(chr(result[i]),end="")

输入flag以获取flag

观察发现是Electron,搜一下Electron逆向

PLAINTEXT
npm install asar -g
cd apps
asar extract app.asar app //解压拿到源码
asar pack app app.asar //重新打包

就可以拿到源码。然后分析。

但是文件有main.jsc,无法直接查看。发现program.js中含有信息。使用bytenode进行编译,网上搜索不断尝试,还是没能复原

后来继续搜索看见了两位学长的wpimage-20241204152325826

image-20241204162510212

两次题目似乎有些许区别,SSSCTF2024应该加了一个联网获取ip的判断

看来之前尝试逆向main.jsc思路可能不太行,跟着学长博客复现了一遍

先hook一下加密方法,获取一下原文

PLAINTEXT
const { Hash } = require('crypto');
const _update = Hash.prototype.update;
Hash.prototype.update = function (...args) {
    console.log('Hash update', args);
    return _update.apply(this, args);
}

改一下host,再写一个小程序返回数据

PYTHON
from flask import Flask, request, jsonify
json = {"status": "success",
    "country": "China",
    "countryCode": "CN",
    "region": "TT",
    "regionName": "Tianting",
    "city": "NanTianMen",
    "zip": "",
    "lat": 38.8804,
    "lon": 121.529,
    "timezone": "Asia/Shanghai",
    "as": "AS4538 China Education and Research Network Center",
    "query": "210.30.97.133"}

app = Flask(__name__)
#/json
@app.route('/json/', methods=['GET'])
def get_json():
    return jsonify(json)
#run
if __name__ == '__main__':
    app.run(host='127.0.0.1', port=80)

myFor

输入一个矩阵,程序会返回自己输入数据加密后对应值跟理想数据,直接爆破

PLAINTEXT
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6

ezex

异常处理。image-20241206125454569

我是修改了ida跳转逻辑,让ida可以分析异常处理的代码

image-20241206125528393

大意是是奇偶数分别异或。写出解密脚本

PYTHON
Buf2 =[0]*28
Buf2[0] = 341250117
Buf2[1] = 142350161
Buf2[2] = 542393405
Buf2[3] = 926947173
Buf2[4] = 962408270
Buf2[5] = 592914762
Buf2[6] = 456790650
Buf2[7] = 0x413c
str = bytearray()
for i in Buf2:
    str.extend(i.to_bytes(4, 'little'))
for a in range(30, -1, -1):
    if a%2==0:
        str[a]^=a+1
    else:
        str[a]^=str[(a-1)%len(str)]
print(str)
'''DUTCTF{t4y_thrOw_aNd_c@tch!!!}'''

ezre1

main函数那个解密后是假的flag

后来继续看代码发现一个疑似加密的地方

image-20241206144749830

疑似这里才是真正的加密部分,尝试写一个解密脚本。

其中

PLAINTEXT
  if ( rand() % 2 )
      s[i] = ~(s[i] & s[(i + 1) % v3]) & (s[(i + 1) % v3] | s[i]);
    else
      s[i] = (s[i] ^ s[(i - 1) % v3]) + 2 * (s[i] & s[(i - 1) % v3]);

这部分逆向操作需要注意一下。

当rand为奇数时,

Game!

shift+f12查看字符串,然后定位到疑似解密部分

image-20241206230850359

然后就直接SetIp到这里,随后继续执行,获得flag

image-20241206231009310

NSSCTF题目

[GFCTF 2021]wordy

把main函数EB全都nop掉。有点多,尝试一下写个脚本

PYTHON
import idautils
import idc

cur_addr = 0x000055F13C525135
end_addr = 0x000055F13C527100

while cur_addr < end_addr:
    if idc.get_wide_byte(cur_addr) == 0xeb and idc.get_wide_byte(cur_addr + 1) == 0xff:
        print(1)
        idc.patch_byte(cur_addr, 0x90)
    cur_addr += 1

image-20241227181336290

去花之后f5把main函数复制一下,执行一遍。

image-20241227181727162

[GXYCTF 2019]luck_guy

动态调试就可以,把流程走一遍

GXY{do_not_hate_me}

[HZNUCTF 2023 final]虽然他送了我玫瑰花

花指令

PLAINTEXT
text:0040114D                 xor     ebx, ebx
.text:0040114F                 test    ebx, ebx
.text:00401151                 jnz     short near ptr byte_401155
.text:00401153                 jz      short loc_401156

ebx肯定为0,所以nop掉jnz,然后把jz换成jmp

image-20241223170127109

输入数据进不同函数异或。

解密脚本如下:

PYTHON
arr = [ 127, 126,  81, 206, 251,  78, 122,  36, 232, 223,
   89, 113,  38, 202, 225, 108, 134,  33, 204, 245,
   40, 113,  20, 216, 239, 110, 119,  98, 250]
for i in range(len(arr)):
    aa = i%5
    if aa==0:
        arr[i] = arr[i]^0x19
    elif aa==1:
        arr[i] = arr[i]-18
    elif aa==2:
        arr[i] = arr[i]+16
    elif aa==3:
        for cc in range(3):
            arr[i] = arr[i]/2+0x7f*cc
            if 32<=arr[i]<=126:
                break
    elif aa==4:
        for cc in range(3):
            arr[i] = ~(arr[i]+0x80*cc)
            if 32<=arr[i]<=126:
                break
print(len(arr))
for i in range(len(arr)):
    print(chr(int(arr[i])&0xff), end='')

[SWPUCTF 2021 新生赛]老鼠走迷宫(主站BabyMaze)

Pyc反编译

用pyinstxtractor对程序处理,得到5.pyc,随后用www.pylingual.io进行反编译,但是得到代码不完整,随后使用uncompyle6,却得到了完整的代码。(奇奇怪怪

分析代码,发现1为墙壁0为路,要找到最短路径。(搜一下就能解决但是还是假装研究一下吧)

[MoeCTF 2022]chicken_soup

花指令,将两个地方修改一下就可以继续用ida了。

随后分析对数据加密的两个函数

C
unsigned int __cdecl sub_D51080(const char *a1)
{
  unsigned int result; // eax
  unsigned int i; // [esp+18h] [ebp-8h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( i >= strlen(a1) )
      break;
    a1[i] = (16 * a1[i]) | ((int)(unsigned __int8)a1[i] >> 4);
  }
  return result;
}
C
void __cdecl sub_D51000(const char *a1)
{
  unsigned int i; // [esp+18h] [ebp-8h]

  for ( i = 0; i < strlen(a1) - 1; ++i )
    a1[i] += a1[i + 1];
}

先逆向第一个过程,第一个过程将数据左移四位后和右移四位进行或运算。(实际上似乎是将十六进制的数字两位进行了交换,例如0xCD变成了0xDC),再写一次就可以交换回来。

第二个过程倒着减一下。

解密代码:(获取data时使用shift+E可以一键提取)

C
#include <stdio.h>
int main(){
    unsigned char data[] = {
        0xCD, 0x4D, 0x8C, 0x7D, 0xAD, 0x1E, 0xBE, 0x4A,
        0x8A, 0x7D, 0xBC, 0x7C, 0xFC, 0x2E, 0x2A, 0x79,
        0x9D, 0x6A, 0x1A, 0xCC, 0x3D, 0x4A, 0xF8, 0x3C,
        0x79, 0x69, 0x39, 0xD9, 0xDD, 0x9D, 0xA9, 0x69,
        0x4C, 0x8C, 0xDD, 0x59, 0xE9, 0xD7
    };
    for(int i=0;i<38;i++){
        unsigned char b = data[i];
        data[i] = b<< 4 | (b >> 4);
    }
    for(int i=36;i>=0;i--){
        data[i] -=data[i+1];
    }
    printf("%s",data);
}

[NISACTF 2022]string

分析代码,前面好多都是干扰,实际上真正起作用的只有几行

C
srand(seed);
  printf("NSSCTF{");
  for ( m = 0; m < v10; ++m )
  {
    v4 = rand();
    printf("%d", (unsigned int)(v4 % 8 + 1));
  }
  putchar(125);

有一个小坑是必须要linux写出来的程序得到的才是正确的答案,win的写出来是错的。因为这个rand依赖于libc版本

[HGAME 2023 week1]easyenc

main函数里就有核心代码

C
v4 = -1i64;
  do
    ++v4;
  while ( *((_BYTE *)v10 + v4) );
  if ( v4 == 41 )
  {
    while ( 1 )
    {
      v5 = (*((_BYTE *)v10 + v3) ^ 0x32) - 86;
      *((_BYTE *)v10 + v3) = v5;
      if ( *((_BYTE *)v8 + v3) != v5 )
        break;
      if ( ++v3 >= 41 )
      {
        v6 = "you are right!";
        goto LABEL_8;
      }
    }
    v6 = "wrong!";

有一个地方需要注意一下就是IDA给的伪代码不完全准确,导致v8显示的数据不全,

image-20241111220925083

image-20241111220952998

可以看到F9这个数据没有被IDA伪代码状态下显示出来。

[NSSRound#3 Team]jump_by_jump

image-20241113201117900

shift+f12居然直接看到了flag。不过假装自己没看见,分析一下。

[CISCN 2022 东北]easycpp

main函数里写了加密逻辑,enc[0]^enc[1],1异或2,2异或3,然后下一轮1异或2。只需要反向异或就行。附上解密代码。

C
#include <stdio.h>
int main(){
	unsigned char enc[] =
	{
		0x0A, 0x0B, 0x7D, 0x2F, 0x7F, 0x67, 0x65, 0x30, 0x63, 0x60, 
		0x37, 0x3F, 0x3C, 0x3F, 0x33, 0x3A, 0x3C, 0x3B, 0x35, 0x3C, 
		0x3E, 0x6C, 0x64, 0x31, 0x64, 0x6C, 0x3B, 0x68, 0x61, 0x62, 
		0x65, 0x36, 0x33, 0x60, 0x62, 0x36, 0x1C, 0x7D
	};
	for(int i=34;i>=0;i--){
		enc[i+2]^=enc[i+3];
		enc[i+1]^=enc[i+2];
		enc[i]^=enc[i+1];
	}
	printf("%s",enc);
}

[CISCN 2023 初赛]babyRE

xml给了一个网站,访问网站导入xml。我写了一个py脚本导出加密数据。

PYTHON
import xml.dom.minidom as minidom
dom=minidom.parse('[CISCN 2023 初赛]babyRE.xml')
root=dom.documentElement
block = root.getElementsByTagName('l')
last = block[3].firstChild.data
enc = []
print(len(block))
for i in range(0, len(block),2):
    print(block[i+1].firstChild.data)
    if block[i+1].firstChild.data!=last:
        enc.insert(int(block[i+1].firstChild.data)-1, block[i].firstChild.data)
    else:
        enc.append(block[i].firstChild.data)
print(enc)

随后分析一下加密过程。

image-20241119121800750

这里注意一下当i=1的时候第0个字符似乎会默认为0处理,也就是test第一个元素没有做处理。

解密脚本:

PYTHON
enc=['102', '10', '13', '6', '28', '74', '3', '1', '3', '7', '85', '0', '4', '75', '20', '92', '92', '8', '28', '25', '81', '83', '7', '28', '76', '88', '9', '0', '29', '73', '0', '86', '4', '87', '87', '82', '84', '85', '4', '85', '87', '30']
for i in range(0,len(enc)):
    enc[i] = int(enc[i])
for i in range(0,len(enc)-1):
    enc[i+1]^=enc[i]
for i in range(0,len(enc)):
    print(chr(enc[i]),end='')

[LitCTF 2023]程序和人有一个能跑就行了

[HGAME 2023 week2]math

C
v11[5 * i + j] += *((char *)&savedregs + 5 * i + k - 368) * v10[5 * k + j];

关键代码,可以理解成

C
v11[5 * i + j] += input1[5 * i + k] * v10[5 * k + j];

其中i,j,k均属于0到4,且原始v11为0

得出结果后跟v12比较,即v12为最终结果

可知v12[0]的值是当i和j都为0时的解,k=1,2,3,4以此类推

随后用z3解方程。

[LitCTF 2023]snake

pyc文件,与以往不同的是需要改头文件的Magic Number。

PYTHON
enum PycMagic {
    MAGIC_1_0 = 0x00999902,
    MAGIC_1_1 = 0x00999903, /* Also covers 1.2 */
    MAGIC_1_3 = 0x0A0D2E89,
    MAGIC_1_4 = 0x0A0D1704,
    MAGIC_1_5 = 0x0A0D4E99,
    MAGIC_1_6 = 0x0A0DC4FC,

    MAGIC_2_0 = 0x0A0DC687,
    MAGIC_2_1 = 0x0A0DEB2A,
    MAGIC_2_2 = 0x0A0DED2D,
    MAGIC_2_3 = 0x0A0DF23B,
    MAGIC_2_4 = 0x0A0DF26D,
    MAGIC_2_5 = 0x0A0DF2B3,
    MAGIC_2_6 = 0x0A0DF2D1,
    MAGIC_2_7 = 0x0A0DF303,

    MAGIC_3_0 = 0x0A0D0C3A,
    MAGIC_3_1 = 0x0A0D0C4E,
    MAGIC_3_2 = 0x0A0D0C6C,
    MAGIC_3_3 = 0x0A0D0C9E,
    MAGIC_3_4 = 0x0A0D0CEE,
    MAGIC_3_5 = 0x0A0D0D16,
    MAGIC_3_5_3 = 0x0A0D0D17,
    MAGIC_3_6 = 0x0A0D0D33,
    MAGIC_3_7 = 0x0A0D0D42,
    MAGIC_3_8 = 0x0A0D0D55,
    MAGIC_3_9 = 0x0A0D0D61,
};

注意一下用010改的时候要反着写,例如0x0A0D0D42改成image-20241125181911770

[WUSTCTF 2020]Cr0ssfun

直接明文给了flag,image-20241125182448740

[CISCN 2021初赛]babybc

查了一下bc文件,应该先用

BASH
clang output.bc -o program

生成可执行文件

C
v27 = 0LL;
  do
  {
    v24 = v27;
    memset(v29, 0, sizeof(v29));
    v25 = &v29[(unsigned __int8)map[5 * v27]];
    if ( *v25
      || (*v25 = 1, v23 = &v29[(unsigned __int8)map[5 * v27 + 1]], *v23)
      || (*v23 = 1, v2 = &v29[(unsigned __int8)map[5 * v27 + 2]], *v2)
      || (*v2 = 1, v1 = &v29[(unsigned __int8)map[5 * v27 + 3]], *v1)
      || (*v1 = 1, v29[(unsigned __int8)map[5 * v27 + 4]]) )
    {
      v22 = 0;
      return v22 & 1;
    }
    ++v27;
  }

这是在检查同一行元素是否相同

总体规则是:输入25个数字(1-5),填充到5*5map中。每行每列元素不同。

  • row[4 * v16] 对应于 map[5 * v16]map[5 * v16 + 1] 的比较。

  • row[4 * v16 + 1] 对应于 map[5 * v16 + 1]map[5 * v16 + 2] 的比较,以此类推。

  • 如果

    PLAINTEXT
    row[x] == 1
    • 当前元素必须小于下一个元素(递增)。
  • 如果

    PLAINTEXT
    row[x] == 2
    • 当前元素必须大于下一个元素(递减)。

col是相反的。

PYTHON
import z3
row = [0, 0, 0, 1, 1, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0]
col = [0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]
r=5
c=5
s = z3.Solver()
m = [[z3.Int(f"m_{i}_{j}") for j in range(c)] for i in range(r)]
print(m)
s.add(m[2][2]==4,m[3][3]==3)
for i in range(r):
    for j in range(c):
        s.add(1 <= m[i][j], m[i][j] <= 5)
for i in range(r):
    for j in range(c):
        for k in range(j+1, c):
            s.add(m[i][j] != m[i][k])
for i in range(c):
    for j in range(r):
        for k in range(j+1, r):
            s.add(m[j][i] != m[k][i])
for i in range(20):
    if row[i] == 1:
        s.add(m[i//4][i%4] > m[i//4][i%4+1])
    if row[i] == 2:
        s.add(m[i//4][i%4] < m[i//4][i%4+1])
for i in range(20):
    if col[i] == 1:
        s.add(m[i//5][i%5] < m[i//5+1][i%5])
    if col[i] == 2:
        s.add(m[i//5][i%5] > m[i//5+1][i%5])
if s.check() == z3.sat:
    model = s.model()
    for i in range(r):
        for j in range(c):
            print(model[m[i][j]], end='')
else:
    print("No solution found.")
'''
把给的数字换成0
14253
53142
35021
21504
42315
'''

map给了两个数字要换成0因为fill_number里

PYTHON
v11 = v14;
    v12 = 5 * v14;
    v13 = *(_BYTE *)(a1 + 5 * v14);
    if ( map[5 * v14] )
    {
      v10 = 0;
      if ( v13 != 48 )
        return v10 & 1;
    }
    else
    {
      map[5 * v14] = v13 - 48;
    }

若map数据非0,则必须输入为0,否则程序退出。

[MoeCTF 2021]baby_bc

先生成可执行文件。

image-20241126094420931

分析加密逻辑。本题采用RC4加密。先输入长度40的v9。在进行RC4加密。然后进入HSencode。解密则需要先逆向HSencode的过程,再想办法得到密钥流然后直接异或就得到原文了。

[HNCTF 2022 WEEK2]来解个方程?

考察z3使用。

PYTHON
from z3 import *
s = z3.Solver()
v=[Int('v%d' % i) for i in range(0,50)] #批量添加变量
s.add() #添加约束条件
s.check() #判断是否添加的约束条件是否有解,有解返回sat,无解返回unsat
a = s.model() #求解
for i in v:
    if a[i] is not None:
        print(chr(a[i].as_long()), end='') #打印字符

[CISCN 2022 东北]happymath

输入长度为32的字符串写入byte_7FF73623AC30中,

ISCTF2024新生赛题目

MIPS

需要先把文件编译一下。

Linux安装一下

BASH
sudo apt install gcc-mips-linux-gnu

然后

BASH
 mips-linux-gnu-gcc -o MIPS MIPS.s

就可以使用ida进行分析了(ida9才可以反编译)

PYTHON
v3 = [104,74,120]
arr = [214,  50,  39,  78, 119, 202, 213, 118, 163,  33,
    2,   9, 104,   9,  69, 137,  54, 154, 186, 144,
  236,  71, 152, 248,  254, 240,0x4b]
v5 = [17,43,26, 1,32,  17,  7, 36,  18,   1, 37,  22]
print(len(v5))
for i in range(len(v5)):
    v5[i] = chr(v5[i] ^ v3[i%3])
print(v5)
key = v5
def gen(key):
    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + ord(key[i % len(key)])) % 256
        tmp = s[i]
        s[i] = s[j]
        s[j] = tmp
    i = j = 0
    data = []
    for _ in range(50):
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        tmp = s[i]
        s[i] = s[j]
        s[j] = tmp
        data.append(s[(s[i] + s[j]) % 256])
    return data
key = gen(key)
enc = arr
c=0
for i in range(len(enc)):
    enc[i]^=key[c]^82
    c+=1
print(bytes(enc))

魔改RC4,多异或了一个82,然后key被异或修改了。

《回忆安魂曲》—第二章:当记忆被割裂

这是一个ELF文件,我采用IDA远程wsl进行动态调试

IDA配置远程调试

在wsl下使用

BASH
./linux_server64

参考文档:IDA远程动态调试(linux & Windows)_ida远程调试-CSDN博客

配置好动调之后,在String找到了加密的key:

i_can_reverse_but_i_can_not_have_you

image-20241111222544679

找到函数但是发现IDA无法生成伪代码

image-20241111222638795

需要把这个改成jmp rax之后可以看到伪代码

image-20241111222712637

总共好几个加密函数,通过动态调试可以知道执行顺序,随后写出解密脚本如下:

PYTHON
# 解密函数
def decrypt_data(encrypted_data, key):
    decrypted_data = []

    for i, enc_byte in enumerate(encrypted_data):
        key_byte = key[i]
        enc_byte ^= key_byte+i
        enc_byte -= 6
        enc_byte ^= (i + 102) ^ 0x52
        decrypted_data.append(enc_byte)

    return decrypted_data


# 示例加密后的数据
encrypted_data = [
    0xEA, 0x0C, 0x1A, 0x11, 0xF6, 0x2C, 0x1D, 0x3E, 0x17, 0x35,
    0x31, 0x29, 0xF4, 0x39, 0x39, 0xD3, 0xC3, 0x2D, 0x00, 0x10,
    0x30, 0x3D, 0xCC, 0x00, 0xD3, 0xC0, 0x4B, 0xC6, 0x11, 0xC7,
    0x29, 0x3E, 0xBA, 0x60, 0x90, 0x34
]  # 假设这是加密后的字节

# 密钥
key = [0x69, 0x5F, 0x63, 0x61, 0x6E, 0x5F, 0x72, 0x65, 0x76, 0x65,
       0x72, 0x73, 0x65, 0x5F, 0x62, 0x75, 0x74, 0x5F, 0x69, 0x5F,
       0x63, 0x61, 0x6E, 0x5F, 0x6E, 0x6F, 0x74, 0x5F, 0x68, 0x61,
       0x76, 0x65, 0x5F, 0x79, 0x6F, 0x75]

# 恢复加密前的数据
decrypted_data = decrypt_data(encrypted_data, key)

# 打印恢复后的数据(以十六进制表示)
print([hex(byte) for byte in decrypted_data])
for i in decrypted_data:
    print(chr(i), end='')

《回忆安魂曲》—第三章:逃不出的黑墙

很经典,就是上面的走迷宫问题

找啊找

问了大佬才知道这个是有壳的。用DIE查壳发现有壳image-20241114232843873

但是直接用upx -d解不出来。网上搜了一下,这个文件好像区段名被修改。(贴一个参考网页UPX防脱壳机脱壳、去除特征码、添加花指令小探 - 吾爱破解 - 52pojie.cn

image-20241114233409112

这里把APK改成UPX,随后就可以upx -d解出来了。

随后有一个小坑就是main里边的unk_40040在程序执行时被悄悄更改了值,动调可以看出来。拿到最后flag

PYTHON
def reverse(s):
    res = ""
    for i in s:
        if i.islower():
            res += i.upper()
        elif i.isupper():
            res += i.lower()
        else:
            res += i
    return res
s = [0x50, 0x4A, 0x5A, 0x4D, 0x5F, 0x42, 0x7E, 0x76, 0x76, 0x5D,
		0x18, 0x18, 0x08, 0x4A, 0x56, 0x66, 0x60, 0x56, 0x4C, 0x66,
		0x5F, 0x08, 0x57, 0x5D, 0x66, 0x08, 0x6D, 0x66, 0x54, 0x79,
		0x50, 0x57, 0x18, 0x18, 0x79, 0x44]
for i in s:
    i^=0x39
    print(reverse(chr(i)),end="")

桀桀桀

比赛时没有做出来,赛后看看网上的wp复现一遍。

先使用z3解方程,答案是ISCTF。

第二步魔改Tea(注意key被TLS改了)。

存在jz/jnz花指令,nop掉

C
int __cdecl tea_0(unsigned int *a1, int *a2)
{
  int result; // eax
  int v3; // [esp+D0h] [ebp-68h]
  int v4; // [esp+DCh] [ebp-5Ch]
  int v5; // [esp+E8h] [ebp-50h]
  int v6; // [esp+F4h] [ebp-44h]
  int i; // [esp+100h] [ebp-38h]
  int v8; // [esp+10Ch] [ebp-2Ch]
  int v9; // [esp+118h] [ebp-20h]
  unsigned int v10; // [esp+124h] [ebp-14h]
  unsigned int v11; // [esp+130h] [ebp-8h]

  v11 = *a1;
  v10 = a1[1];
  v9 = 0;
  v8 = -1640531527;
  v6 = *a2;
  v5 = a2[1];
  v4 = a2[2];
  v3 = a2[3];
  for ( i = 0; i < 32; ++i )
  {
    v9 += v8;
    v11 += (v5 + (v10 >> 5)) ^ (v9 + v10) ^ (v6 + 16 * v10);
    v10 += (v3 + (v11 >> 5)) ^ (v9 + v11) ^ (v4 + 16 * v11);
    if ( i == 15 )
    {
      v6 = a2[2];
      v5 = a2[3];
      v4 = *a2;
      v3 = a2[1];
    }
    else if ( i == 23 )
    {
      v8 = 1131796;
    }
  }
  *a1 = v11;
  result = 4;
  a1[1] = v10;
  return result;
}

然后第三步:

PLAINTEXT
rand();
v9 = sub_41126C();
enc2(Str, v9);

注意这个代码,对应汇编如下。

ASM
.text:004121D6                 call    ds:rand
.text:004121DC                 cmp     esi, esp
.text:004121DE                 call    sub_41126C
.text:004121E3                 mov     [ebp+var_74], eax
.text:004121E6                 mov     eax, [ebp+var_74]
.text:004121E9                 push    eax             ; int
.text:004121EA                 lea     ecx, [ebp+Str]
.text:004121ED                 push    ecx             ; Str
.text:004121EE                 call    enc2

如果sub_41126C未返回值的话那么v9就应该是rand的返回值,ida分析有误。

再来看enc2的内容

C
void __cdecl sub_411B90(char *Str, int a2)
{
  char v2; // cl
  signed int v3; // [esp+D0h] [ebp-2Ch]
  signed int v4; // [esp+DCh] [ebp-20h]

  v3 = 0;
  v4 = j_strlen(Str);
  while ( v3 < v4 )
  {
    if ( v3 % 2 )
      v2 = (a2 >> 6) ^ Str[v3];
    else
      v2 = (a2 - ((unsigned __int8)(a2 >> 6) << 6)) ^ Str[v3];
    Str[v3++] = v2;
  }
}

对str进行了奇偶分组异或操作。

解密操作:先奇偶分组异或,然后Tea解密。

解密脚本

C
#include <iostream>
#include <string.h>
#include <stdlib.h>
void DeTea(unsigned char* enc, uint32_t* key, uint32_t XorNum) {
	uint32_t k1, k2, k3, k4;
	uint32_t sum, delta;
	uint32_t v1, v2;
	uint32_t* flag = NULL;
	int index = 0;
	int c = 0;
	flag = (uint32_t*)enc;
	int key1, key2, len;
	int i = 0;
	key1 = XorNum >> 6;
	key2 = XorNum - (key1 << 6);
	len = 32;
	for (i=0; i < len; i++) {
		if (i % 2) {
			enc[i] ^= key1;
		}
		else {
			enc[i] ^= key2;
		}
	}
	k1 = key[2], k2 = key[3], k3 = key[0], k4 = key[1];
	for (c = 0; c < 4; c++) {
		delta = 0x114514;
		k1 = key[2], k2 = key[3], k3 = key[0], k4 = key[1];
		sum = delta * 8 + 24 * 0x9e3779b9;
		v1 = flag[c * 2];
		v2 = flag[c * 2 + 1];
		for (index=0; index < 32; index++) {
			v2 -= ((v1 << 4) + k3) ^ (v1 + sum) ^ ((v1 >> 5) + k4);
			v1 -= ((v2 << 4) + k1) ^ (v2 + sum) ^ ((v2 >> 5) + k2);
			sum -= delta;
			if (index == 7) {
				delta = 0x9e3779b9;
			}
			else if (index == 15) {
				k1 = key[0];
				k2 = key[1];
				k3 = key[2];
				k4 = key[3];
			}
		}
		flag[c * 2] = v1;
		flag[c * 2 + 1] = v2;
	}
	printf("%s\n",flag);
}

int main(){
	unsigned char enc[60]={
		177, 210, 249, 122, 131,  76,  81,  35, 183, 173, 
		169, 190, 232, 250,  36,  22, 147, 254,  66, 215, 
		176,  31,  82, 247,  90, 125, 128, 232,  40, 252, 
		65, 111
	}; 
	int Seed=0;
	char v10[6] = "ISCTF";

	for (int i = 0; i < 5; ++i )
		Seed += v10[i] ^ 0xA1;
	uint32_t key[4] = {28519, 26994, 26740, 29549};
	for(int i=0;i<4;i++){
		key[i]^=0xA1u;
	}
	srand(Seed);
	int xornum = rand();
	DeTea(enc,key,xornum);
	printf("%s",enc);
}

注意一下int Seed=0。一开始没让它=0,导致一直不行。

《回忆安魂曲》—第四章:退!初识凤仙儿

赛时也没做出来,赛后复现一遍。

cpython逆向

查看一下有什么

PLAINTEXT
>>> dir(test)
['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__test__', 'check', 'hack_data', 'key', 'say', 'sbox']

然后

PLAINTEXT
test.key H1m
test.hack_data
[27, 16, 43, 29, 127, 46, 51, 102, 50, 1, 112, 50, 53, 101, 57, 1]
test.sbox
[1, 14, 4, 13, 10, 2, 5, 8, 7, 6, 9, 12, 15, 3, 11, 0]

接着使用ida分析,大致猜测先sbox换序再异或。

解密脚本:

PYTHON
key = "H1m"
hack_data=[27, 16, 43, 29, 127, 46, 51, 102, 50, 1, 112, 50, 53, 101, 57, 1]
sbox=[1, 14, 4, 13, 10, 2, 5, 8, 7, 6, 9, 12, 15, 3, 11, 0]
res=[0]*16
for i in range(len(hack_data)):
    hack_data[i] ^= ord(key[i%3])
for i in range(len(hack_data)):
    res[sbox[i]]=hack_data[i]
for i in range(len(hack_data)):
    print(chr(res[i]),end='')

ISCTF{I_WANT_U!}

ISCTF2023新生赛题目

FlowerRSA

首先有一个花指令

PLAINTEXT
  xor     rbx, rbx
.text:0000000000001268                 test    rbx, rbx
.text:000000000000126B                 jnz     short labela
.text:000000000000126D                 jz      short labelb

rbx值肯定为0,所以肯定会跳转到lableb。把无效部分nop掉。

image-20241127161133968

输入52位的v7,然后分成13组传给s2n函数。比较每一组的465次方mod3162244531与c是否相等。

进行了RSA加密运算。使用yafu进行分解质因数。

PLAINTEXT
factor(3162244531)

代码如下

PYTHON
c =[1966878405,2375075638,  2166893744,  2129446000,  2488145363,  746243878,  1904115824,  818668601,  2983811740,  1840670651,  306202172,  2009857636,  299417177]
e = 465
q = 56369
p = 56099
n = p*q
phi = (p-1)*(q-1)
d = pow(e,-1,phi)
flag = ''
for i in c:
    flag+=(pow(i,d,n).to_bytes(4,'big').decode('utf-8'))
print(flag)

注意to_bytes可以把数字转成字符,然后decode把bytes转成str格式。方便拼接flag。

ezrust

拖入IDA分析,一开始有很多变量无法识别,需要F5多刷新几下。

HBCTF 部分题目

ezre

sm4加密算法。找一个解密脚本

Newstar 2024

PangBai 泰拉记(1)

反调试,关键在于result

image-20241117160022419

当检测到调试时,result的值变成1,执行if语句中的内容。把key的值改变。从而下一步产生错误flag

应该手动让它执行else语句,从而获得正确key值。拿到正确flag。

011vm

反编译之后很复杂,直接看题解了。

取啥名好呢?

根据题目提示,推测应该是程序转移到异常处理程序image-20241119212147860

发现疑似数据的东西

SMc_math

关键在于encrypt函数被加密了,运行时才会被解密。

可以打一个断点image-20241120111900633

在解密完成时打断点然后恢复函数内容。

image-20241120112014955

重新分析代码

image-20241120112133121

C
_BOOL8 __fastcall encrypt(unsigned int *a1)
{
  __int64 v2; // [rsp+10h] [rbp-38h]
  __int64 v3; // [rsp+18h] [rbp-30h]
  __int64 v4; // [rsp+20h] [rbp-28h]
  __int64 v5; // [rsp+28h] [rbp-20h]
  __int64 v6; // [rsp+30h] [rbp-18h]
  __int64 v7; // [rsp+38h] [rbp-10h]
  __int64 v8; // [rsp+40h] [rbp-8h]

  v2 = *a1;
  v3 = a1[1];
  v4 = a1[2];
  v5 = a1[3];
  v6 = a1[4];
  v7 = a1[5];
  v8 = a1[6];
  return 5 * (v3 + v2) + 4 * v4 + 6 * v5 + v6 + 9 * v8 + 2 * v7 == 0xD5CC7D4FFLL
      && 4 * v8 + 3 * v5 + 6 * v4 + 10 * v3 + 9 * v2 + 9 * v7 + 3 * v6 == 0x102335844BLL
      && 9 * v6 + 4 * (v5 + v4) + 5 * v3 + 4 * v2 + 3 * v8 + 10 * v7 == 0xD55AEABB9LL
      && 9 * v3 + 5 * v2 + 9 * v8 + 2 * (v4 + 2 * v5 + 5 * v6 + v7) == 0xF89F6B7FALL
      && 5 * v6 + 9 * v5 + 7 * v2 + 2 * v3 + v4 + 3 * v8 + 9 * v7 == 0xD5230B80BLL
      && 8 * v8 + 6 * v5 + 10 * v4 + 5 * v3 + 6 * v2 + 3 * v7 + 9 * v6 == 0x11E28ED873LL
      && v2 + 4 * (v4 + v3 + 2 * v5) + 9 * v6 + v7 + 3 * v8 == 0xB353C03E1LL;
}

得到这个,需要解方程。用

ezre1


Thanks for reading!

一些题目和踩坑记录

周四 11月 07 2024
5871 字 · 40 分钟