- 这个题的意思是:
一共8栈灯,每盏灯的状态改变都会改变它前后挨着的两个灯的状态,要求点亮所有的灯获得flag
要求输入数的范围在1-8之间,0是重新开始
思路一:
- 一:
算法是1-8,8栈灯,点一个改变3状态,写脚本爆破吧!!(想模仿出题人的想法把中间过程表达出来,结果还是自己太年 轻)
贪心
一个灯如果按了第二下,就会抵消上一次按下所产生的影响。
因此,一个灯只有按或者不按两种情况,不存在一个灯要开关多次的情况。
例如八个灯 00000000
按1后 11000000
按3后 10110000
按1后 01110000
这和八个灯 00000000
只按一次3后 01110000
是完全相同的情况
#####但是后来我忽略了一个因素:我们这里最后一个灯会影响第一个灯,它是个环,放在这不行
lamp = [0, 0, 0, 0, 0, 0, 0, 0]
a = []
status = True
for i in range(1, 6, 1):
if lamp[i-1] == 0:
lamp[i-1] ^= 1
lamp[i] ^= 1
lamp[i+1] ^= 1
if lamp[7] == 0:
lamp[7] = 1
lamp[0] ^= 1
lamp[1] ^= 1
print(lamp)
lamp[7] = 1
lamp[0] = 1
lamp[1] = 1
# 我想多了
上面的脚本压根不是那个情况,回头去od动态调试
思路二:
毕竟是逆向,后来进入od再次寻找解决方案:
搜索到字符串done the flag is找到汇编代码处:
00FDE968 done the flag is
这个地址的头是FDE940
跳转来自00FD7AB4
直接在开头的地方把其中的call指令改成call 00FD7AB4
直接到达了游戏成功应该在的位置
eax=0046F5D8, (ASCII "zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}")
终于啊!!!
它的内存的各种mov操作把字符一个接一个压到栈中,后来又一波操作整个打印出来
F4的作用是,一下循环一次
知识点:
- 1 按计数寄存器 ((E)CX) 中指定的次数重复执行字符串指令,直到寄存器 (E)CX 中的计数递减到 0,或是重复到 ZF 标志不再满足指定的条件。REP(重复)、REPE(相等时重复)、REPNE(不相等时重复)、REPZ(为零时重复)及 REPNZ(不为零时重复)助记符都是可以添加到一些字符串指令中的前缀。REP 前缀可以添加到 INS、OUTS、MOVS、LODS 及 STOS 指令,REPE、REPNE、REPZ 及 REPNZ 前缀可以添加到 CMPS 与 SCAS 指令。(REPZ 与 REPNZ 前缀分别是 REPE 与 REPNE 前缀的同义形式)。同非字符串指令一起使用时,REP 前缀的行为未定义。
- REP 前缀一次只能应用于一条字符串指令
- 2stos 包括 stosb stosw stosd, 涉及的寄存器是eax, edi, 功能如下:
stosb 将al中的值复制到 byte ptr es:[edi] 中, 同时edi++
stosw 将ax中的值复制到 word ptr es:[edi] 中, 同时edi++
stosd 将eax中的值复制到 dword ptr es:[edi] 中, 同时edi++
int b[0x33];
__asm //下面这三句汇编语句用来初始化数组b, 简单高效
{
XOR EAX, EAX lea edi, b
mov ecx, 0x33
rep stosd
}
指令, 终止条件1,终止条件2
REP, ECX=0, 无
kREPE/REPZ, ECX=0, ZF=0
REPNE/REPNZ,ECX=0, ZF=1
mov eax,0CCCCCCCCh //int3中断,0CCCCCCCCh相当于0xCCCCCCCC,这段代码是初始化堆栈和分配局部 变量用的,往分配好的局部变量空间放入int3中断的原因是:防止该空间里的东东被意外执行。
rep stos dword ptr es:[edi] //重复stos指令ecx中值的次数
- 3
%p是专门打印地址的, %x是以十六进制形式打印数据
打印size_t 类型的值时要小心。这是无符号值,如果选错格式说明符,可能会得到不可靠的结果。推荐的格式说明符是%zu
size_t 用做sizeof 操作符的返回值类型,同时也是很多函数的参数类型,包括malloc 和strlen。它是无符号整数
- 4 本题的贪心算法
我们只需要考虑是否按下第一个灯。因为如果第一个灯的状态被确定了,那么是否按下第二个灯也就决定了(如果第一个灯与期望不同,则按下,如果期望相同,则不按下)同理,第三个灯是否按下也唯一确定。所以,本题只要分两种情况:灯1被按下和没有被按下之后使用for循环判断别的灯是否需要按下即可当循环结束,若现在的灯况与答案相同,则输出两种方案中按键次数最少的,若不同,则impossible! ACM特殊密码锁:我们这个题不适用,因为我们的第一个就算是确定还有最后一个可以影响它 #include<iostream> #include<cstring> #include<algorithm> using namespace std; string lockin,lockout; int L1[35],L2[35],L3[35]; int main(){ int num1=0,num2=0; cin>>lockin>>lockout; int len=lockin.length(); for(int i=0;i<len;i++){ L1[i]=(int)(lockin[i]-'0'); L3[i]=L1[i]; L2[i]=(int)(lockout[i]-'0'); } //不按第一把锁 for(int i=1;i<len;i++){ if(L1[i-1]!=L2[i-1]){ num1++; L1[i-1]^=1; L1[i]^=1; L1[i+1]^=1; } } //按第一把锁 L3[0]^=1; L3[1]^=1; num2++; for(int i=1;i<len;i++){ if(L3[i-1]!=L2[i-1]){ num2++; L3[i-1]^=1; L3[i]^=1; L3[i+1]^=1; } } int flag1=1,flag2=1; //flag1标志第一个不按下能不能解开(跟L2对照),flag2标志第二个 for(int i=0;i<len;i++){ if(L1[i]!=L2[i]){ flag1=-1; } if(L3[i]!=L2[i]){ flag2=-1; } } if(flag1==-1 && flag2==-1){ //两个都不行就完蛋 cout<<"impossible"<<endl; } else if(flag1==1 && flag2==1){ int num=min(num1,num2); //两个都可以的话,取小的那一个 cout<<num<<endl; } else{//只有一个可以的情况 if(flag1==1) cout<<num1<<endl; if(flag2==1) cout<<num2<<endl; } return 0; }
- 5
INT3断点是断点的一种,在诸如Ollydbg中的快捷键是F2,是一种很常用的断点类型。INT3指令的机器码为CC,所以通常也称之为CC指令。用INT3断点的好处是可以设置无数个断点,缺点是改变了原程序指令,容易被软件检测到。例如为了防范API被下断,一些软件会检测API的首地址是否为CCh,以此来判断是否被下了断点