Vn1c0rn的逆向之旅

Study

1、 ida

  • shift f12---跳转string

  • ctrl x------交叉引用

  • shift e-----数据导出

  • a-----------转化为字符串数组

  • \------------去除灰色的变量解释

  • /------------添加注释

  • N-----------重命名

  • ctrl z------返回上一步, 撤回原行为

  • Y-----------重组数组(更改变量类型)

  • Alt b -------搜索bytes string

  • *(shift+8)------------构建数组(记得取消dup格式)

  • u-------------取消定义

  • _-------------把数值转换成无字符型

2、凯撒密码

可带入数值判断偏移量, 解密(注意大小写的条件可能不同)

3、RSA

ctf工具中公钥分析的应用, 需含begin end

4、x64dbg

按如图所示找到程序入口点(OEP)

选中(OEP)后按 shift d-------调用字符串界面

5、汇编代码转换成二进制可执行文件

linux下gcc安装

sudo apt install gcc

gcc -o name(可执行文件名) name.s(汇编代码)

6、打包

(1)upx使用

upx -v //查看command

upx --help //查看detail

(2)pyinstxtractor.py使用

用于脱pyinstaller

python3 pyinstxtractor.py path/file_name

脱壳后文件将于当前路径下生成

pyc文件转py

1、安装.pyc.py所使用的库uncompyle6, 这里用了清华源并且指定使用了3.9.0版本库

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple uncompyle6==3.9.0

找到测试脚本.pyc所在的文件夹, cd 到此路径下, 并输入以下打包命令:

uncompyle6 -o 测试脚本.py 测试脚本.pyc

注意:uncompyle6针对的是3.8及以下的文件, 对高版本不适用

解决方案:可以找到uncompyle6识别版本的位置(uncompyle6报错路径)手动补上新版本号。

2、pycdc的使用

pycdc -o 测试脚本.py 测试脚本.pyc

7、加密函数

搜索一下(CryptCreateHash)是哈希算法的意思, 且0x8004表示sha1方法

这些粉红色的东东是Windows API

8、ida换编码

类似这种输出一个&byte_的, 有可能是因为有中文。

Option---general---strings---Default 8-bit由UTF-8换成另一个, 重启ida, 注意load exsite, id64存在编码方式不变。

尝试(1)不重启, 按f5.

(2)选中对应位置(注意带上‘0’)按a, 此时才能把中文带到伪代码中, 不加‘0’可能带不到伪代码中。

有的中文换编码后也没发现, 找到对应位置选中, 右键即可看到内容, 如图

9、花指令---jmp

选中此句, 点u

找到多出的语句, 此题为e9那句

直接把e9那句nop掉, 再按c转换成c代码

最后在main函数前面选中点p重新定义成函数

定义后结果

之后就可以反编译啦

时隔很长时间, 终于看懂了这个含义是什么, 打算补一下。

jz  loc_117A+1,代表跳转到117A的第二个字节的位置, 跳过了第一个字节jmp(E9), 而ida会识别出来e9并于后面的机械码配对, 导致反编译错误。

10、dup命令

8  dup(0), 分配一段空间, 由8个0组成

故数组的后8个元素均为0, 而且末尾不存在8这个元素。

11、ida中元素转换为c语言数组常见错误

在clion中用h后缀表示的十六进制会报错, 更不能加上单引号使其强行正确, 应转化为0x前缀格式

字符型:先全选R键转换, 再复制粘贴;

整型:shift  e提取, 再复制   (还有一个好处就是, 无需全选, 只需选中对应行, shift e可以提取该数组的所有值)

12、逆向中py脚本的编写

(1)字符串:

一维:a=' '

二维:b=[”    “,”   “]

(2)将字符转换为ASCLL码值   ord()

将ASCLL码值转变为字符    chr()

(3)py中整除符号://

(4)print()会自动换行, 不换行需加上print('',end=' ')

13、ida识别错误转换问题

很明显是数组指针类型的v3, 故把__int64改成char *

后面的v3[i%3]+2*(i/3)也将改变成二维数组的类型

14、1)迷宫:

找到迷宫(二维数组看成一个迷宫, 三维数组看成多个迷宫)(自定义数组根据题目含义分析)

(1)直接convert---convert string----复制该十六进制数据, 在ida对话框中输出, 即可得到字符

(2)先按u取消定义, 后将其改为Byte型, 根据需要改变类型Byte、word、dword

再按*重组数组, 一般可以知道迷宫的行数或者列数, 估计另一个, 相乘得到数组大小。

再convert to Byte或word dword

附上迷宫脚本

def solve_maze(mp):
    # delta = [y,x,step]
    delta = [
        [0, -1, "a"], [0, 1, "d"], [-1, 0, "w"],
        [1, 0, "s"], [5, 0, "x"], [-5, 0, "y"]  # 操作数需要改
    ]
    path = ""
    N, M = 25, 5  # 长和宽需要改
    st = mp.index("s")  # 需要改 起始位置
    ed = mp.index("#")  # 终点位置
    ID = lambda x, y: x * M + y
    vis = [[False] * M for _ in range(N)]

    def dfs(x, y, cPath=""):
        nonlocal path
        if x < 0 or x >= N or y < 0 or y >= M or vis[x][y] or mp[ID(x, y)] == "*":  # 不可以走的路径
            return False
        if ID(x, y) == ed:
            path = cPath
            return True
        vis[x][y] = True
        for i in range(6):  # 对应delta有几个操作数
            fl = dfs(x + delta[i][0], y + delta[i][1], cPath + delta[i][2])
            if fl:
                vis[x][y] = False
                return True
        vis[x][y] = False
        return False

    dfs(st // M, st % M)
    return path


def main():
    mp = "**************.****.**s..*..******.****.***********..***..**..#*..***..***.********************.**..*******..**...*..*.*.**.*"  # 迷宫的数据
    path = solve_maze(mp)
    print(path)


if __name__ == "__main__":
    main()

2)进阶迷宫:(如果发现地图的路线有多种, 第一种是想想是不是三维迷宫(移动键有6个), 第二种便有可能是此情况, 也可根据代码分析, 虽然有时候我看不出来, 哈哈)

遇到障碍物停止, 跑出地图程序结束。

15、linux文件远程调试

Debugger---process options

Application 路径加文件名(linux虚拟机)(可以只在这行加文件名, 让linux自动检测, 其他行不写任何东西)

Input file  路径加文件名(Linux虚拟机)

Directory 路径(Linux虚拟机)

Hostname  虚拟机ip

升高文件权限  chmod +x  文件名

例如:chmod +x rainbow

16、混淆

调试!!!

让程序跑起来, 之后输入内容, 在内容位置下断点(下断点后按ese返回), 调用该内容时便会停止, 分析该处对输入内容的加密!

17、z3的使用

第一步 创建变量

import z3
x = [z3.Int(f'x_{i}') for i in range(10)]
y = [z3.BitVec(f'y_{i}', 8) for i in range(10)]

第二步 创建求解器

solver = z3.Solver()

第三步 添加约束

solver.add()

第四步 求解方程

if solver.check() == z3.sat:
    print(solver.model())
else:
    print("该方程无解")

如果想按自定义顺序输出, 可以采取下面方法

m=solver.model() 
print(m[x])
print(m[y])

注意其中的m[x]与m[y]不是一个数字类型,而是一个字符串形式的数据,不能直接进行加减运算,可使用m[x].as_long()进行加减计算,转换成字符串也要chr(m[x].as_long())

注意事项:如果约束中出现位移符号,由于创建变量并非为int型,而是z3库的ArithRef类型,不能进行位移,可采取以下方法:

一:等价的方法,左移x位表示乘以2的x次方_

二:位向量转换为字符串

result1 = "".join(chr(m.evaluate(each).as_long()) for each in flag)
result2 = "".join(chr(m.evaluat(flag[i]).as_long()) for i in range(lenth))
print(result)


//最好不要用下面的形式,用evaluate()最好.
for i in flag:
    print(chr(m[i].as_long()),end='')
