Posted on ::

在nix上开发tauri

最好使用direnv

{
  description = "Tauri Dev Shell";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    rust-overlay.url = "github:oxalica/rust-overlay";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    {
      nixpkgs,
      rust-overlay,
      flake-utils,
      ...
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        overlays = [ (import rust-overlay) ];
        pkgs = import nixpkgs {
          inherit system overlays;
        };
      in
      {
        devShells.default =
          with pkgs;
          mkShell {
            nativeBuildInputs = [
              pkg-config
              clang_20
              libllvm
              gobject-introspection
              cargo
              (rust-bin.beta.latest.default.override {
                extensions = [
                  "rust-src"
                  "rust-analyzer"
                  "clippy"
                ];
                targets = [ "x86_64-pc-windows-msvc" ];
              })
              cargo-xwin
              rustup
              cargo-tauri
              nodejs
            ];
            buildInputs = [
              at-spi2-atk
              atkmm
              cairo
              gdk-pixbuf
              glib
              gtk3
              harfbuzz
              librsvg
              libsoup_3
              pango
              webkitgtk_4_1
              openssl
            ];
          };
      }
    );
}

接下来安装create-tauri-app

cargo install create-tauri-app --locked
cargo create-tauri-app

创建项目

# cargo create-tauri-app
✔ Project name · my-tauri
✔ Identifier · fun.colorsky.tauri
✔ Choose which language to use for your frontend · Rust - (cargo)
✔ Choose your UI template · Vanilla

Template created! To get started run:
  cd my-tauri
  cargo tauri android init

For Desktop development, run:
  cargo tauri dev

For Android development, run:
  cargo tauri android dev

项目文件就在 ./my-tauri

大致长这样

my-tauri
├── README.md
├── src
│   ├── assets/
│   ├── index.html
│   ├── main.js
│   └── styles.css
└── src-tauri
    ├── build.rs
    ├── capabilities/
    ├── Cargo.toml
    ├── gen/
    ├── icons/
    ├── src
    │   ├── lib.rs
    │   └── main.rs
    └── tauri.conf.json

src目录下是前端代码, src-tauri则是放了后端代码

为Windows编译二进制文件

cargo xwin build --release --target x86_64-pc-windows-msvc

tauri的逆向

我大致分为2部分, 一部分是前端逆向, 关键在于提取前端的资源文件, tauri会用brotli将前端资源全部压缩, debug时压缩等级为2, release为9, 这些可以在tauri对资源打包的源代码找到, 另外就是后端部分的逆向

前端部分

目前有一个项目 tauri-dumper 可以把 windowsmacos 平台tauri应用的前端资源直接dump出来, 对于其他平台还需要手动dump, 可以参考此文章

后端部分

当前端的某个函数长这样的时候

async function greet() {
  greetMsgEl.textContent = await invoke("some_command", { name: greetInputEl.value });
}

就说明前端调用了使用rust编写的后端函数

mod cry;

#[tauri::command]
fn some_command(name: &str) -> String {
    let data = name.as_bytes().to_vec();
    let cipher = cry::crypt(&data, &"ColorSkyFun");
    if cipher
        == vec![
            78, 240, 77, 233, 128, 40, 110, 146, 222, 242, 138, 7, 84, 125, 236, 21, 87, 111, 144,
            24, 55, 192, 93, 137, 123, 220, 76, 5,
        ]
    {
        format!("Yes~")
    } else {
        format!("No!")
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![some_command])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

generate_handler!宏会将#[tauri::command]派生的代码进行再次编程, 展开后得到这样的代码

tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(move |__tauri_invoke__| {
    let __tauri_cmd__ = __tauri_invoke__.message.command();
    match __tauri_cmd__ {
        "some_command" => {
            ({
                move || {
                    #[allow(unused_imports)]
                    use ::tauri::ipc::private::*;
                    #[allow(unused_variables)]
                    let ::tauri::ipc::Invoke {
                        message: __tauri_message__,
                        resolver: __tauri_resolver__,
                        acl: __tauri_acl__,
                    } = __tauri_invoke__;
                    let result = some_command(
                        match ::tauri::ipc::CommandArg::from_command(::tauri::ipc::CommandItem {
                            plugin: ::core::option::Option::None,
                            name: "some_command",
                            key: "name",
                            message: &__tauri_message__,
                            acl: &__tauri_acl__,
                        }) {
                            Ok(arg) => arg,
                            Err(err) => {
                                __tauri_resolver__.invoke_error(err);
                                return true;
                            }
                        },
                    );
                    let kind = (&result).blocking_kind();
                    kind.block(result, __tauri_resolver__);
                    return true;
                }
            })()
        }
        _ => {
            return false;
        }
    }
})

下面这段代码最为重要

match ::tauri::ipc::CommandArg::from_command(
    ::tauri::ipc::CommandItem {
        plugin: ::core::option::Option::None,
        name: "some_command",
        key: "name",
        message: &__tauri_message__,
        acl: &__tauri_acl__,
    }) {
    Ok(arg) => arg,
    Err(err) => {
        __tauri_resolver__.invoke_error(err);
        return true;
    }
},

在编译为二进制后, 可以在ida里搜索some_command从而定位这段代码的位置

*(_QWORD *)&v20[12] = 0;
*(_QWORD *)v20 = aSomeCommand; // some_command
*(_QWORD *)&v20[2] = 12;
*(_QWORD *)&v20[4] = aName_0; // name
*(_QWORD *)&v20[6] = 4;
*(_QWORD *)&v20[8] = v18;
*(_QWORD *)&v20[10] = &v30;
v33 = 1;
sub_7FF63314F8B0(Src, (__int64 *)v20);

接下来关注v20这个变量, 这样就可以定位到some_command这个函数具体的实现.

sub_7FF63300D380(
      (unsigned int)v20,
      (unsigned int)p_Size,
      (unsigned int)&off_7FF63351B280,
      (unsigned int)&off_7FF63351B2B0,
      (__int64)&qword_7FF63351B2C0);            // "ColorSkyFun"
nullsub_1();
v10 = sub_7FF632F41080(28, 1);
if ( !v10 )
    sub_7FF6334FF7F1(1, 28);
*(__m128i *)v10 = _mm_load_si128((const __m128i *)&xmmword_7FF63351B030);
*(_QWORD *)(v10 + 16) = 0x895DC03718906F57uLL;
*(_DWORD *)(v10 + 24) = 88923259;
if ( *(_QWORD *)&v20[4] == 28
    && _mm_movemask_epi8(
        _mm_and_si128(
        _mm_cmpeq_epi8(
        _mm_loadu_si128((const __m128i *)(v10 + 12)),
        _mm_loadu_si128((const __m128i *)(*(_QWORD *)&v20[2] + 12LL))),
        _mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)v10), _mm_loadu_si128(*(const __m128i **)&v20[2])))) == 0xFFFF )
{
    // skip
}

sub_7FF63300D380就是crypt函数, v10就是密文

Table of Contents