吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2197|回复: 10
收起左侧

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

  [复制链接]
hans7 发表于 2022-10-2 12:15
本帖最后由 hans7 于 2024-5-22 13:14 编辑

引言

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

谁会g  内联汇编.jpg

依赖

  • 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

4-读取main函数前若干字节的内容效果-允许ASLR.jpg

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:

5-花指令效果-Windows.jpg

Ubuntu:

6-花指令效果-Ubuntu.jpg

如何去除花指令

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;
}

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;
}

参考资料

  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

免费评分

参与人数 3威望 +1 吾爱币 +23 热心值 +3 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
RUO + 2 + 1 热心回复!
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Liliright 发表于 2022-10-2 12:20
膜拜膜拜,太厉害了
El28PoJie 发表于 2022-10-2 17:03
sgbyg 发表于 2022-10-2 17:16
aonima 发表于 2022-10-2 17:47
很少用gcc
ICzcz 发表于 2022-10-2 19:09

太厉害了,大佬!!!
RUO 发表于 2022-10-2 19:38
帖子左侧那个目录是怎么实现的?发了几张帖子但是不知道怎么搞
第一品霄 发表于 2022-10-2 22:29
学习一下了
 楼主| hans7 发表于 2022-10-2 23:47
RUO 发表于 2022-10-2 19:38
帖子左侧那个目录是怎么实现的?发了几张帖子但是不知道怎么搞

全文都用markdown来写,目录靠markdown的n级标题(n个”#“表示n级标题)来实现,写完了点击MD那个图标来插入markdown文本。
RUO 发表于 2022-10-3 18:57
hans7 发表于 2022-10-2 23:47
全文都用markdown来写,目录靠markdown的n级标题(n个”#“表示n级标题)来实现,写完了点击MD那个图标来 ...

忘记还能markdown了,还用markdown的话估计的重新排版了。。只能下次再试试了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 01:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表