//或者下面形式
print(''.join(chr(m[flag[i]].as_long())for i in range(len(flag)))

这个8代表设置有8位,这种方法也可以快速设置变量,8位设置后可以对其再次进行约束

两个约束均可, 用其一即可。

哎,对于python的运用,还得多练。

更多用法可参考z3使用

If(condition, true_case, false_case)

错误示例:
from z3 import *

x = Int('x')
y = Int('y')

# 这种方式是错误的, 因为它是在运行时评估的, 而不是构建符号表达式
if x > y:
    result = x
else:
    result = y

# 创建求解器并添加约束
solver = Solver()
solver.add(x == 5, y == 3)

if solver.check() == sat:
    model = solver.model()
    print(f"x = {model[x]}, y = {model[y]}, result = {model.evaluate(result)}")
else:
    print("不可满足")

正确用法:
from z3 import *

# 创建整数变量
x = Int('x')
y = Int('y')

# 使用 Z3 的 If 函数构建条件表达式
result = If(x > y, x, y)

# 创建求解器并添加约束
solver = Solver()
solver.add(x == 5, y == 3)

if solver.check() == sat:
    model = solver.model()
    print(f"x = {model[x]}, y = {model[y]}, result = {model.evaluate(result)}")
else:
    print("不可满足")

18、D810插件使用

shift+ctrl+D-----启动D810

启动后选择对应的混淆, 这里是ollvm混淆, 故选择此项, 点Start, 之后就可以欢快的按F5啦。

19、__gmpz_init_set_str函数

注意最后一个参数, base

21、base64类型思路

换表, 再正常不过, 当直接查看base_table时, 发现是原表, 我们不能知道它有没有换表, 在哪个函数里换表。这时就用到交叉引用啦, 查看一下, 除base64加密函数的另一个调用base_table的函数应该就是换表函数。

在这里就是O_OLookAtYou这个函数啦。

22、ida显示栈指针

23、idapython在脱花上的运用

调用pyscript

(1)file->script file(Alt F7)  "先编写好脚本, 再调用"

(2)file->sript commend(Shift F2)  "于ida中编写脚本"

常用ida的python函数  参考http://mtw.so/61YmOa

example

该代码便将所有的EB FF中的EB给nop掉了, 至于为什么这么做, 请看:

24、汇编指令

rol  左移, 最高位(最左面)补到最低位(最右面)

shr 右移   最右边的直接消失, 最左边补0

25、对于int型的返回给char型, char只读取最低八位

26、change byte的另一种用法(除去花外)

程序对密文encode, 输出为flag, 而程序只改动几个字符并没有全部改动, 目标:“改变encode的字符数”

汇编代码处, 选中数据区,右键->patching->change byte

之后修改成应该的数值就行了。

27、2月26日

1)siglongjmp, sigsetjmp

参考文献:https://blog.csdn.net/m0_61243666/article/details/131386182

2)signal函数

参考文献:https://blog.csdn.net/weixin_45556441/article/details/116279144

signal函数如果出现时很有可能出现了linux异常处理, 需动调观察哪里出现了异常处理, 异常处理后调用函数的作用。

3)ida中变量交叉引用出的函数是按顺序执行的。

28、2月27日

1)tea算法一般会将数据以四个字节的形式加密。一般表示为v1=*flag, v2=*(flag+4)

29、2月28日

1)流密码RC4
魔改注意点:轮数由256改为128;Data[k]=Data[k] ^ s[t]改为Data[k]=Data[k] ^ k ^ s[t]

/*初始化函数*/
void rc4_init(unsigned char *s, unsigned char *key, unsigned long Len) {
    int i = 0, j = 0;
    char k[256] = {0};
    unsigned char tmp = 0;
    for (i = 0; i < 256; i++) {
        s[i] = i;
        k[i] = key[i % Len];
    }
    for (i = 0; i < 256; i++) {
        j = (j + s[i] + k[i]) % 256;
        tmp = s[i];
        s[i] = s[j]; // 交换s[i]和s[j]
        s[j] = tmp;
    }
}

/*加解密*/
void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Len) {
    int i = 0, j = 0, t = 0;
    unsigned long k = 0;
    unsigned char tmp;
    for (k = 0; k < Len; k++) {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        tmp = s[i];
        s[i] = s[j]; // 交换s[x]和s[y]
        s[j] = tmp;
        t = (s[i] + s[j]) % 256;
        Data[k] ^= s[t];
    }
}

解密2 :python解密

form Crypto.Cipher  import ARC4     # Cipher表示对称密码

rc4  = ARC4.new(b  ' ' )            # 'key'
flag  = rc4.decrypt(bytes.fromhex(  ' ' )     # 'convert->hexstring'

解密3 :自吐(ida脚本编写)

from idaapi import *

v  = bytes.fromhex(  ' ' )
#第一个参数是 输入数据的内存地址
#第二个参数是 已经被加密后的数据
patch_bytes( 0x0 , v)
#py小知识:byte、string、元组不能原地修改,list可以

注意运行脚本后可能密文中可能又byte为0, 会影响strlen的判断, 此时就可以用7)的方法修改寄存器的值来解决了

2)ida如何将数据变为无符号型选中后右键->invert sign(快速识别常量)或者选中后按下划线

3)F4运行到光标位置

4)安装python密码库 pip install pycryptodome

5)RAX寄存器存取返回值, 例如strlen(flag)的值将存在RAX中

6)自动填充ida库中函数, 新建系统环境变量, 将ida目录中的python的路径设为变量值

7)在ida动态调试时可以修改寄存器的值:在general registers中选中寄存器右键->Modify value

30、2月29日

1)如果数据类型为dword, >>31与%256是一个性质;如果为word型, >>15与%256是一个性质。(以此就可以贴近上述的rc4代码)。

2)rc4魔改方式1, 将最后的异或操作改为+, -, *, /, 这样自吐与python解法就行不通了。

31、3月1日

1)<stdint.h>库, 含有uint16_t,uint32_t等类型。

2)加密算法一般只改某些特征值, 不会改变加密逻辑(因为可能玩逆向的害怕自己改了加密, 不知道怎么解密了), 所以识别加密算法尤为重要。

3)TEA算法(ida识为什么类型int or uint,再解密时便使用什么类型,如果int型密文报错便(int)强转一下)

#include 

void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;               /* set up */
    uint32_t delta=0x9e3779b9;                         /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];       /* cache key */
    for (i=0; i < 32; 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) {
    uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;      /* set up */
    uint32_t delta=0x9e3779b9;                         /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];       /* cache key */
    for (i=0; i<32; 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;
}

由delta=0x9e3779b9识别, 这个数据也很有可能被魔改

XTEA

#include 

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned     int num_rounds, uint32_t v[2], uint32_t     const key[4]) {
    unsigned     int i;
    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
    for (i=0; i < num_rounds; i++) {
        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
        sum += delta;
        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
    }
    v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
        sum -= delta;
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    }
    v[0]=v0; v[1]=v1;
}

由sum的位置确定, 其中detal的值也很有可能被魔改。

XXTEA

  #define DELTA 0x9e3779b9
  #define MX ((z>>5^y<<2) + (y>>3^z<<4) ^ (sum^y) + (k[p&3^e]^z)) 
 
  long btea(long* v, long n, long* k) {
    unsigned long z=v[n-1], y=v[0], sum=0, e;
    long p, q ;
    if (n > 1) {          /* Coding Part */
      q = 6 + 52/n;
      while (q-- > 0) {
        sum += DELTA;
        e = (sum >> 2) & 3;
        for (p=0; p<n-1; p++) y = v[p+1], z = v[p] += MX;
        y = v[0];
        z = v[n-1] += MX;
      }
      return 0 ; 
    } else if (n < -1) {  /* Decoding Part */
      n = -n;
      q = 6 + 52/n;
      sum = q*DELTA ;
      while (sum != 0) {
        e = (sum >> 2) & 3;
        for (p=n-1; p>0; p--) z = v[p-1], y = v[p] -= MX;
        z = v[n-1];
        y = v[0] -= MX;
        sum -= DELTA;
      }
      return 0;
    }
    return 1;
  }

一般来说,识别可以通过,delta 以及round = 6 + 52/n(sum >> 2) & 3这种特殊的运算来判断。

