lua 代码加密方案

require 实现

  • require函数在实现上是依次调用package.searchers(lua51中是package.loaders)中的载入函数,成功后返回。在loadlib.c文件里有四个载入函数的实现,分别为searcher_preload, searcher_Lua, searcher_C, searcher_Croot。
  • searcher_preload是从读取LUA_REGISTRYINDEX的_PRELOAD字段。已经require过的文件会写入到该表中
  • searcher_Lua是依据文件名称查找package.path中的全部路径的lua文件。存在文件则返回
  • searcher_C是搜索package.cpath中的全部路径下的库文件
  • searcher_Croot是对require(“a.b.c”)的情况,读取a库。然后查找函数名为lua_a_b_c的lua_CFunction函数
static void findloader (lua_State *L, const char *name) {
  int i;
  luaL_Buffer msg;  /* to build error message */
  luaL_buffinit(L, &msg);
  lua_getfield(L, lua_upvalueindex(1), "searchers");  /* will be at index 3 */
  if (!lua_istable(L, 3))
    luaL_error(L, LUA_QL("package.searchers") " must be a table");
  /*  iterate over available searchers to find a loader */
  for (i = 1; ; i++) {
    lua_rawgeti(L, 3, i);  /* get a searcher */
    if (lua_isnil(L, -1)) {  /* no more searchers? */
      lua_pop(L, 1);  /* remove nil */
      luaL_pushresult(&msg);  /* create error message */
      luaL_error(L, "module " LUA_QS " not found:%s",
                    name, lua_tostring(L, -1));
    }
    lua_pushstring(L, name);
    lua_call(L, 1, 2);  /* call it */
    if (lua_isfunction(L, -2))  /* did it find a loader? */
      return;  /* module loader found */
    else if (lua_isstring(L, -2)) {  /* searcher returned error message? */
      lua_pop(L, 1);  /* remove extra return */
      luaL_addvalue(&msg);  /* concatenate error message */
    }
    else
      lua_pop(L, 2);  /* remove both returns */
  }
}
  • 如今要实如今require时能读取加密文件,有两种办法,一种是直接改动源码。即改动第二个载入函数,又一次实现当中读取文件内容的函数,另外一种办法是在lua中改动package.searchers表。在载入器的第一和另外一种之间加入一个载入器函数,该载入器模拟searcher_Lua函数。搜索path路径,然后逐个匹配文件。然后读取文件内容,解密。然后调用load载入并返回(c中为luaL_loadbufferx),这里在载入时最好传入文件名称作为来源參数。方便在调试信息中定位.
  • 加密方案可使用相似xxtea轻量级的加密算法
  • 在对lua文件进行加密打包时。能够在文件头写入指定的签名内容。以方便在解密前预先推断是否为有效的加密文件

改动lua源码方案

  • 在searcher_Lua中终于是调用lua_load(L, getF, &lf, lua_tostring(L, -1), mode)载入源文件,该函数的第二个參数getF是一个lua_Reader函数,所以这里能够重写该函数以实现解密,也能够向外部暴露一个接口用来将自己定义的文件读取函数作为參数传给lua_load。以下是原版的getF实现
static const char *getF (lua_State *L, void *ud, size_t *size) {
  LoadF *lf = (LoadF *)ud;
  (void)L;  /* not used */
  if (lf->n > 0) {  /* are there pre-read characters to be read? */
    *size = lf->n;  /* return them (chars already in buffer) */
    lf->n = 0;  /* no more pre-read characters */
  }
  else {  /* read a block from file */
    /* 'fread' can return > 0 *and* set the EOF flag. If next call to
       'getF' called 'fread', it might still wait for user input.
       The next check avoids this problem. */
    if (feof(lf->f)) return NULL;
    *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f);  /* read block */
  }
  return lf->buff;
}

外部改动载入器方案

  • 直接改动package.searchers表,向当中加入载入器,c版实现例如以下
void addLuaSearcher(lua_CFunction func)
{
    if (!func) return;

    // stack content after the invoking of the function
    // get loader table
    lua_getglobal(m_state, "package");          /* L: package */
    lua_getfield(m_state, -1, "loaders");       /* L: package, loaders */
    // insert loader into index 2
    lua_pushcfunction(m_state, func);           /* L: package, loaders, func */
    for (int i = lua_objlen(m_state, -2) + 1; i > 2; --i)
    {
        lua_rawgeti(m_state, -2, i - 1);        /* L: package, loaders, func, function */
        // we call lua_rawgeti, so the loader table now is at -3
        lua_rawseti(m_state, -3, i);            /* L: package, loaders, func */
    }
    lua_rawseti(m_state, -2, 2);                /* L: package, loaders */
    // set loaders into package
    lua_setfield(m_state, -2, "loaders");       /* L: package */
    lua_pop(m_state, 1);
}
  • 载入器函数实现依据传入的文件名称。逐个匹配的package.path中的内容,存在文件后,然后读取文件内容,解密,最后再将解出的内容调用load载入并返回(c中为luaL_loadbufferx),实现能够參照lua源码中的searcher_Lua实现