首页 > 分享 > 【reverse】通俗易懂的gcc内联汇编入门+示例:实现花指令

【reverse】通俗易懂的gcc内联汇编入门+示例:实现花指令

文章目录 引言依赖Hello worldDemo1:读取函数若干个字节的数据效果 Demo2:基础的花指令效果如何去除花指令 参考资料 引言

基于Visual Studio的内联汇编教程已然不少,且质量较好。但基于gcc/g++的内联汇编教程少得可怜,且即使是英文文档也……真是一把辛酸泪!但是看到本文的你们,就不必感受那些辛酸了,只需在5分钟后感叹一句:原来这么简单!因为我也是萌新,可能本文有诸多谬误,还请指出。

依赖 Windows10:mingw64Ubuntu20.04:g++8.4.0

本文juejin:https://juejin.cn/post/7149765832665464869/

本文52pojie:https://www.52pojie.cn/thread-1695068-1-1.html

本文csdn:https://blog.csdn.net/hans774882968/article/details/127141703

作者:hans774882968以及hans774882968以及hans774882968

Hello world

int a = 10, b; asm( "movl %1, %%eax; movl %%eax, %0;" :"=r"(b) // output :"r"(a) // input :"%eax" // clobbered register );

cpp

运行

12345678

也许初看此代码的你们和我有着一样的感受:

不习惯AT&T语法。这离谱的冒号写法是什么?

首先解决第二个问题。

第一个冒号表示输出Operands。这个例子中是b变量。输出Operands的字符串的第一个字符应该是'='。第二个冒号表示输入Operands。这个例子中是a变量。第三个冒号表示clobbered register,告诉gcc你的内联汇编需要使用一些寄存器,编译器应该在执行内联汇编之前将所有活动数据移出该寄存器。指定clobbered register往往是必要的,不指定的话会造成bug。第四个冒号(这个例子没用到,Demo2用到了)用于asm goto,主要用于实现汇编的跳转。

更具体准确的解释,可以看参考链接1。

接着解决第一个问题。不习惯AT&T,要改用intel风格:

代码加一行".intel_syntax noprefixn"。g++编译参数新加一项:-masm=intel。

asm( ".intel_syntax noprefixn" "...n" );

cpp

运行

1234

那么编译命令如下:

g++ -masm=intel g++_asm_hw.cpp -o g++_asm_hw.exe # Windows g++ -masm=intel g++_asm_hw.cpp -o g++_asm_hw # Ubuntu

bash

12

最后,我们只需要通过一些简单的实操,加深印象。

Demo1:读取函数若干个字节的数据

我们写一段代码,来读取main函数若干个字节的数据。源码如下(Windows和Ubuntu下一致):

#include <iostream> #include <cstdio> using namespace std; typedef long long LL; #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define re_(i,a,b) for(int i = (a);i < (b);++i) #define dwn(i,a,b) for(int i = (a);i >= (b);--i) const int N = 105; void dbg() { puts (""); } template<typename T, typename... R>void dbg (const T &f, const R &... r) { cout << f << " "; dbg (r...); } template<typename Type>inline void read (Type &xx) { Type f = 1; char ch; xx = 0; for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1; for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0'; xx *= f; } void read() {} template<typename T, typename ...R>void read (T &x, R &...r) { read (x); read (r...); } int main (int argc, char **argv) { int a[6]; LL main_addr = (LL) main; printf ("main %xn", (int) main_addr); asm ( ".intel_syntax noprefixn" "mov rax, %2n" "mov ebx, dword ptr cs:[rax]n" "mov %0, ebxn" "add rax, 4n" "mov ebx, dword ptr cs:[rax]n" "mov %1, ebxn" "add rax, 4n" :"=r" (a[0]), "=r" (a[1]) :"r" (main_addr) :"%rax" ); main_addr += 8; asm ( ".intel_syntax noprefixn" "mov rax, %2n" "mov ebx, dword ptr cs:[rax]n" "mov %0, ebxn" "add rax, 4n" "mov ebx, dword ptr cs:[rax]n" "mov %1, ebxn" "add rax, 4n" :"=r" (a[2]), "=r" (a[3]) :"r" (main_addr) :"%rax" ); main_addr += 8; asm ( ".intel_syntax noprefixn" "mov rax, %2n" "mov ebx, dword ptr cs:[rax]n" "mov %0, ebxn" "add rax, 4n" "mov ebx, dword ptr cs:[rax]n" "mov %1, ebxn" "add rax, 4n" :"=r" (a[4]), "=r" (a[5]) :"r" (main_addr) :"%rax" ); re_ (i, 0, 6) printf ("a[%d] = %xn", i, a[i]); return 0; }

cpp

运行

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879

注意:

输出Operands和输入Operands不宜超过4个(感觉可能是我姿势不对?),否则有bug。必须指定clobbered register为rax,否则出来的结果不对。我们可以通过对比两者反汇编的差异去探究具体的原因。

编译命令:

g++ -masm=intel x.cpp -o x.exe # Windows g++ -masm=intel -no-pie x.cpp -o x # Ubuntu

bash

12

-no-pie用于去除ASLR,在这里不是必需的。

效果

Ubuntu下:

main 317f91eb # 每次都会变,但无所谓 a[0] = fa1e0ff3 a[1] = e5894855 a[2] = 40ec8348 a[3] = 48cc7d89 a[4] = 64c07589 a[5] = 25048b48 1234567

在这里插入图片描述

Windows下:

main 4015c1 a[0] = e5894855 a[1] = 50ec8348 a[2] = 48104d89 a[3] = e8185589 a[4] = 1fb a[5] = e5058d48 1234567

通过IDA、x64dbg等工具查看main函数相关的数据,来确认结果的正确性。

Demo2:基础的花指令

我们来出一道带有最简单的花指令的逆向题。源码如下(Windows和Ubuntu一致):

#include <iostream> #include <cstdio> using namespace std; typedef long long LL; #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define re_(i,a,b) for(int i = (a);i < (b);++i) #define dwn(i,a,b) for(int i = (a);i >= (b);--i) const int N = 105; void dbg() { puts (""); } template<typename T, typename... R>void dbg (const T &f, const R &... r) { cout << f << " "; dbg (r...); } template<typename Type>inline void read (Type &xx) { Type f = 1; char ch; xx = 0; for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1; for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0'; xx *= f; } void read() {} template<typename T, typename ...R>void read (T &x, R &...r) { read (x); read (r...); } int main (int argc, char **argv) { string s; cin >> s; string t = "gmbh|tdvdug~"; asm goto ( ".intel_syntax noprefixn" "xor rax, raxn" "jz %l[label]n" ".byte 0xEBn" : : :"%rax" :label ); label: if (s.size() != t.size() ) { puts ("Lose"); return 0; } bool fl = true; re_ (i, 0, t.length() ) { if (char (s[i] + 1) != t[i]) { int tmp1 = 1; asm goto ( ".intel_syntax noprefixn" "mov eax, %0n" "test eax, eaxn" "jnz %l[label1]n" ".byte 0xEBn" : :"r" (tmp1) :"%rax" :label1 ); label1: fl = false; break; } } int tmp2 = 114514; asm goto ( ".intel_syntax noprefixn" "mov eax, %0n" "cmp eax, 114514n" "jz %l[label2]n" ".byte 0xEBn" : :"r" (tmp2) :"%rax" :label2 ); label2: puts (fl ? "Win" : "Lose"); return 0; }

cpp

运行

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586

这段代码有3处花指令,思路都一样,利用了jmp的opcode:0xEB,加上配套的必定跳转的语句。

gcc在内联汇编里实现跳转的资料真的特别少,我费劲心思才找到参考链接2,但参考链接2给出的写法里,也只有asm goto那种写法是可以通过编译的。

注意点:

报错:invalid 'asm': operand number out of range。参考链接2给出的%l0, %l1的写法会引发这个错误,不清楚原因,但只需要改成%l[label_name]就行。分清c语言标签和汇编标签,并且不要重名。

编译命令:

g++ -masm=intel 花指令hw.cpp -o 花指令hw.exe # Windows g++ -masm=intel 花指令hw.cpp -o 花指令hw # Ubuntu

bash

12 效果

Windows:

在这里插入图片描述

Ubuntu:

在这里插入图片描述

如何去除花指令

Windows:去除3个0xEB后,按F5即可。

int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v3; // rbx unsigned __int64 v4; // rbx char v5; // bl const char *v6; // rax char v8[32]; // [rsp+20h] [rbp-60h] BYREF char v9[47]; // [rsp+40h] [rbp-40h] BYREF char v10; // [rsp+6Fh] [rbp-11h] BYREF int v11; // [rsp+70h] [rbp-10h] int v12; // [rsp+74h] [rbp-Ch] int i; // [rsp+78h] [rbp-8h] char v14; // [rsp+7Fh] [rbp-1h] _main(argc, argv, envp); std::string::basic_string(v9); std::operator>><char>(refptr__ZSt3cin, v9); std::allocator<char>::allocator(&v10); std::string::basic_string(v8, "gmbh|tdvdug~", &v10); std::allocator<char>::~allocator(&v10); v3 = std::string::size(v9); if ( v3 == std::string::size(v8) ) { v14 = 1; for ( i = 0; ; ++i ) { v4 = i; if ( v4 >= std::string::length(v8) ) break; v5 = *(_BYTE *)std::string::operator[](v9, i) + 1; if ( v5 != *(_BYTE *)std::string::operator[](v8, i) ) { v12 = 1; v14 = 0; break; } } v11 = 114514; if ( v14 ) v6 = "Win"; else v6 = "Lose"; puts(v6); } else { puts("Lose"); } std::string::~string(v8); std::string::~string(v9); return 0; }

cpp

运行

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152

Ubuntu:去除3个0xEB后,回到main函数头,鼠标右键Create Function,按F5即可。

int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v3; // rbx unsigned __int64 v4; // rbx char v5; // bl const char *v6; // rax char v8; // [rsp+12h] [rbp-6Eh] BYREF char v9; // [rsp+13h] [rbp-6Dh] int i; // [rsp+14h] [rbp-6Ch] int v11; // [rsp+18h] [rbp-68h] int v12; // [rsp+1Ch] [rbp-64h] char v13[32]; // [rsp+20h] [rbp-60h] BYREF char v14[40]; // [rsp+40h] [rbp-40h] BYREF unsigned __int64 v15; // [rsp+68h] [rbp-18h] v15 = __readfsqword(0x28u); std::string::basic_string(v13, argv, envp); std::operator>><char>(&std::cin, v13); std::allocator<char>::allocator(&v8); std::string::basic_string(v14, "gmbh|tdvdug~", &v8); std::allocator<char>::~allocator(&v8); v3 = std::string::size(v13); if ( v3 == std::string::size(v14) ) { v9 = 1; for ( i = 0; ; ++i ) { v4 = i; if ( v4 >= std::string::length(v14) ) break; v5 = *(_BYTE *)std::string::operator[](v13, i) + 1; if ( v5 != *(_BYTE *)std::string::operator[](v14, i) ) { v11 = 1; v9 = 0; break; } } v12 = 114514; if ( v9 ) v6 = "Win"; else v6 = "Lose"; puts(v6); } else { puts("Lose"); } std::string::~string(v14); std::string::~string(v13); return 0; }

cpp

运行

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 参考资料 Inline Assembly——osdev:https://wiki.osdev.org/Inline_assemblygcc inline asm goto的写法:https://www.appsloveworld.com/c/100/6/labels-in-gcc-inline-assembly

相关知识

花指令总结
Java编程,揭秘那些鲜为人知的“花指令”:效率提升还是隐藏风险?
Reverse花指令及反混淆
给dll加花指令
木马免杀之汇编花指令技巧
逆向花指令入门
android 加花指令 花指令的作用
花指令的原理、常用花指令收集及花指令示例
代码混淆与反混淆学习
花指令

网址: 【reverse】通俗易懂的gcc内联汇编入门+示例:实现花指令 https://m.huajiangbk.com/newsview2499822.html

所属分类:花卉
上一篇: 易语言编程:999朵玫瑰教程与源
下一篇: 如何用python 画一朵花 –