这里v指密文或者明文:n指round【len(flag)/4】正是加密,负是解密:k指密钥

魔改点:(1)(sum>> 2) & 3改为(sum >> 2)& 5 (2)delta值

将由小端序输入的flag改为正常序

for (int i = 0; i < 8; ++i) {
    printf("%c%c%c%c", (flag[i]) & 0xFF, (flag[i] >> 8) & 0xFF, (flag[i] >> 16) & 0xFF, (flag[i] >> 24) & 0xFF);
}

4)程序里面藏程序?findme你跟我搁这玩Misc呢,在遇到题目特别提示让我们找flag,且程序中的flag都是fake,有个做题新思路,main函数中的buffer变量可能含有着新的exe的二进制,以MZ,90为标志。

5)RC4魔改的点:s[i]=i变为s[i]=256-i(并且在ida中显示可能直接为-i,此时因为变量为char型,所以可以更换,而自己的代码就需要注意是否为char型,例如将Data[k]=s[t]变为Data[k]=s[256-t],此时这个t就是int型,而在ida里面绕来绕去改成char型的变量,直接显示-i)

31、3月4日

(1)汇编指令

CALL与RET为对应的指令, 如果遇到一个变量v6()这种情况, 很可能就是执行了一个call指令, 而v6的数据是RET的机械码, 若v6是输入的数据, 以此可以判断v6的部分数据为RET的机械码。

(2)AES加解密

from Crypto.Cipher import AES
import struct

data = struct.pack("<QQ",   ,  )        //>
key = b''
cipher = AES.new(key, mode=AES.MODE_ECB)
flag = cipher.decrypt(data)
print(flag)

其中struct.pack的目的是将一长串的数据改为多个byte类型的字节, '<'表示的是小端序, ‘Q'表示8个字节, ‘QQ’表示两个数, 都是8字节

(3)如果start函数识别不出main函数了, 一般离call的一个函数最近的那个函数便是main函数

(4)RSA加密

在string中找找关键词, 上面5个库都有可能, 之后google搜索lib..., 找一份可执行文件, 之后将可执行文件拖到ida中分析完成后打包退出, 之后在逆向分析的文件中点file->bindiff, 之后打开上面的打包文件(注意文件目录不能有中文), 出现很多窗口, 在一个绿色的窗口里面选中按shift下滑, 到_60-70左右, 右键impor symbols/comments, 之后返回main函数, f5反编译一下。_

(5)RSA解密

安装大数分析库

3月7日

(1)

该加密的逆向为

	for (int i = 0; i < len; i++)//求解code2
	{
		if (str[i] < 'A' || str[i]>'Z')
		{
			if (str[i] < 'a' || str[i]>'z')
			{
				if (str[i] < '0' || str[i]>'9')
					code2[i] = str[i];
				else
					code2[i] = (str[i] - 48 + 7) % 10 + 48;
			}
			else
			{
				code2[i] = (str[i] - 97 + 23) % 26 + 97;
			}
		}
		else
		{
			code2[i] = (str[i] - 65 + 23) % 26 + 65;
		}

3月13日

1)vm初体验

buuoj  网鼎杯singal 参考视频http://mtw.so/5FtEha、

4月28日

1、

application中的android:name将比主函数先运行, 可以先进去看看, 很可能不在主函数中, 而在这个函数中

而到这里后我就看不懂什么意思了, 但是主函数中的又是fake flag;后来看了wp才知道软件运行后会生成一个shell.apk,而这个路径在

直线位置是数据所在路径而后面这个我就不确定了, 也不知道是不是固定的。

5月18日

1、随机数逆向

1)相同时间戳的随机数的结果可能相同也可能不同。

2)目前遇到的题型有:srand(time(0)&0xff)

这里注意使用for i->0xff爆破, 而不是用time(0)&0xff。

5月19日

1)lua语言反编译(一般都是源码, 只有少数会编译)

unluac下载链接https://sourceforge.net/projects/unluac/files/latest/download

java -jar D:\ctf\ctf_gj\CTF_gj_Re\rehb\unluac.jar file_name>out.lua

5月20日

1)十六进制代码转程序

将上面的十六进制代码直接复制到cyberchef中, From Hex, (无需在意0x0与0x00不同, 新版cyberchef会自动填充), 之后点击图示位置保存文件

注:看了好多题解都强调用32位, 也不知道是不是这一题的缘故, 就尽量用32位吧, 不行就64再看看。

5月21日

1)对于加密:

v4:  0->len
src_2[v4] = src_2[v4] + src_2[v4 + 1] - 70;

可以解密:

v4 len->0;
src_2[v4] = src_2[v4] - src_2[v4 + 1] + 70;

2)exe文件与ELF文件的前0x40个字节是DOS  MZ头, 其中最后四个字节即红框中的字节是“PE/ELF文件头偏移地址”, 意思就是从文件第一个字节开始经过多少个字节到达PE/ELF文件头。

关键注意点:(1)如果发现程序Die识别成了DOS16, 可以用010打开看一看“PE文件头偏移地址”是否正确, 当然ELF应该也是这样。当然如果没有看到PE和ELF的话, 那应该就真是个DOS16文件了。

(2)注意这里是小端序, 00 01 00 00实际上指的是0x00000100。

5月22日

1)python的使用

此图是错误使用, 可以用下图方法

2)更新了对XXTea的认识, 详情查看XXTea最下侧最新内容。

3)将有小端序char组成的DWORD改为正序

for (int i = 0; i < 8; ++i) {
    printf("%c%c%c%c", (flag[i]) & 0xFF, (flag[i] >> 8) & 0xFF, (flag[i] >> 16) & 0xFF, (flag[i] >> 24) & 0xFF);
}

4)Hook_MessageBox关键部分

关键是后半部分,

VirtualProtect(v11, 8ui64, 4u, flOldProtect);: 这行代码调用了 Windows API 中的 VirtualProtect 函数, 它用于修改指定内存区域的访问权限。在这里, 它将 v11 指向的内存区域的权限设置为 4u 所代表的权限(通常是可读)。

将v11的内存区域权限设为可读, 之后将v11指向要hook的函数, 实现hook, 所以在调用MessageBoxA时便会执行v11指向的函数。

复现经验汇总

CISCN

androidso_ez

参考文章:https://www.cnblogs.com/sK07XdAy/p/18203747

静态分析

1、当有时候代码很长的时候, 想自己猜猜加密含义时, 可喂给chatgpt一试
2、Rot13, 以前也经常见, 可就是没有仔细看看, 虽然很简单, 但可以了解了解偏移量, 喂给Cyberchef会更省事些
Rot13, 顾名思义一般情况下偏移13, 如下

//ch为大写字母
ch=(ch-'A'+13)%26+'A'
//ch为小写字母
ch=(ch='a'+13)%26+'a'

当代码块中的13改为16, 即为偏移16, 对于偏移13:chrot13(rot13(ch))而对于偏移16:chrot16(rot10(ch))
3、re真是一半靠蒙一半靠猜啊, 猜测jiejie函数为Rc4加密, 猜测Rc4加密后以此异或0x03,0x89,0x33,0xB8,0x54,0x0C,0x20,0x6A
img
4、对于源代码, 其中有key算法为AES, 加密却是DES的矛盾, 查阅资料后有以下分析:

public SecretKeySpec​(byte[] key, String algorithm)
从给定的字节数组构造一个密钥。此构造函数不检查给定的字节是否确实指定了指定算法的密钥。例如, 如果算法是DES, 则此构造函数不检查key是否为8字节长, 并且也不检查弱键或半弱键。
这句话从参考文章中偷的, 也没看太懂
img
就虽然SecretKeySpec key = new SecretKeySpec(str2, "AES");但由于SecretKeySpec()函数原因, 加密Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");依旧按照DES加密。

动态分析

asm_re

参考文章:https://blog.csdn.net/Myon5/article/details/139046502
1、处理ida跑出来的汇编代码时,如果给了下图的信息,从图中可以获取的主要信息
img

Processor       : ARM   //ARM架构
Byte sex        : Little endian     //小端序
CODE64      //64位文件

