要去看埃菲尔铁塔的顶
欢迎关注本人微博:t.cn/RGSLVUk
自己动手写 程序外壳<二>
本次来讨论写壳是修改程序入口,从编译时地址转换运行时地址技术。
程序运行时由PE加载器将其加载内存,换算程序入口,根据重定位表进行地址重定位,根据导入表中dll 获取 所需API地址填入IAT中 ,然后调用main函数大致如此。
程序入口 = 加载地址 + OptionalHeader.AddressOfEntryPoint 。需要修改正常程序的OptionalHeader.AddressOfEntryPoint字段指向 外壳程序入口函数 RVA地址。
关于编译时地址转换运行时地址,其原理如下公式:
差值A = 编译地址 - 运行时地址
运行时地址 = 差值A + 编译地址
这里 用 CALL 指令 来获取运行时地址,然后计算偏移。
由CALL指令原理可知,其功能是将当前EIP(当前?) 保存在栈中,然后 JMP 到 指定地址
CALL 指令形如在下面场景中
void A() {}
void B() { A() ;}
那么其机器码是 E8 offset ( offset = 当前EIP - A的地址 ,然后取数值的补码)
总之 这中调用是以相对地址来调用,所以总是可以执行成功的。
那么 根据CALL 指令 将返回地址压入栈中 ,我们利用 [ ESP+ 4] 处即为返回地址,
利用这个值 - 5(CALL指令占5 字节) - 硬编码函数地址 = 差值A。
程序如下
// & RetAddress = ESP + 8
long __stdcall RunAddressSubIfAddress(long RetAddress)
{
long* pRetAddress;
long ret ;
if(0 == RetAddress) //触发异常
{
DB(0xCC);
DB(0xEB);
DB(0xCC);
}
pRetAddress = &RetAddress;
pRetAddress --;
ret = (*pRetAddress) - 5 - (long)GetRunAddressSubIfAddress;
*pRetAddress = RetAddress;
return ret;
}
__declspec(naked)
long __stdcall GetRunAddressSubIfAddress()
{
__asm call RunAddressSubIfAddress; //这里没有给数
}
// 将编译地址转换为运行时地址
PVOID __stdcall GetRunAddress(PVOID pData)
{
// 编译值 + 偏移(运行时 - 编译时)
long sub = GetRunAddressSubIfAddress();
long ret = (long)((long)pData + sub);
return (PVOID) ret;
}
//计算 运行地址 - 编译地址
//
//
// 跳到 RunAddressSubIfAddress 时
// push ebp
// mov ebp ,esp
// RetAddress 在: [ esp + 8 ] 实参 <其实值是 的 返回到 GetRunAddress 的下一条地址>
// [ esp+ 4 ] 是返回地址 也就是 GetRunAddress调用 RunAddressSubIfAddress 后的下一条地址
// pRetAddress = &RetAddress , 也就是 pRetAddress = esp + 8
// pRetAddress -- 就是 esp + 4 也就是指向的是 返回地址 (运行时)
// 那么 (* pRetAddress ) <运行是返回 GetRunAddressSubIfAddress 下一条地址 >- ( GetRunAddressSubIfAddress + 5 编译后下一条指令地址)
// 这个差值就是 运行时 与 编译时 的差值,得到这个差值就可以 定位所有 编译时数据了。。。
//