Study
1、 ida
shift f12---跳转string
ctrl x------交叉引用
shift e-----数据导出
a-----------转化为字符串数组
\------------去除灰色的变量解释
/------------添加注释
N-----------重命名
ctrl z------返回上一步,撤回原行为
Y-----------重组数组(更改变量类型)
*(shift+8)------------构建数组(记得取消dup格式)
u-------------取消定义
_-------------把数值转换成无字符型
2、pyc
形如%1f要将%改成\x
for后:换行要tab
3、凯撒密码
可带入数值判断偏移量,解密(注意大小写的条件可能不同)
4、RSA
ctf工具中公钥分析的应用,需含begin end
x64dbg
按如图所示找到程序入口点(OEP)
选中(OEP)后按 shift d-------调用字符串界面
5、汇编代码转换成二进制可执行文件
linux下gcc安装
sudo apt install gcc
gcc -o name(可执行文件名) name.s(汇编代码)
6、打包
(1)upx使用
upx程序路径于windows终端打开,upx.exe 需脱壳程序地址与名称
wsl下直接upx -d -文件名 注:原程序(文件)直接脱壳,无新文件产生。
(2)pyinstxtractor.py使用
用于脱pyinstaller
脚本路径与windows终端打开,python3 pyinstxtractor.py +需脱壳程序地址与名称
脱壳后文件将于pyinstxtractor.py路径下生成
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 ('x' ) \\或x,y,z,e,f = z3.Ints("x y z e f" )
第二步 创建求解器
solver = z3.Solver()
第三步 添加约束
solver.add()
第四步 求解方程
if solver.check() = = z3.sat:
print (solver.model()) <br> 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次方
二:用z3.BitVec(位向量类型)
a1 = [z3.BitVec(f'x_{i}' ,8 ) for i in range ( 10 )]
三:位向量转换为字符串
result = "".join(chr(m.evaluate(each).as_long()) for each in flag)
print(result)
for i in flag:
print(chr(m[i].as_long()),end='')
这个8代表设置有8位,这种方法也可以快速设置变量,8位设置后可以对其再次进行约束
两个约束均可,用其一即可。
上面快速设置变量的方法也使用与Int型,但需要将后面的8去除。
快速输出与转化形式为字符型的方法
哎,对于python的运用,还得多练。
更多用法可参考z3使用
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
4、对于源代码,其中有key算法为AES,加密却是DES的矛盾,查阅资料后有以下分析:
public SecretKeySpec(byte[] key, String algorithm)
从给定的字节数组构造一个密钥。此构造函数不检查给定的字节是否确实指定了指定算法的密钥。例如,如果算法是DES,则此构造函数不检查key是否为8字节长,并且也不检查弱键或半弱键。
这句话从参考文章中偷的,也没看太懂
就虽然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跑出来的汇编代码时,如果给了下图的信息,从图中可以获取的主要信息
Processor : ARM //ARM架构
Byte sex : Little endian //小端序
CODE64 //64位文件
2、如果给了机械码,我们可以把文本文件喂给gpt,输出机械码
之后到010editor中新建十六进制文件,ctrl+shift+v复制进去,之后按照给定的格式用ida打开
反编译即可
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、
gdb_debug
1、随机数的深入研究:伪随机数列指的是以一个种子生成的随机数,它在相同的位置的值是相同的
seed=0x0;
srand(seed);
for (int j = 0; j < 38; ++j) {
printf("0x%x, ",rand());
}
上面的代码会生成38个不同的随机数.
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 >
5、在搜索随机数的逆向的时候,发现一个挺好的题目https://blog.csdn.net/weixin_45055269/article/details/112426573
这题也是随机数的一种出题的思路,改时间戳,将随机数异或图片的数据,动态调试,将时间戳改为图片的生成时间,再异或一遍,就得到原图了.
DRK CTF
flower_tea
1、call与retn的花指令
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,则表示当前进程没有被调试。
3、之前提过,又hook的地方肯定存在virtualprotect,通过异或把virtualprotect函数名字改了也是逆天
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
参数
hModule:这是一个模块句柄,指向包含函数或变量的 DLL 模块。这个句柄通常由 LoadLibrary 或 GetModuleHandle 函数返回。
lpProcName:这是一个指向以空结尾的字符串,包含要检索的函数或变量的名称,或者包含函数的序号(由 MAKEINTRESOURCE 宏生成)。
返回值
成功时,返回值是名称为lpProName的函数或变量的地址。
失败时,返回值为 NULL。可以通过调用 GetLastError 函数获取更多的错误信息。
而对于ModuleName,可能是反编译的问题,不细究了,下图是题解的显示
所以GetProcAddress()通过在kernel32.dll用到的virtualprotect获得了virtualprotect函数的地址
所以函数返回virtualprotect函数的地址
4、对virtualprotect函数的认识
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花指令
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
此处栈上内容(图取自上述链接)
dword ptr [ebp+4]就指向retn addr,注意点就是这个+4
从大佬的文章还知道一点,需要注意有没有破坏寄存器,需要更改寄存器的值(当然如果这两个寄存器后面都没用到的话当我没说x 不过保险起见,建议还是补上最好)。
6、call,pop,jmp花指令
call后将0x140001648存入栈顶,pop rax将栈顶传给rax,又jmp rax,相当于直接retn了
7、stack frame is too big
成因:分析栈帧时有异常出现
解决方案:找到明显不合常理的stack 双击进入栈帧界面,按U键盘删除对应的stack
有可能加壳
可能由花指令导致,手动或自动检查并去掉花指令
此题是因为去花导致分析异常,用解决方法1,双击进入stack界面
按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中发现,函数非常少,很可能是进行了某些操作,对其加密
既然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一下即可
每日一道逆向题
7.17
[buu soullike]
1、之前有情况"stack frame is too big",ida分析栈帧时出现异常,这种属于去除花指令之类的出现的问题;而此题出现了function is too big,查看汇编也发现没什么问题,可能function确实这么大
在文件管理器中找到此文件
将functionmaxsize进行更改,将64改大一些,可以改为1024,只不过反编译的时候会慢很多,耐心等待
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了
2、去除Eazfuscator混淆,de4dot工具
de4dot.exe "D:\xxx.exe"
7.21
DASCTF 2024暑期挑战赛
[Strangeprograme]
1、抽象,直接chef跑出来的十六进制转字符,明知道倒序了,之后发现有问号,以为最后几位的解密错了,就没交答案,吐了
在反调试的时候遇到is_debug,一个很好的办法,将所有调用is_debug的地方下断点,调试的时候将eax改为0
2、将加密逻辑动调,手搓一份,再解密即可