2、如果给了机械码, 我们可以把文本文件喂给gpt, 输出机械码
img
之后到010editor中新建十六进制文件, ctrl+shift+v复制进去, 之后按照给定的格式用ida打开
img
反编译即可
3、解密:emm...反编译后, 还是看不懂, 哎, 就假装我看懂了吧。之后把cipher的部分喂给gpt把密文取出来, 解密即可

#include <stdio.h>

int main() {
    unsigned int flag[] = {0x1FD7, 0x21B7, 0x1E47, 0x2027, 0x26E7, 0x10D7, 0x1127, 0x2007, 0x11C7, 0x1E47, 0x1017,
                        0x1017, 0x11F7, 0x2007, 0x1037, 0x1107, 0x1F17, 0x10D7, 0x1017,0x1F67, 0x1017, 0x11C7, 0x11C7, 0x1017, 0x1FD7, 0x1F17, 0x1107, 0x0F47, 0x1127, 0x1037, 0x1E47, 0x1037, 0x1FD7, 0x1107, 0x1FD7, 0x1107, 0x2787};
    for (int i = 0; i < 38; ++i) {
        flag[i] = flag[i] - 30;
        flag[i] ^= 0x4d;
        flag[i] -= 20;
        flag[i] /= 80;
        printf("%c", flag[i]);
    }
    return 0;
}

whereThel1b

1、看不懂,根本看不懂,果然就是快乐猜猜猜啊,也不能说没有收获
img
cpython阅读可以从string下手,很多函数看不懂,其实都有他们在py中简单的名称的,例如cpython中的__pyx_k_random在这里就能看到是random,之后交叉引用,就能尝试猜一猜如何加密了
2、对于base64可以爆破,每三个字符三个字符爆破,三个字符输出四个数据,check成功就输出.而且可以根据密文数据得到flag长度(56/4)*3

gdb_debug

1、随机数的深入研究:伪随机数列指的是以一个种子生成的随机数,它在相同的位置的值是相同的

seed=0x0;
srand(seed);
for (int j = 0; j < 38; ++j) {
    printf("0x%x, ",rand());
}

上面的代码会生成38个不同的随机数.
img

seed=0xf0000000;
for (int j = 0; j < 38; ++j) {
    srand(seed);
    printf("0x%x, ",rand());
}

而此代码只会生成38个0x26.

seed=0xf0000000;
for (int j = 0; j < 38; ++j) {
    srand(seed);
    rand();
    printf("0x%x, ",rand());
}

此代码会生成38个0x1e27.以此类推下去,不难理解伪随机数列的含义
2、在做伪随机数题目时,需要注意时linux文件还是windows文件,如果时linux文件要将脚本在linux系统下执行因为两个系统生成的随机数是不同的,但也都是伪随机数列
3、逆天,一定要注意程序生成了多少次随机数啊啊啊啊啊啊!!!!

//此处调用了38次rand()
for ( i = 0LL; ; ++i )
{
  len2 = strlen(input);
  if ( i >= len2 )
    break;
  rand_num = rand();
  *(v28 + i) = input[i] ^ rand_num;
}
//Fisher-Yates 洗牌算法的一种实现,更换数组位置
//此次调用了37次rand()
for ( j = 0LL; ; ++j )
{
    v8 = strlen(input);
    if ( j >= v8 )
    break;
    *(ptr + j) = j;
    }
for ( k = strlen(input) - 1; k; --k )
{
    v18 = rand() % (k + 1);
    v19 = *(ptr + k);
    *(ptr + k) = *(ptr + v18);
    *(ptr + v18) = v19;
}
//此处调用了38次rand
for ( n = 0LL; ; ++n )
{
    v13 = strlen(input);
    if ( n >= v13 )
      break;
    v12 = *(v31 + n);
    *(v31 + n) = rand() ^ v12;
}

故可以分别用rand1[38],rand2[37],rand[38]存放,这里很关键,如果用rand2[38],那么rand3[38]全是错的.
3、爆破的时候,一定要注意将cipher置回初始值,吐了,研究半天不知道哪里写错了,用gpt对了一下,才发现是cipher在爆破一次后值就变了,后续的爆破根本就是不成立的.
4、i*0x10000000 <=> i<<28,而不是i<<7 >
img
5、在搜索随机数的逆向的时候,发现一个挺好的题目https://blog.csdn.net/weixin_45055269/article/details/112426573
这题也是随机数的一种出题的思路,改时间戳,将随机数异或图片的数据,动态调试,将时间戳改为图片的生成时间,再异或一遍,就得到原图了.

DRK CTF

flower_tea

1、call与retn的花指令
img

call    loc_140001224   //call到loc_140001224位置, 并将栈顶设置为返回地址0x14000121F
pop     rax     //将栈顶元素(返回地址)弹到rax中
add     rax, 0Ch    //将rax加上0x0C
push    rax     //将rax入栈, 即将当前rax的值作为返回地址
retn     //读取栈顶地址(0x14000121F+0x0C)返回

2、除了IsDebuggerPresent API外, 另一个反调试检测:
在x64下, 调试标志位(BeingDebugged flag)在PEB表(Process Environment Block)偏移0x2的位置;通过获取gs寄存器找到peb表(gs:[0x60])的位置;readgsqword(0x62)得到调试标志位,读取该值如果该值为1, 表示当前进程正在被调试;如果为0, 则表示当前进程没有被调试。
img
3、之前提过, 又hook的地方肯定存在virtualprotect, 通过异或把virtualprotect函数名字改了也是逆天
img

FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR  lpProcName
);

参数
hModule:这是一个模块句柄, 指向包含函数或变量的 DLL 模块。这个句柄通常由 LoadLibrary 或 GetModuleHandle 函数返回。
lpProcName:这是一个指向以空结尾的字符串, 包含要检索的函数或变量的名称, 或者包含函数的序号(由 MAKEINTRESOURCE 宏生成)。
返回值
成功时, 返回值是名称为lpProName的函数或变量的地址。
失败时, 返回值为 NULL。可以通过调用 GetLastError 函数获取更多的错误信息。

而对于ModuleName, 可能是反编译的问题, 不细究了, 下图是题解的显示
img
所以GetProcAddress()通过在kernel32.dll用到的virtualprotect获得了virtualprotect函数的地址
所以函数返回virtualprotect函数的地址
4、对virtualprotect函数的认识
img

BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

参数
lpAddress:指向要更改保护属性的内存区域的起始地址。必须是系统分配的页面中的地址。

dwSize:要更改保护属性的内存区域的大小(以字节为单位)。

flNewProtect:新的保护属性。可以是以下值之一(或它们的组合):
PAGE_NOACCESS (0x01):禁止所有访问。
PAGE_READONLY (0x02):允许读取访问。
PAGE_READWRITE (0x04):允许读写访问。
PAGE_WRITECOPY (0x08):允许写入时复制访问。
PAGE_EXECUTE (0x10):允许执行访问。
PAGE_EXECUTE_READ (0x20):允许执行和读取访问。
PAGE_EXECUTE_READWRITE (0x40):允许执行、读取和写入访问。
PAGE_EXECUTE_WRITECOPY (0x80):允许执行和写入时复制访问。
PAGE_TARGETS_INVALID:指定目标区域内的所有代码地址都无效。
PAGE_TARGETS_NO_UPDATE:禁止更新目标区域内的代码地址。
lpflOldProtect:指向一个变量, 该变量接收之前的保护属性。
返回值
如果函数成功, 返回值为非零值。
如果函数失败, 返回值为零。可以调用 GetLastError 函数获取扩展错误信息。

题中第三个参数便是64(0x40),允许执行、读取和写入访问。
5、依旧是call, retn花指令
img
rsp(Register Stack Pointer)寄存器包含了当前栈的顶部地址(64位)
而esp(Extended Stack Pointer)寄存器包含了当前栈的顶部地址(32位)
与第一点相同, call后会将返回地址0x1400014BA压栈, 而

sub     qword ptr [rsp+0], 5Ah      //则将栈顶地址减去了0x5A, 即将返回地址变为了0x1400014BA-0x5A=0x140001460
mov     rdx, 1      //rdx=1
test    rdx, rdx        //rdx进行&操作, rdx还是1
jz      loc_140001489       //因为rdx为1所以不会跳转
retn

