文章目录

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

引言

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

依赖

  • Windows10:mingw64
  • Ubuntu20.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
);

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

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

首先解决第二个问题。

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

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

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

  1. 代码加一行".intel_syntax noprefix\n"
  2. g++编译参数新加一项:-masm=intel
asm(".intel_syntax noprefix\n""...\n"
);

那么编译命令如下:

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

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

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 %x\n", (int) main_addr);asm (".intel_syntax noprefix\n""mov rax, %2\n""mov ebx, dword ptr cs:[rax]\n""mov %0, ebx\n""add rax, 4\n""mov ebx, dword ptr cs:[rax]\n""mov %1, ebx\n""add rax, 4\n":"=r" (a[0]), "=r" (a[1]):"r" (main_addr):"%rax");main_addr += 8;asm (".intel_syntax noprefix\n""mov rax, %2\n""mov ebx, dword ptr cs:[rax]\n""mov %0, ebx\n""add rax, 4\n""mov ebx, dword ptr cs:[rax]\n""mov %1, ebx\n""add rax, 4\n":"=r" (a[2]), "=r" (a[3]):"r" (main_addr):"%rax");main_addr += 8;asm (".intel_syntax noprefix\n""mov rax, %2\n""mov ebx, dword ptr cs:[rax]\n""mov %0, ebx\n""add rax, 4\n""mov ebx, dword ptr cs:[rax]\n""mov %1, ebx\n""add rax, 4\n":"=r" (a[4]), "=r" (a[5]):"r" (main_addr):"%rax");re_ (i, 0, 6) printf ("a[%d] = %x\n", i, a[i]);return 0;
}

注意:

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

编译命令:

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

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

效果

Ubuntu下:

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

Windows下:

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

通过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 noprefix\n""xor rax, rax\n""jz %l[label]\n"".byte 0xEB\n":::"%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 noprefix\n""mov eax, %0\n""test eax, eax\n""jnz %l[label1]\n"".byte 0xEB\n"::"r" (tmp1):"%rax":label1);
label1:fl = false;break;}}int tmp2 = 114514;asm goto (".intel_syntax noprefix\n""mov eax, %0\n""cmp eax, 114514\n""jz %l[label2]\n"".byte 0xEB\n"::"r" (tmp2):"%rax":label2);
label2:puts (fl ? "Win" : "Lose");return 0;
}

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

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

注意点:

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

编译命令:

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

效果

Windows:

Ubuntu:

如何去除花指令

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

int __cdecl main(int argc, const char **argv, const char **envp)
{__int64 v3; // rbxunsigned __int64 v4; // rbxchar v5; // blconst char *v6; // raxchar v8[32]; // [rsp+20h] [rbp-60h] BYREFchar v9[47]; // [rsp+40h] [rbp-40h] BYREFchar v10; // [rsp+6Fh] [rbp-11h] BYREFint 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";elsev6 = "Lose";puts(v6);}else{puts("Lose");}std::string::~string(v8);std::string::~string(v9);return 0;
}

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

int __cdecl main(int argc, const char **argv, const char **envp)
{__int64 v3; // rbxunsigned __int64 v4; // rbxchar v5; // blconst char *v6; // raxchar v8; // [rsp+12h] [rbp-6Eh] BYREFchar 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] BYREFchar v14[40]; // [rsp+40h] [rbp-40h] BYREFunsigned __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";elsev6 = "Lose";puts(v6);}else{puts("Lose");}std::string::~string(v14);std::string::~string(v13);return 0;
}

参考资料

  1. Inline Assembly——osdev:https://wiki.osdev.org/Inline_assembly
  2. gcc inline asm goto的写法:https://www.appsloveworld.com/c/100/6/labels-in-gcc-inline-assembly

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

  1. linux gcc 内联汇编入门

    目录 2. 概览(Overview of the whole thing.) 3.GCC汇编语法(GCC Assembler Syntax.) 3.1. 源-目标顺序(Source-Destinati ...

  2. (转)GCC内联汇编入门

    转自:http://blog.csdn.net/wuyao721/article/details/3573598 原文为GCC-Inline-Assembly-HOWTO,在google上可以找到原文 ...

  3. 最牛X的GCC 内联汇编

    导读 正如大家知道的,在C语言中插入汇编语言,其是Linux中使用的基本汇编程序语法.本文将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 ...

  4. GCC Inline ASM GCC内联汇编

    GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM--GCC内联汇编.这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码 ...

  5. 汇编语言---GCC内联汇编

    GCC支持在C/C++代码中嵌入汇编代码,这些代码被称作是"GCC Inline ASM"(GCC内联汇编); 一.基本内联汇编(寄存器前一个%) GCC中基本的内联汇编非常易懂, ...

  6. 【转贴】GCC内联汇编基础

    原文作者 Sandeep.S 英文原文 [https://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html] 本文将介绍GCC编译环境下 ...

  7. gcc 内联汇编用法介绍

    前言 大部分内容翻译提取自某国外HOW-TO文档,原地址: http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html AT& ...

  8. linux arm gcc 内联汇编参考手册

    关于本文档 GNU C 编译器为 ARM 精简指令系统处理器提供了在 C 代码中内嵌汇编的功能.这种非常酷的特性提供了一些 C 代码没有的功能,比如手工优化软件关键代码.使用相关的处理器指令. 本文假 ...

  9. C语言 intel架构处理器下利用gcc内联汇编 fcos 指令 实现三角函数计算小程序

    Intel提供了丰富的浮点运算指令,fcos就是其中之一.下面就来实际试试看吧. #include <stdio.h>int main(int argc, char** argv) {if ...

最新文章

  1. 将扫描字符转换成点阵信息
  2. 第二章:搭建Android开发环境(读后感)
  3. 洛谷P1155 双栈排序
  4. 【TypeError: float() argument must be a string or a number, not ‘map’】
  5. Java并发编程之堵塞队列介绍以及SkipList(跳表)
  6. Set、Map集合、栈、队列
  7. asp.net中实现群发邮件功能
  8. 安卓学习笔记40:基于套接字网络编程
  9. .net中模拟键盘和鼠标操作
  10. SOTA来啦!BERT又又又又又又魔改了!DeBERTa登顶GLUE~
  11. gifrecord可以卸载吗_频繁安装卸载 App,手机真能受得了吗?!
  12. 读《因果的真相》第三章摘抄笔记
  13. Wps格式怎么转换成word,只要三分钟轻松搞定
  14. 2023 年对程序员英语学习记单词很有帮助的网站
  15. 前端基础学习之h5c3-购物车宣传页动画小练习
  16. 三极管频率大,放大倍数小原因
  17. dlib.get_frontal_face_detector()函数返回值
  18. 基于【GIS地理信息+实景三维】在一体化地质灾害监测预警平台中的核心应用
  19. Java分别采用继承、多态、抽象类、接口实现猫和狗的入门案例
  20. 丹佛斯变频器al13故障_丹佛斯变频器十大常见故障分析及维修对策

热门文章

  1. Linux系统下安装wgrib2
  2. 互联网面试——WPF面试问题
  3. 运算放大器的性能指标
  4. mysql修改表字段名称
  5. python输出换行
  6. Ubuntu16.04 Flash Player播放插件安装
  7. 如何把视频或者音频转成文字
  8. Websocket系列 -- 协议详解
  9. linux cadaver 命令,备份Linux系统的数据到坚果云
  10. 初级,中级,高级程序员需要具备的能力