浅谈逆向中关于位运算的常见问题
零、前言
在CTF比赛中,逆向题目通过IDA反编译后为C/C++的汇编逻辑。而我们作为打比赛的,最常用的语言肯定是python了。
问题是,就算我们逆出来逻辑完全一样,但我们在编译器里实际编译出来却跑出了乱码。大多数时候,只是我们没想到两语言之间的整数溢出不同。
问题:逆向逻辑完全正确,但 Python 解密出来是乱码。
本文仅浅谈相关例子与建议
一、C 与 Python 整数模型差异
C 语言整数
C 中整数是 固定宽度。
| 类型 | 位宽 |
|---|---|
| char | 8 bit |
| short | 16 bit |
| int | 32 bit |
| long long | 64 bit |
例如:
unsigned char
只有 8 bit,在IDA中以BYTE显示,范围是0 ~ 255。
位结构
11111111
↑
8 bit
最大值:
255
Python 整数
Python 使用:
Arbitrary Precision Integer
也就是:
无限精度整数
示例:
2 ** 1000 |
在 Python 中完全合法。
但在 C 中会溢出。
二、什么是整数溢出
例子:
unsigned char x = 250; |
数学结果表示:
250 + 20 = 270
但 8bit 最大是:
255
溢出过程
270 = 100001110
只保留 低 8 位:
00001110
结果:
14
图解
270: 100001110
↓
保留低8位
↓
00001110
↓
14
数学等价:
270 mod 256
三、Python 如何模拟溢出
使用 mask:
& 0xFF
示例:
x = (250 + 20) & 0xFF |
结果:
14
位运算图解
270 = 100001110
0xFF = 11111111
AND
----------------
00001110
四、CTF最常见运算陷阱
1.加法溢出
C 代码
unsigned char x = 250; |
真实结果:
14
Python错误写法
x = 250 + 20 |
结果为:
270
正确写法
x = (250 + 20) & 0xFF |
五、减法负数问题
C 代码
unsigned char x = 10; |
数学结果:
-40
C 实际结果:
216
原因是:
-40 mod 256 = 216
Python正确
x = (10 - 50) & 0xFF |
六、按位取反 ~(最常见)
C
unsigned char x = 0x55; |
位变化:
01010101
↓
10101010
结果:
0xAA = 170
Python直接计算
~0x55 |
结果:
-86
原因:
Python使用 无限补码。
正确写法
(~0x55) & 0xFF |
结果:
170
七、左移溢出
C 代码:
unsigned char x = 200; |
计算:
200 << 2 = 800
8 bit截断:
800 mod 256 = 32
Python正确写法:
x = (200 << 2) & 0xFF |
八、右移问题
右移分为两种:算术右移和逻辑右移。
算术右移
为符号位扩展。
-8 >> 1 = -4
逻辑右移
即为高位补0。
Python只有:
算术右移
如果要逻辑右移:
(x & 0xFFFFFFFF) >> n |
九、char 符号问题
C 中:
char
可能是:
signed char
或
unsigned char
例如:
char x = 200; |
真实值:
200 - 256 = -56
Python模拟:
x = 200 |
十、脚本模板
推荐准备以下函数:
def u8(x): |
示例:
plain = u8((cipher + 86) ^ 0x32) |
十一、例题
1.[HGAME 2023 week1]easyenc
下载了题目附件,先查壳&查看基本信息:

没壳,直接进IDA,主函数main即为核心加密逻辑
{ |
可以看到,v8数组+ v9存放的就是我们的密文,do-while即为我们的输入环节;循环里的*((_BYTE *) v10 + v3)就是所传入的输入数组,循环校验输入与原本密文进行对比
根据查看基本信息,x86是小端序的,所以内存中实际为struct.pack(" <i ", v8[i] ),从低字节到高字节,得按小端序再转换为字节流。
加密过程是
y = (x ^ 0x32) - 86 |
所以逆着加密即为解密
input[i] = (cipher[i] +86) ^ 0x32 |
我们尝试编写exp:
import struct |
输出的是一堆乱码

我们重新看反编译代码,注意到 *((BYTE*) V8 + V3),前面说过,BYTE实际上就是unsigned char,当C进行运算时,写回char只能保留低 8 位,也等价于value mod 256。
而我们是用python编写的exp,python并不会自动截断,这时候就要用到& 0xFF了。
0xFF = 255 = 11111111b |
作用就是模拟 C 的字节溢出,只保留最低 8 位,使 Python 运算结果与程序中的 unsigned char 完全一致。
有了以上知识,我们再次编写exp:
import struct |
这个时候就能输出正确的flag了

所以,有时候我们分析完反汇编代码,写了正确逻辑的exp后跑出了乱码,有时候并不是思路不对,而是没理解语言背后的一些溢出机制,一些差异。