所以我们要在0x140001460位置重新反汇编, call loc_140001489与loc_140001489里的内容直接nop掉即可
此处为qword ptr [rsp+0],突然又翻到一个文章,让我对此处加深了理解 https://c10udlnk.top/p/reSkillsOn-ALLaboutJunkCode/

...:00815023 038 E8 00 00 00 00          call    $+5
...:00815028 03C 55                      push    ebp
...:00815029 040 8B EC                   mov     ebp, esp
...:0081502B                             db      36h
...:0081502B 040 36 83 45 04 0A          add     dword ptr [ebp+4], 0Ah
...:00815030 040 5D                      pop     ebp
...:00815031 03C C3                      retn

此处栈上内容(图取自上述链接)
img
dword ptr [ebp+4]就指向retn addr,注意点就是这个+4
从大佬的文章还知道一点,需要注意有没有破坏寄存器,需要更改寄存器的值(当然如果这两个寄存器后面都没用到的话当我没说x 不过保险起见, 建议还是补上最好)。
6、call, pop, jmp花指令
img
call后将0x140001648存入栈顶, pop rax将栈顶传给rax, 又jmp rax, 相当于直接retn了
7、stack frame is too big
成因:分析栈帧时有异常出现
解决方案:找到明显不合常理的stack 双击进入栈帧界面, 按U键盘删除对应的stack
有可能加壳
可能由花指令导致, 手动或自动检查并去掉花指令
img
此题是因为去花导致分析异常, 用解决方法1, 双击进入stack界面
img
按U将异常出undefine
8、对于int64转为int32

delta[0] = 0xE1C49E7259578627ui64;
delta[1] = 0x8C3DA26BBC24167Fui64;
unsigned int delta[4]={0x59578627,0xE1C49E72,0xBC24167F,0x8C3DA26B}

9、魔改tea, 可以将delta的值魔改成数组, 每次对应的delta不同

//encode:
for (int i = 0; i <15 ; i+=4) {
        for (int j = 0; j <= 32; ++j) {
            sum += delta[flag[i]%4];
            flag[i] += (sum >> 3) ^ (4 * ke[j%4]) ^ sum ^ flag[i+3];

            sum += delta[flag[i+1]%4];
            flag[i+1] += (16 * sum) ^ (ke[(j+1)%4] >> 1) ^ sum ^ flag[i];

            sum += delta[flag[i+2]%4];
            flag[i+2] += (sum >> 2) ^ (8 * ke[(j+2)%4]) ^ sum ^ flag[i+1];

            sum +=delta[flag[i+3]%4];
            flag[i+3] += (2 * sum) ^ (ke[(j+3)%4] >> 2) ^ sum ^ flag[i+2]
        }
    }
