攻防世界 Windows_Reverse1
wp的思路方法不是我的,我只是做记录理思路
题目
分析过程
PE查壳,有一个UPX壳
用工具脱壳
IDA看看main函数
两个关键参数:input(v6改名) 和 flag(v4改名)
光看反汇编函数,这两个参数怎么都联系不起来
想着地址说不定连在一起呢?
-00000404 input db ?
-00000804 flag db ?
好吧,地址都联系不起来
通过学习才知道有地址偏移间接操作
看看main函数的汇编
sub esp, 804h
esp寄存器存放的是栈指针,栈指针减804h,可以将esp+804h看作ebp基指指针
堆栈指针及相关寄存器
堆栈是操作系统中,最为常见的一种数据结构。严谨的说,堆栈是包括堆和栈两种数据结构的,但是我们通常说堆栈,其实就是指栈。在栈中,最重要的两个指针是 SP(栈指针) 和 BP(基指指针)。SP(Stack Pointer),栈指针,在 32 位系统中,ESP(Extended SP) 寄存器存放的就是栈指针。在 64 位系统中,表现为 RSP 寄存器。SP 永远指向系统栈最上面一个栈帧的栈顶。所以 SP 是栈顶指针
BP(Base Pointer),基指指针,在 32 位系统中,EBP(Extended BP)寄存器存放的就是基指指针。在 64 位系统中,表现为 RBP 寄存器。BP 指向栈帧的底部,一般称之为栈底指针
上述定义相信你会在大多数博客见到,但是这些指针及寄存器的作用到底是什么呢?SP,指针即地址,存放栈顶指针,目的就是,下一次对栈操作的时候,系统可以及时找到栈的当前位置。
————————————————版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/song_lee/article/details/105297902
后面的红框可以看作在 基址esp+804h 的基础上 加上中间代码的指令字节长度,其实就是取input和flag
指令字长度=操作码长度+地址码长度
(源操作数地址长度和目的操作数地址长度)汇编指令长度与寻址方式有关,规律或原则如下:
一、没有操作数的指令,指令长度为1个字节
二、操作数只涉及寄存器的的指令,指令长度为2个字节
如:mov bx,ax
三、操作数涉及内存地址的指令,指令长度为3个字节
如:mov ax,ds:[bx+si+idata]
四、操作数涉及立即数的指令,指令长度为:寄存器类型+1
8位寄存器,寄存器类型=1,如:mov al,8;指令长度为2个字节
16位寄存器,寄存器类型=2,如:mov ax,8;指令长度为3个字节
五、跳转指令,分为2种情况:
1、段内跳转(指令长度为2个字节或3个字节)
jmp指令本身占1个字节
段内短转移,8位位移量占一个字节,加上jmp指令一个字节,整条指令占2个字节
如:jmp short opr
段内近转移,16位位移量占两个字节,加上jmp指令一个字节,整条指令占3个字节
如:jmp near ptr opr
2、段间跳转,指令长度为5个字节
如:jmp dword ptr table[bx][di]
或 jmp far ptr opr
或 jmp dword ptr opr
注意:形如“jmp 1234:5678”的汇编指令,是在Debug中使用的汇编指令,汇编编译器并不认识,如果在源程序中使用,那么在编译时便会报错。
input 给了 eax
flag 给了 ecx
看看sub_401000函数是否能把两个参数联系起来
有ecx,有eax,可以联系起来
v1
是一个 _BYTE
类型的指针,并且注释指出 ecx
寄存器可能被用作 v1
的存储位置
v4 = input - v1
注意,我们是将 &input 传入这个函数,&input表示取input变量的地址
这行代码表示将input的地址减去v1的地址,v4是地址差--->因此v1+v4表示input的地址
*v1 = byte_402FF8[(char)v1[v4]];
先看看括号里面 v1[v4] :其实这个可以看作是v1+v4,也就是input的地址——v1不断+1,可以看作是一种遍历input的新操作
数组格式:参照地址[偏移量]
本质:通过参照地址+偏移量来遍历数组
再看看括号外面byte_402FF8[...]:可以看作将遍历到的 input中的单个字符的ascii值 作为偏移量在byte_402FF8数组中寻址
那看看byte_402FF8数组
中间还有一些乱七八糟的东西,最后有两个疑似关键的字符串
ASCII表显示字符是从32开始的,可显示字符编号范围是32-126(0x20-0x7E),共95个字符。
我们是将input的单个字符的ascii值作为偏移量,这个ascii值最小从32开始,32之前的都取不到
敲计算器可得,地址是从0x403018开始,从0x403078结束,也就是说,只查后面两个字符串
注意!写脚本的时候不能把前面丢掉,如果你把前面的丢掉,原本在32索引位置的字符串就变成0索引了,就查不到了
综上,sub_401000函数的作用就是将输入的input单个字符的ascii值作为偏移量,作为偏移量在byte_402FF8数组中寻址,结果给*v1,也就是放到ecx寄存器里,最后给了flag
逆向脚本
1 key1=[ 2 255, 255, 255, 255, 255, 255, 255, 255, 78, 230, 3 64, 187, 177, 25, 191, 68, 255, 255, 255, 255, 4 255, 255, 255, 255, 254, 255, 255, 255, 1, 0, 5 0, 0, 126, 125, 124, 123, 122, 121, 120, 119, 6 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 7 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 8 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 9 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 10 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 11 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 12 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 13 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 14 38, 37, 36, 35, 34, 33, 32, 0, 0, 0, 15 0, 0 16 ] 17 key2="DDCTF{reverseME}" 18 flag="" 19 for i in key2: 20 flag+=chr(key1[ord(i)]) 21 print("flag{"+flag+"}")
flag
flag{ZZ[JX#,9(9,+9QY!}