逆向入门(1)
前言:
本篇文章面向想入门逆向的新手小白。
NSSCTF和BUUCTF是两个刷题网站,知识点下面会有对应的题,大家可以自己试着做一下
NSSCTF:https://www.nssctf.cn/problem
BUUCTF:https://buuoj.cn/challenges
1.看exe模式(位数)
把一个exe程序拖到DetectItEasy或者exeinfope里面看这个exe的位数(一般是64位有的时候也可能是32位)(后期脱壳也会用到这个工具)这决定是把exe拖到ida-32里面还是拖到ida-64里面。
这个exe程序就是64位的
2.IDA Pro的使用快捷键
题:BUU--easyre
先看位数
64位的程序,拖到ida-64
然后点yes
当然你也可以ctrl+f 查找main函数 然后再F5反汇编查看函数
ctrl+c将flag复制下来然后提交
3.位运算
符号 | 描述 | 运算规则 |
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0;有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
1.&
清零
如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
取一个数的指定位
比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。
判断奇偶
只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。
2.|
常用来对一个数据的某些位设置为1
比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。
3.~ (取反)
使一个数的最低位为零
使a的最低位为0,可以表示为:
a & ~1
~1的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0。因为" ~"运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。
4.^ (异或) 相同为0,不同为1
翻转指定位
比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。
与0相异或值不变
例如:1010 1110 ^ 0000 0000 = 1010 1110
3)交换两个数
void Swap(int &a, int &b){
if (a != b){
a ^= b;
b ^= a;
a ^= b;
}
}
5.>>
操作数每右移一位,相当于该数除以2。
6.<<
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
4.移位
[SWPUCTF 2021 新生赛]简简单单的逻辑
list[i]>>4,就是将当前列表元素向右移4位,即当i=0时 值为47 >>4 是将47的二进制 高位向右移动四位 也就是 0010111向右移动4位 00000010=2。
(list[i] & 0xf)<<4,就是先与0xf进行按位运算,保留低4位,然后左移4位。&0xf <<4 是将 47的二进制值00101111跟16的二进制值(1111)进行与运算,也就是当两个位置的数都为1时 与运算的结果才为1 否则都是0,00101111&00001111=00001111再将其移动到高位 因为有个<<4 =240
然后将两个值相加。
#include <stdio.h>
#include <string.h>int main() {char result[] = "bcfba4d0038d48bd4b00f82796d393dfec";char flag[100] = {0};int list[] = {47, 138, 127, 57, 117, 188, 51, 143, 17, 84, 42, 135, 76, 105, 28, 169, 25};int len = sizeof(list) / sizeof(list[0]);int i, key;for (i = 0; i < len; i++) {key = (list[i] >> 4) + ((list[i] & 0xf) << 4);int hex_value = 0;sscanf(result + 2 * i, "%2x", &hex_value);flag[i] = (char)(hex_value ^ key);}flag[len] = '\0';printf("%s\n", flag);return 0;
}
5.替换
直接看题
拖进ida-64里面
但这不是真正的flag
那我们再来分析一下函数
也就是把flag里面的o替换成0
(如果你的是数字的话,那你就按R转成字符)
所以flag就是{hell0_w0rld}
6.异或(Xor)
基本概念
1.1 符号
异或是一种二进制的 位运算,符号以 XOR 或 ^ 表示。
1.2 运算规则
相同为0,不同为1,即
1 ^ 1 = 0
0 ^ 0 = 0
1 ^ 0 = 1
由运算规则可知,任何二进制数与零异或,都会等于其本身,即 A ^ 0 = A。
1.3 异或性质
(1)交换律:A ^ B = B ^ A
(2)结合律:(A ^ B)^ C = A ^ ( B ^ C )
(3)自反性:A ^ B ^ B = A (由结合律可推:A ^ B ^ B = A ^ ( B ^ B ) = A ^ 0 = A)
异或运算的性质
(1)0^N=N, N^N=0;
(2)异或运算满足交换律和结合律,a^b=b^a (a^b)^c=a^(b^c)
题
放入ida64
左边ctrl+f寻找main函数,然后f5反汇编
分析代码(首先这是一个c语言)
一般for循环都是重要内容,所所以我们直接从for循环开始看
循环的条件是i<33,也就是从索引 1 开始到 32(因为条件是 i < 33),在每次循环中,它将当前字符 __b[i] 与前一个字符 __b[i - 1] 进行按位异或(^)操作,并将结果重新赋值给当前字符 __b[i]
在for循环下面还有一个if语句,如果if括号里面的东西为真,就输出“success”,这就要求strncmp(__b, global, 0x21uLL)这个条件为假,因为前面有个!(非)
strncmp(__b, global, 0x21uLL)这个语句是比较_b和global的前32个字符
我们看一下global的内容
发现了主要内容,shift+e导出
用c语言写解密函数
#include <stdio.h>
int main()
{int arr[33]={ 0x66, 0x0A, 0x6B, 0x0C, 0x77, 0x26, 0x4F, 0x2E, 0x40, 0x11,0x78, 0x80, 0x5A, 0x38, 0x55, 0x11, 0x70, 0x19, 0x46, 0x1F,0x76, 0x22, 0x40, 0x23, 0x44, 0x0E, 0x67, 0x06, 0x68, 0x0F,0x47, 0x32, 0x4F,};char flag[33]={0};int i;flag[0] = 0x66;for(i=1;i<33;i++){flag[i]=arr[i] ^ arr[i-1];}for(i=0;i<33;i++)printf("%c",flag[i]);
}
return 0;
就得到了flag
7.Base家族(换表)
我建议你积累一下换表base的脚本
不换表base
拖入ida
S1里面没东西,看看s2
用在线解base网站:https://www.toolhelper.cn/EncodeDecode/Base64
当然你也可以用工具
换表base
找main函数
点开看看
Base64的编码方式
所以密文就是 5Mc58bPHLiAx7J8ocJIlaVUxaJvMcoYMaoPMaOfg15c475tscHfM/8==
退回 f5反汇编
看一下这个函数
把61换成字符型,61的ASCII码是=
再看一下aQvejafhmuyjbac
所以base换表就是 qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD
就得到了flag
还可以使用在线网站
ctfer必备厨子:https://gchq.github.io/CyberChef/
就得到了flag
总结:
base64与base64换表
标准的base64加解密所用表是【ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/】刚好64个字符
如果对标准表中的字符进行顺序替换,称之为base64换表
-
对于标准表的base64,可以直接用编程语言提供的解密函数直接解密或者使用在线网站解密等
-
对于换表base64,需要多做一步映射,将换表的字符映射回标准表,然后再进行解密
怎么映射呢?拿本题来举例,换表是【qvEJAfHmUYjBac+u8Ph5n9Od17FrCL/X0gVtM4Qk6Tz3wNSsyoebilxWKGZpRD】
密文是【5Mc58bPHLiAx7J8ocJllaVUxaJvMcoYMaoPMaOfg15c475tschfM/8==】
密文中第一个字符是5,在换表中的索引(下标)为19,而在标准表中,索引19处的字符为【T】
密文中第二个字符是M,在换表中的索引(下标)为37,而在标准表中,索引19处的字符为【I】
以此类推,完成映射
映射可以通过自写循环来实现,也可以直接使用字符串函数translate。
自写循环映射
import base64 #导入base64模块用于解密
s1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' #标准表
s2 = 'qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD' #base64换表
en_text = '5Mc58bPHLiAx7J8ocJIlaVUxaJvMcoYMaoPMaOfg15c475tscHfM/8==' #密文map_text = '' #用于存放密文通过换表映射回标准表的字符
for i in en_text:if(i != '='): #注意密文中存在等号的情况下,不需要替换!idx = s2.index(i) #获取每个密文的字符在换表中的索引map_text += s1[idx] #取出标准表中的该索引的字符,就是正常base64加密的密文else:map_text += i
print(map_text) #可以先看看标准表base64加密的密文
print(base64.b64decode(map_text)) #直接使用提供的base64解密函数,获得明文,就是flag
库函数映射
import base64 #导入base64模块用于解密
s1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' #标准表
s2 = 'qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD' #base64换表
en_text = '5Mc58bPHLiAx7J8ocJIlaVUxaJvMcoYMaoPMaOfg15c475tscHfM/8==' #密文map = str.maketrans(s2, s1) #用str类中的maketrans建立映射,注意第一个参数是需要映射的字符串,第二个参数是映射的目标(将换表的字符映射回标准表)
map_text = en_text.translate(map) #映射实现替换密文,替换前是base64换表加密,替换后则是base64标准表加密
print(map_text) #可以先看看标准表加密的密文
print(base64.b64decode(map_text)) #直接使用提供的base64解密函数,获得明文,就是flag
8.脱upx壳
首先要查壳,可以用exeinfope工具,把exe文件拖进去
无壳
有壳
对于有壳的文件你就得脱壳(注意要把你要脱壳的文件放在upx工具里面)保持路径一致
在脱upx壳的工具里面cmd打开终端
输入命令
upx -d 你要脱壳的exe名称
这就脱完了
不信你再检查一下
这就完了