//偷的decode, 我写的感觉和以这个一样啊, 但是就是结果不对
//decode:
for (i = 0; i < len; i += 4) {
    uint32_t* c[4] = { &flag[(len - (i + 3)) % len],&flag[(len - (i + 2))
                                                          % len],&flag[(len - (i + 1)) % len],&flag[(len - i) % len] };
    for (j = 32; j >= 0; j--) {
        *c[3] -= ((e ^ *c[2]) ^ (key[(j + 3) % 4] >> 2)) ^ (e << 1);
        e -= delta[*c[3] % 4];
        *c[2] -= ((e ^ *c[1]) ^ (key[(j + 2) % 4] << 3)) ^ (e >> 2);
        e -= delta[*c[2] % 4];
        *c[1] -= ((e ^ *c[0]) ^ (key[(j + 1) % 4] >> 1)) ^ (e << 4);
        e -= delta[*c[1] % 4];
        *c[0] -= ((e ^ *c[3]) ^ (key[j % 4] << 2)) ^ (e >> 3);
        e -= delta[*c[0] % 4];
    }

elec_go

Electron(原名为Atom Shell[6])是GitHub开发的一个开源框架。[7]它通过使用Node.js(作为后端)和Chromium的渲染引擎(作为前端)完成跨平台的桌面GUI应用程序的开发。Electron现已被多个开源Web应用程序用于前端与后端的开发, 著名项目包括GitHub的Atom和微软的Visual Studio Code。[8][9]

一个基础的Electron包含三个文件:package.json(元数据)、main.js(代码)和index.html(图形用户界面)。框架由Electron可执行文件(Windows中为electron.exe、macOS中为electron.app、Linux中为electron)提供。开发者可以自行添加标志、自定义图标、重命名或编辑Electron可执行文件。

Debug

1、die没查出壳, 但是直接拖到ida中发现, 函数非常少, 很可能是进行了某些操作, 对其加密
img
既然die不行, 那就多试试别的, 就此, 我顺便把其他几个更新一下
查是查出来了,怎么脱呢,研究研究特征码之类的 https://www.52pojie.cn/thread-326995-1-1.html
根据文章得到
(1)修改区段名(UPX0...)和修改标识(3.-.-UPX!)后,die,PEid,Exeinfope都能查到
(2)去掉特征码后,只有Exeinfope可以识别出来
(3)加垃圾区段与移动PE头后,都识别不出了
(4)添加花指令还没看
2、借助x64dbg及Scylla的UPX手工脱壳
参考文章: https://bbs.kanxue.com/thread-268159.htm
就此题的学习文章:

Emoji CTF

1、之前高神视频中的rc4加密写idapy解密的问题真给遇上了,cipher中有一位为0,会导致程序执行的时候strlen的返回值变小,导致程序加密(对称密码,解密)提前结束,运行到strlen位置,pytch一下即可
img
img

每日一道逆向题

7.17

[buu soullike]
1、之前有情况"stack frame is too big",ida分析栈帧时出现异常,这种属于去除花指令之类的出现的问题;而此题出现了function is too big,查看汇编也发现没什么问题,可能function确实这么大
在文件管理器中找到此文件
img
将functionmaxsize进行更改,将64改大一些,可以改为1024,只不过反编译的时候会慢很多,耐心等待
img
2、python中没有++的自增运算符,z3爆破贴加密代码的时候需要注意

7.18

[buu firmware]
1、路由器逆向,这么神奇的东西
参考wp https://blog.csdn.net/weixin_52369224/article/details/121219080
参考binwalk安装 https://blog.csdn.net/QQ1084283172/article/details/65441110
2、完整安装binwalk需要再ubuntu上进行,debian会缺少一些软件包(被折磨好久),之后可以选择wp中的方法,安装firmware-mod-kit来解压binwalk未解压成功的.squashfs文件
3、为此,我还重新换源,啥啥的,救命,换源方法: root打开etc/apt/sources.list
4、更换linux网络与主机网络一致方法: https://blog.csdn.net/lxy580/article/details/134423709
5、已经放弃了,debian装binwalk太麻法了,痛苦,就先大概知道有这样的路由器逆向的题目吧

7.19

[V&N2020 CSRe]
1、c#的混淆: Eazfuscator混淆,用die竟然识别不出来,看来要换一个die了
img
2、去除Eazfuscator混淆,de4dot工具

de4dot.exe "D:\xxx.exe"

7.20

[ciscn2023初赛 moveAside]
1、movfuscator混淆(一大堆mov mov mov)
参考文章: https://blog.csdn.net/CherestSan/article/details/117608664
[movfuscator][1]
混淆器: [1]:https://github.com/xoreaxeaxeax/movfuscator
混淆程序指令:movcc test.c -o test
[demovfuscator][2]
解混淆器: [2]:https://github.com/leetonidas/demovfuscator

7.21

DASCTF 2024暑期挑战赛
[Strangeprograme]
1、抽象,直接chef跑出来的十六进制转字符,明知道倒序了,之后发现有问号,以为最后几位的解密错了,就没交答案,吐了
在反调试的时候遇到is_debug,一个很好的办法,将所有调用is_debug的地方下断点,调试的时候将eax改为0
2、将加密逻辑动调,手搓一份,再解密即可

7.22

Deadsec
[flagchecker]
1、die和exeinfo都查不出pyinstaller但确实是用pyinstaller打包的,不能理解. 发现如果不是pyinstaller打包的解包时会报错"Missing cookie, unsupported pyinstaller version or not a pyinstaller archive",所以如果看到python,而且可以解包,就可以考虑这个文件是pyinstaller打包的了,还是太信赖die和exeinfo了
2、fork后,调试子程序
./linux_server -p 22222 //开个新端口
将fork后的程序patch为死循环,之后调试到fork后,开始运行父子进程
再打开一个ida,点击debugger,attach
img
之后将端口改为22222
img

7.23

WKCTF
[so_easy]
1.对于native题,下面这个文件的观赏性最好
img
2.CRC64效验,也是离谱,我就说这个加密好像哪里见过,但是再次看到,还是不会,害
先贴一下crc32校验吧

//查表形式
uint32_t crc32(const uint8_t *buf, size_t size, int32_t *crc32_tab){
    const uint8_t *p = buf;
    uint32_t crc = ~0U;
    while (size--){
        crc = crc32_tab[(crc ^ *p++) & 0xff] ^ (crc >> 8);
    }
    return ~crc;
}

//不查表形式
uint32_t crc32(uint8_t* data, int size){
    uint32_t r = ~0;
    uint8_t *end = data + size;
    while (data < end){
        r ^= *data++;
        for (int i = 0; i < 8; ++i) {
            uint32_t t = ~((r&1) - 1);
            r = (r>>1) ^ (0xEDB88320 & t);
        }
    }
    return ~r;
}

真是不能理解这个加密形式,真太奇怪了,emo了,等我再修炼一段时间在研究吧,附上crc64解密脚本

secret = [0xBC8FF26D43536296, 0x520100780530EE16, 0x4DC0B5EA935F08EC,
        0x342B90AFD853F450, 0x8B250EBCAA2C3681, 0x55759F81A2C68AE4]
key = 0xB0004B7679FA26B3

flag = ""

# 产生CRC32查表法所用的表
for s in secret:
    for i in range(64):     #round
        sign = s & 1
        # 判断是否为负
        if sign == 1:
            s ^= key
        s //= 2
        # 防止负值除2, 溢出为正值
        if sign == 1:
            s |= 0x8000000000000000
    # 输出表
    print(hex(s))
    # 计算CRC64
    j = 0
    while j < 8:
        flag += chr(s&0xFF)
        s >>= 8
        j += 1
print(flag)

参考文章: https://forum.butian.net/share/1508
3、用z3爆破此题,我一开始的想法也是这样,但是没处理好,也没搞出来,真痛苦啊
what?也没人告诉我z3有自己的If函数啊,

If(condition, true_case, false_case)

错误示例:

from z3 import *

x = Int('x')
y = Int('y')

# 这种方式是错误的, 因为它是在运行时评估的, 而不是构建符号表达式
if x > y:
    result = x
else:
    result = y

# 创建求解器并添加约束
solver = Solver()
solver.add(x == 5, y == 3)

if solver.check() == sat:
    model = solver.model()
    print(f"x = {model[x]}, y = {model[y]}, result = {model.evaluate(result)}")
else:
    print("不可满足")

正确用法:

from z3 import *

# 创建整数变量
x = Int('x')
y = Int('y')

# 使用 Z3 的 If 函数构建条件表达式
result = If(x > y, x, y)

# 创建求解器并添加约束
solver = Solver()
solver.add(x == 5, y == 3)

if solver.check() == sat:
    model = solver.model()
    print(f"x = {model[x]}, y = {model[y]}, result = {model.evaluate(result)}")
else:
    print("不可满足")

附上此题的z3代码:

from z3 import *


chiper = [0x540A95F0C1BA81AE, 0xF8844E52E24A0314, 0x09FD988F98143EC9, 0x3FC00F01B405AD5E]
for i in range(4):
    v10 = BitVec('v10', 64)
    solver = z3.Solver()
    for j in range(255, 0, -5):
        v12 = (2 * v10) ^ 0x71234EA7D92996F5
        # if v10 >> 63 == 0:
        #     v12 = 2 * v10
        v12 = If(v10 >= 0, 2 * v10, v12)
        v13 = (2 * v12) ^ 0x71234EA7D92996F5
        # if v12 >> 63 == 0:
        #     v13 = 2 * v12
        v13 = If(v12 >= 0, 2 * v12, v13)
        v14 = (2 * v13) ^ 0x71234EA7D92996F5
        # if v13 >> 63 == 0:
        #     v14 = 2 * v13
        v14 = If(v13 >=0, 2 * v13, v14)
        v15 = (2 * v14) ^ 0x71234EA7D92996F5
        # if v14 >> 63 == 0:
        #     v15 = 2 * v14
        v15 = If(v14 >= 0, 2 * v14, v15)
        v10 = (2 * v15) ^ 0x71234EA7D92996F5
        # if v15 >> 63 == 0:
        #     v10 = 2 * v15
        v10 = If(v15 >= 0, 2 * v15, v10)
    solver.add(v10 == chiper[i])

    if solver.check() == z3.sat:
        print(solver.model())
    else:
        print("error")

7.24

RoarCTF2019
[polyre]
1、去除虚假控制流与平坦控制流: https://github.com/cq674350529/deflat
2、安装angr: https://www.cnblogs.com/level5uiharu/p/16925853.html
3、启动虚拟环境: source home/sy/angrfile/venv/bin/activate
4、Useage

Flat_control_flow useage: python3 deflat.py -f path/file_name --addr main_addr
Bogus_control_flow useage: python3 debogus.py -f pyth/file_name --addr main_addr

5、用d810去除混淆

7.25

2021羊城杯
[EasyVM] "angr解法"
1、已知flag的部分与size,进行约束 参考文章: https://www.cnblogs.com/61355ing/p/10523797.html

flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(28)]
flag = claripy.Concat(*[claripy.BVV(b'flag{') + flag_chars] + claripy.BVV(b'}'))

2、并需要注意不让这28个字符中出现0x00或者'\n', 当然有其他的限制条件也可以一并加上去。

for k in flag_chars:
    st.solver.add(k != 0)
    st.solver.add(k != 10)

3、文章: https://34r7hm4n.me/0x401RevTrain-Tools/angr/11_利用angr符号执行梭哈VM类CTF赛题/
给了两个脚本,一个可以跑出来,另一个跑不出来,确实奇怪哈
不是,我flag都丢脸上了,也Fail?
img

7.26

[xor]
1、python的字符串转化成列表用法

cipher = "this_is_cipher"
flag = [(ord(list(cipher)[i]) ^ 0x30) for i in range(14)]

7.27

[ctf进阶指北]
1、what?学一年归来,仍是新生,乘法逆元解题???感觉基础知识差好多啊(怒了)
对于x * y = 1,则x与y互为乘法逆元
对于有限域中的元素 a, 它的乘法逆元记作 a^(-1), 满足以下条件:
a * a^(-1) = 1
以有限域 GF(p) 为例, 其中 p 是素数。在这样的有限域中, 对于任意非零元素 a, 它的乘法逆元
可以通过求解下面的方程来得到:
a * a^(-1) ≡ 1 (mod p)
这里的“mod p”表示对 p 取模。求解这个方程, 就能找到 a 的乘法逆元 a^(-1)。
a1为unsigned char*数组
a1[k]为有限域(0x00-0xff)

for(int i = 0;i < 12;i++)
{
    a1[i] = a[i] * 17 + 113;
}

如何求a1[i]呢,原a1[i] * 17等于a1[i] - 113则求出17在256下的乘法逆元ss,再用(a1[i] - 113) * ss就等于原a1[i] * 17 * ss就等于a1[i]
17在mod 256下的逆元是241

for(int i = 0;i < 12;i++){
    a1[i] = (a[i] - 113) * 241
}

而为什么不能直接a1[i] = (a1[i] - 113) / 17则是因为可能会在a1[i] * 17时产生溢出,导致数据损失.
2、哎,怒了,真遇到了,用z3取余应该可以解出来啊......知道可能会溢出,但是一直不知道怎么写脚本

