调用欺骗拾遗

调用欺骗拾遗

https://grxer.github.io/2023/05/04/ELF%20Dynamical-link/ 中显示的运行时链接那段代码让我想起了之前在lnnks老师那里,看到一个比较有意思的code

环境vs2022 x86 Release版

#include <windows.h>
#include <iostream>
#pragma optimize("",off)
void show(void* pf, HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
) {
//ar[2]:ebp;ar[3] ret; jar[4] void *pf;
int ar[2];
ar[4] = ar[3] ^ ar[4];
ar[3] = ar[3] ^ ar[4];
ar[4] = ar[3] ^ ar[4];
}
int main() {
show(MessageBoxW, 0, TEXT("grxer"), TEXT("grxer"), MB_OK);
std::cout << "hello" << std::endl;
return 0;
}

show函数利用栈溢出把返回地址所处地址ar[3]改为pf指针指的函数(push参数后call嘛),只改返回地址pf函数不能正常返回main的,所以我们把pf的返回地址ar[3]处改为了原来show的返回地址,弹出messagebox后正常返回main函数,但是main在ret的时候会crash掉

image-20230506154546803

为什么呢?

由于messagebox函数是winapi,Windows API默认的函数调用协议__stdcall,所以最后以一条ret 10h的指令来内平栈,show函数是C/C++默认的函数调用协议_cdecl,在调用返回main后会用 add esp,14h进行外平栈,所以我们比原来的普通show函数调用多了一个ret 10h,ret10h等效于 pop eip;add esp,10h ,所以我们比原来多平了20字节的栈

最简单的一种方法,是在调用show之前用内联汇编压5次栈,或者我们去分配5个四字节的局部变量,去填充一块栈空间

#include <windows.h>
#include <iostream>
#pragma optimize("",off)
void show(void* pf, HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
) {
//ar[2]:ebp;ar[3] ret; jar[4] void *pf;
int ar[2];
ar[4] = ar[3] ^ ar[4];
ar[3] = ar[3] ^ ar[4];
ar[4] = ar[3] ^ ar[4];
}
int main() {
//1.
_asm {
push 666
push 666
push 666
push 666
push 666
}
//2.
//int ar[5]={0};
show(MessageBoxW, 0, TEXT("grxer"), TEXT("grxer"), MB_OK);
std::cout << "hello" << std::endl;
return 0;
}

解决了栈的问题但不完美,我们这样做调用的目的是隐藏掉调用痕迹去迷惑逆向分析的人也好或者杀软也好

push完5个参数后,show函数也要push,谁家好人push十个参数啊,一眼顶真了

我们调用show返回main时由于一些非法操作导致栈其实已经平了,add会导致栈不平,我们可以控制返回地址干嘛不直接跳过这条add指令呢

image-20230506152018853

直接ar[4]+=3;

#include <windows.h>
#include <iostream>
#pragma optimize("",off)
void show(void* pf, HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
) {
//ar[2]:ebp;ar[3] ret; jar[4] void *pf;
int ar[2];
ar[4] = ar[3] ^ ar[4];
ar[3] = ar[3] ^ ar[4];
ar[4] = ar[3] ^ ar[4];
ar[4] += 3;
}
int main() {
(show)(MessageBoxW, 0, TEXT("grxer"), TEXT("grxer"), MB_OK);
std::cout << "hello" << std::endl;
return 0;
}

show函数是cdecl的外平栈,我们可以把他强转为stacall在调用时来抹除这个外平栈,相当于我show函数编译成的代码是外平栈的,但是我们在调用时把他当作内平栈的stdcall,欺骗编译器不给我们平栈

#include <windows.h>
#include <iostream>
#pragma optimize("",off)
void show(void* pf, HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
) {
//ar[2]:ebp;ar[3] ret; jar[4] void *pf;
int ar[2];
ar[4] = ar[3] ^ ar[4];
ar[3] = ar[3] ^ ar[4];
ar[4] = ar[3] ^ ar[4];
}
typedef void(_stdcall* pf)(void* pf, HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
int main() {
((pf)show)(MessageBoxW, 0, TEXT("grxer"), TEXT("grxer"), MB_OK);
std::cout << "hello" << std::endl;
return 0;
}