salt = [21, 24, 215, 9, 183, 232, 62, 241, 194, 18, 65, 50, 67, 150, 206, 250,
255, 224, 213, 19, 22, 140, 103, 11, 248, 106, 243, 5, 121, 184, 211, 77, 224,
74, 149, 228, 68, 113, 180, 26, 151, 241, 122, 177, 0]
target = [58, 123, 72, 156, 80, 41, 69, 186, 53, 70, 205, 115, 21, 195, 28,
247, 171, 85, 243, 183, 193, 161, 110, 209, 71, 132, 238, 189, 99, 105, 179,
193, 37, 16, 105, 37, 218, 173, 177, 193, 85, 199, 251, 254, 0]
S = b""
for i in range(0, 44, 4):
    T = (target[i] << 24) | (target[i+1] << 16) | (target[i+2] << 8) |target[i+3]
    B = (salt[i] << 24) | (salt[i+1] << 16) | (salt[i+2] << 8) | salt[i+3]
    # A * 0x01919811 + B == T

    A = (T - B) * pow(0x01919811, -1, 2**32) % 2**32
    S += A.to_bytes(4, "big")
    print(S)

这里面的pow(0x01919811, -1, 2**32)就是求逆元,然后用A.to_bytes(4, "big")以大端序转化成字节串(题目比较特殊,是大端序).

7.28

[buu findkey]
1、大胆写一写,总是觉得好像哪里有问题,就不敢写........,明明很简单的,害

7.29

[moe dynamic]
1、之前64位的dll与32位的dll不会补,好像补错了几个位置,现在也不会报错了,而是直接运行不了,还好我最终还是发现了问题所在
img
之后重新安装补一下dll就行.

7.30

[moe moedaily]
1、很新颖的题目,excel/xlsx逆向?加密逻辑以每步的值与excel函数存在第二张secret表中表示.此题是tea加密

7.31

[moe Bomb Defusion]
1、第一次遇到whl逆向,真好神奇,记录一下方法吧,whl是第三方库的组合,可以将自己的py文件,或者py文件打包的pyd文件打包在一起,用pip install xxx.whl就可以安装,注意whl的名称中一般会包含python适用的版本,可以选择创建一个虚拟环境安装whl.
2、将whl当成压缩包解压,如果有py文件源码,那太好了,但是一般为了防止反编译一般会选择将源码打包为pyd文件,网上有一篇pyd的逆向,看着好难....... https://bbs.kanxue.com/thread-259124.htm
3、在安装好whl后,调用help(xxx)可以查询到库函数的一些基本信息,之后根据信息,使用库函数,进行逆向
4、可以将解压缩的pyd文件放到ida中查看,可以看到一些字符串信息,可以提供帮助.

8.1

[moe d0tN3t]
1、我勒个c#啊,多用dnspy吧,net_reflector又不知道抽什么风......

2024.9

[moe ezMaze]

1、dfs处理迷宫问题

int is_obstacle(int x,int y,unsigned char *map){
    unsigned char v3;
    if ( x > 80 || y > 56 || x < 1 || y < 1 )
        return 1;
    v3 = map[10 * y - 10 + (x - 1) / 8];
    map[10 * y - 10 + (x - 1) / 8] = (1 << (7 - (x - 1) % 8)) | v3;
    return ((int)v3 >> (7 - (x - 1) % 8)) & 1;
}

(1)处理障碍问题,这个地方利用位运算标记障碍物,并且已经走过的位置被标记,不能再走.这样就导致走上错误道路时通过dfs算法无法回溯.
可以单独设置函数set obstacle

void set_obstacle(int x, int y, unsigned char *map) {
    // 将指定位置设置为障碍
    map[10 * (y - 1) + (x - 1) / 8] |= (1 << (7 - (x - 1) % 8));
}

对于set后,如果走向错误路径需要恢复现场

map[10 * (y - 1) + (x - 1) / 8] &= ~(1 << (7 - (x - 1) % 8));

(2)对于生成所有路径的方法,每一次都将可能的路段走一遍

for (int i = 0; i < 4; ++i) {
        int Newx = x, Newy = y;

        switch (option[i]) {
            case 'w':
                Newy--; // 向上
                break;
            case 's':
                Newy++; // 向下
                break;
            case 'a':
                Newx--; // 向左
                break;
            case 'd':
                Newx++; // 向右
                break;
            default:
                continue; // 不处理其他情况
        }

        // 检查边界和障碍
        if (Newx < 1 || Newx > 80 || Newy < 1 || Newy > 56 || is_obstacle(Newx, Newy, map)) {
            continue; // 继续下一个方向
        }

        path.push_back(option[i]); // 记录路径

        // 递归调用, 传递新坐标(全部路径)
        dfs_gogo(map, Newx, Newy);

        // 递归调用, 传递新坐标(单一路径)
        // if (dfs_gogo(map, Newx, Newy)) {
        // return 1; // 如果找到路径
        // }

        path.pop_back(); // 回溯
    }

具体就是分析调用dfs_gogo()函数的情况,二者区别导致前者可以输出所有路径.

以vector<vector> all_paths;存储所有路径,输出全部路径:

#include <iostream>
#include <vector>

using namespace std;

int is_obstacle(int x, int y, unsigned char *map) {
    // 检查越界
    if (x < 1 || x > 80 || y < 1 || y > 56)
        return 1; // 越界视为障碍

    // 计算映射数组的索引
    unsigned char v3 = map[10 * (y - 1) + (x - 1) / 8];

    // 返回是否为障碍
    return (v3 >> (7 - (x - 1) % 8)) & 1;
}

void set_obstacle(int x, int y, unsigned char *map) {
    // 将指定位置设置为障碍
    map[10 * (y - 1) + (x - 1) / 8] |= (1 << (7 - (x - 1) % 8));
}

char option[4] = {'w', 's', 'a', 'd'};
vector<char> path;
vector<vector<char>> all_paths; // 存储所有路径

int dfs_gogo(unsigned char *map, int x, int y) {
    // 检查是否到达目标
    if (x == 75 && y == 55) {
        all_paths.push_back(path); // 将当前路径加入所有路径
        return 1; // 找到路径
    }

    // 标记当前位置为障碍
    set_obstacle(x, y, map);

    // 遍历方向
    for (int i = 0; i < 4; ++i) {
        int Newx = x, Newy = y;

        switch (option[i]) {
            case 'w':
                Newy--; // 向上
                break;
            case 's':
                Newy++; // 向下
                break;
            case 'a':
                Newx--; // 向左
                break;
            case 'd':
                Newx++; // 向右
                break;
            default:
                continue; // 不处理其他情况
        }

        // 检查边界和障碍
        if (Newx < 1 || Newx > 80 || Newy < 1 || Newy > 56 || is_obstacle(Newx, Newy, map)) {
            continue; // 继续下一个方向
        }

        path.push_back(option[i]); // 记录路径

        // 递归调用, 传递新坐标
        dfs_gogo(map, Newx, Newy);

        path.pop_back(); // 回溯
    }

    // 取消障碍标记
    map[10 * (y - 1) + (x - 1) / 8] &= ~(1 << (7 - (x - 1) % 8));
    return 0; // 没有找到路径
}

int main() {
    unsigned char map[560] = {
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xEA, 0xA8, 0xA4, 0x92,
            0x4F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3D, 0x14, 0x94, 0x92, 0xB8, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
            0xBB, 0xDA, 0x1F, 0x29, 0x7B, 0xFF, 0xFE, 0xFF, 0xC0, 0x00, 0x3B, 0xDA, 0xDC, 0x00, 0x03, 0x00,
            0x00, 0xFF, 0xDF, 0xFF, 0xFB, 0xDA, 0xDD, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x3B, 0xDA,
            0xDC, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xB8, 0x42, 0x1F, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF,
            0xC0, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x03, 0x00, 0x00, 0xFF, 0xDF, 0xFF, 0xF8, 0x7C, 0x3D, 0xFF,
            0xFF, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x3B, 0xFD, 0xBC, 0x00, 0x03, 0x00, 0x3E, 0x1F, 0xFF, 0xFF,
            0xB8, 0x7C, 0x7F, 0xFF, 0xFB, 0xFF, 0xBE, 0xDF, 0xFF, 0xFF, 0xBF, 0x7D, 0xBD, 0x29, 0x7B, 0xFF,
            0xBE, 0xDF, 0xFF, 0xFF, 0x98, 0x7C, 0x3F, 0xFF, 0xFB, 0xFF, 0xBE, 0xDF, 0x80, 0x00, 0x4F, 0xFF,
            0xF8, 0x00, 0x02, 0x00, 0x3E, 0xDF, 0xBF, 0x7F, 0x6F, 0xFF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFE, 0xDF,
            0x96, 0xAB, 0x60, 0x00, 0x08, 0x00, 0x02, 0x01, 0xFE, 0xDF, 0x96, 0xAB, 0x7F, 0xFF, 0xEF, 0xFF,
            0xFB, 0xFD, 0xFE, 0xDF, 0x95, 0x4B, 0x70, 0x00, 0x0C, 0x00, 0x03, 0x01, 0xFE, 0xDF, 0x95, 0x4B,
            0x77, 0xFF, 0xFD, 0xFF, 0xFF, 0x7F, 0xFE, 0xDF, 0xAA, 0x96, 0x70, 0x00, 0x0C, 0x00, 0x03, 0x00,
            0x06, 0xDF, 0x92, 0x57, 0x7F, 0xFF, 0xEF, 0xFF, 0xFB, 0xFF, 0xF6, 0xDF, 0x92, 0x94, 0x70, 0x00,
            0x0C, 0x00, 0x03, 0x00, 0x06, 0xDF, 0x8A, 0x4B, 0x77, 0xFF, 0xFD, 0xFF, 0xFF, 0x7F, 0xFE, 0xDF,
            0xAE, 0xD5, 0x70, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x0E, 0xDF, 0x95, 0x4A, 0xFF, 0xFF, 0xEF, 0xFF,
            0xFB, 0xFF, 0xEE, 0xDF, 0x8A, 0x4A, 0x4A, 0x50, 0xEC, 0x00, 0x03, 0xFF, 0xEE, 0xDF, 0xBF, 0x7F,
            0x7F, 0xFF, 0xE1, 0xFF, 0xFF, 0xFF, 0xEE, 0xDF, 0xFF, 0xFF, 0xF5, 0x5D, 0x7F, 0xFF, 0xFF, 0xFF,
            0xEE, 0xDF, 0x80, 0x00, 0x38, 0xD5, 0x35, 0x2A, 0x98, 0x00, 0x0E, 0xDF, 0xBF, 0xFF, 0xBD, 0x51,
            0x2F, 0xFF, 0xFB, 0xFF, 0xFE, 0xDF, 0xA0, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x03, 0x00, 0x00, 0xDF,
            0xAF, 0xFF, 0xFF, 0xC0, 0x7D, 0xFF, 0xFF, 0x7F, 0xFF, 0xDF, 0xA0, 0x00, 0x3F, 0xDF, 0x7C, 0x00,
            0x03, 0x00, 0x00, 0xDF, 0xBF, 0xFF, 0xBF, 0xDF, 0x7F, 0xFF, 0xFB, 0xFF, 0xFE, 0xDF, 0xA0, 0x00,
            0x3F, 0xDF, 0x7C, 0x00, 0x03, 0x00, 0x00, 0xDF, 0xAF, 0xFF, 0xFF, 0xDF, 0x7D, 0xFF, 0xFF, 0x7F,
            0xFF, 0xDF, 0xA0, 0x00, 0x3F, 0xDF, 0x7C, 0x00, 0x03, 0x00, 0x00, 0xDF, 0xBF, 0xFF, 0xBF, 0xDF,
            0x7F, 0xFF, 0xFB, 0xFF, 0xFE, 0xDF, 0xBC, 0x8B, 0xBF, 0xDF, 0x7F, 0xFF, 0xFB, 0xFF, 0xFE, 0xDF,
            0xA9, 0x24, 0x9F, 0xDF, 0x7F, 0xFF, 0xFB, 0xFF, 0xFE, 0x5F, 0xB2, 0xA1, 0xCF, 0xDF, 0x78, 0x00,
            0x02, 0x00, 0x03, 0x5F, 0xB1, 0x52, 0xEF, 0xDF, 0xFB, 0xFF, 0xFE, 0xFF, 0xFB, 0x5F, 0xAA, 0x22,
            0xE0, 0x00, 0x08, 0x00, 0x02, 0x01, 0xF8, 0x5F, 0xBD, 0x29, 0xFF, 0xFF, 0xEF, 0xFF, 0xFB, 0xFD,
            0xFF, 0xDF, 0xB2, 0x52, 0xB0, 0x00, 0x0C, 0x00, 0x03, 0x01, 0xFF, 0xDF, 0xB4, 0x94, 0xB7, 0xFF,
            0xFD, 0xFF, 0xFF, 0x7F, 0xFF, 0xDF, 0xB2, 0x4A, 0xB0, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x00, 0xDF,
            0xB2, 0x52, 0x5F, 0xFF, 0xEF, 0xFF, 0xFB, 0xFF, 0xFE, 0xDF, 0xB2, 0x54, 0xD0, 0x00, 0x0C, 0x00,
            0x03, 0x00, 0x00, 0xDF, 0xB2, 0x52, 0x57, 0xFF, 0xFD, 0xFF, 0xFF, 0x7F, 0xFF, 0xDF, 0xB2, 0x91,
            0x30, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xDF, 0xB0, 0x89, 0x7F, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F,
            0xFE, 0xDF, 0xBC, 0x91, 0x10, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFE, 0xDF, 0x80, 0x00, 0x00, 0x00,
            0x0D, 0xFF, 0xFE, 0x94, 0x8A, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

    };

    int start_x = 2, start_y = 2; // 起始坐标
    dfs_gogo(map, start_x, start_y); // 从起点开始搜索

    // 输出所有路径
    for (const auto& p : all_paths) {
        for (char val : p) {
            cout << val;
        }
        cout << endl;
    }

    return 0;
}

(3)对于逆向题,肯定要在这么多路径里面确定一个正确路径,常考的便是最短路径或者给定路径长度判断.
解决方法:不仅判断x,y的位置还需要判断此时path的长度,当path.size() == taget_len时,输出路径.也可以设置一个min_path来记录当前的最短路径.最后输出最短路径.

2024.10

  1. 函数传参

    参数 1 2 3 4
    寄存器 rcx rdx r8 r9

    如果在一个函数中看到有黄色的变量,这个变量是寄存器的值,如果识别是rdx,就有可能是ida识别参数识别少了,可以手动y添加一个参数

  2. qmemcpy不是一个函数调用,是ida的优化

2024.11

  1. python学习

     import struct
    
     key1 = struct.pack("<4Q", 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa)
    
     key2 = struct.pack("<QQQQ", 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaaaaaaaaaa)
    

    八字节的是Q,对于其他情况,可以点进pack函数中,向上翻,可以找到
    img

  2. 编码解码识别

     printf("%02x", x);  //十六进制编码
     sscanf(buf, "%02x", x)  //十六进制解码
    
    • z3的bitVec类型进行约束的时候,要<=127而不能是<128,此时会被默认为无符号类型
    • 如果只有位移运算,就用int型,可以避免bitVec类型溢出导致错误.

常用命令

  1. android

    adb -s 设备名 shell

  2. ubuntu

    uname -m //查看系统架构(32/64位)
    qemu-riscv64 file_name.riscv64.elf //运行riscv64程序
    wget https://file-name.com/file-name.tar.gz //下载文件
    tar -zxvf file-name.tar.gz //解压文件
    cd file-name //进入解压后的目录
    python3 -m venv myenv //创建虚拟环境
    source myenv/bin/activate //激活虚拟环境

    // GDebi图形化安装deb,右键open with GDebi package install
    sudo apt install gdebi
    // dpkg命令安装deb
    sudo dpkg -i package_name.deb

  3. Windows

    sc config i8042prt start= disabled //禁用键盘
    sc config i8042prt start= auto //启用键盘

posted @ 2024-07-24 18:25  Un1corn  阅读(37)  评论(0编辑  收藏  举报