Actual combat of wasm to c call

This article introduces two cases. In this article, the content mentioned in the previous article will be skipped, mainly focusing on new content and knowledge, so it is recommended to read it first

1. A German map vector tile reverse (fast wasm reverse), execute the C code I translated by wasm2c

2. Execute the C code 2 translated by wasm2c

3. Case of wasm to c calling and encapsulation to dll

4.XXX video ckey9 Generation analysis and implementation of 1

Case 1: ape anthropology 2022 Spring Festival topic

Sample address: https://match.yuanrenxue.com/match/20

New knowledge:

  1. Import function environment detection processing
  2. Secondary pointer value
  3. Compile to command line mode call

Open the URL and grab the package with f12


See that there is a sign in the parameter. Through the call stack, it is easy to find that it is the result of calling the sign function


The break point is followed by the input. It is found that it is the export function calling wasm


Search wasm download and convert it to c file


In the header file, you can see that there are many import functions. Here you need to implement the logic of the functions used. The most rough way is to fill in the breakpoints under all import functions, which run to which one

For example, the import function is called here__ wbindgen_is_undefined. According to js logic, it can be directly changed to

u32 wbindgen_is_undefined(u32 p0i32){
    return 0;
}

For others, by analogy, you can directly assign NULL to those that have not been executed. The complete code is as follows

u32 wbg_self_e23d74ae45fb17d1(void){
    return 36;
}

u32 wbindgen_object_clone_ref(u32 p0i32){
    return p0i32 + 1;
};

u32 wbindgen_is_undefined(u32 p0i32){
    return 0;
}

u32 wbg_instanceof_Window_434ce1849eb4e0fc(u32 p0i32){
    return 1;
}

u32 wbg_document_5edd43643d1060d9(u32 p0i32){
    return p0i32 + 1;
};

u32 wbg_body_7538539844356c1c(u32 p0i32){
    return p0i32 + 1;
};

void wbindgen_object_drop_ref(u32 p0i32){

}


u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_instanceof_Window_434ce1849eb4e0fcZ_ii)(u32) = wbg_instanceof_Window_434ce1849eb4e0fc;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_document_5edd43643d1060d9Z_ii)(u32) = wbg_document_5edd43643d1060d9;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_body_7538539844356c1cZ_ii)(u32) = wbg_body_7538539844356c1c;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_newnoargs_f579424187aa1717Z_iii)(u32, u32) = NULL;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_call_89558c3e96703ca1Z_iii)(u32, u32) = NULL;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_globalThis_d61b1f48a57191aeZ_iv)(void) = NULL;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_self_e23d74ae45fb17d1Z_iv)(void) = wbg_self_e23d74ae45fb17d1;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_window_b4be7f48b24ac56eZ_iv)(void) = NULL;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbg_global_e7669da72fd7f239Z_iv)(void) = NULL;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbindgen_is_undefinedZ_ii)(u32) = wbindgen_is_undefined;
u32 (*Z_Z2EZ2Findex_bgZ2EjsZ___wbindgen_object_clone_refZ_ii)(u32) = wbindgen_object_clone_ref;
void (*Z_Z2EZ2Findex_bgZ2EjsZ___wbindgen_object_drop_refZ_vi)(u32) = wbindgen_object_drop_ref;
void (*Z_Z2EZ2Findex_bgZ2EjsZ___wbindgen_throwZ_vii)(u32, u32) = NULL;

This completes all the import functions, and the next step is to write your own export function


Here, after the function call, you get a secondary pointer. To get the final string, you need to read the pointer first, then read the string, and enter the complete code

#include <stdio.h>
#include <stdlib.h>
#include "match20.c"

extern void init_wasm(void);
extern char* get_sign(char*);

void init_wasm(){
    init_func_types();
    init_globals();
    init_memory();
    init_table();
    init_exports();
}

char* get_sign(char* content){
    u32 retptr = w2c___wbindgen_add_to_stack_pointer(-16);
    int content_len = (int)strlen(content);
    u32 content_ptr = w2c___wbindgen_malloc( content_len + 1);
    memcpy(w2c_memory.data + content_ptr, content, content_len + 1);
    w2c_sign(retptr, content_ptr, content_len);
    int out_ptr = 0;
    out_ptr += (w2c_memory.data + retptr)[0];
    out_ptr += (w2c_memory.data + retptr)[1] << 8;
    out_ptr += (w2c_memory.data + retptr)[2] << 16;
    out_ptr += (w2c_memory.data + retptr)[3] << 24;

    char* out_str = (char *)malloc(33);
    out_str[32] = 0;
    memcpy(out_str, w2c_memory.data + out_ptr, 32);
    return out_str;
}

int main(int argc,char *argv[]) {
    return 0;
}

Then compile it into dll for calling

"D:/MinGW64/bin/gcc" -shared -Os -s -o match20.dll main.c wasm-rt-impl.c

Then try calling in python

It's a very strange error. There is this file in the directory, but it shows that the module cannot be found. If someone knows why, please reply to me in the comment area. Thank you first.

Since dll cannot be called and exe can always be called, you can also pass parameters through the command line and call exe to obtain the results. Two arguments are provided in the main function to accept command line arguments. argc and argv, one is the length of the command line parameter and the other is the command line parameter list. The complete code is as follows

int main(int argc,char *argv[]) {
    init_wasm();
    char* content = argv[1];

    u32 retptr = w2c___wbindgen_add_to_stack_pointer(-16);
    int content_len = (int)strlen(content);
    u32 content_ptr = w2c___wbindgen_malloc( content_len + 1);
    memcpy(w2c_memory.data + content_ptr, content, content_len + 1);
    w2c_sign(retptr, content_ptr, content_len);
    int out_ptr = 0;
    out_ptr += (w2c_memory.data + retptr)[0];
    out_ptr += (w2c_memory.data + retptr)[1] << 8;
    out_ptr += (w2c_memory.data + retptr)[2] << 16;
    out_ptr += (w2c_memory.data + retptr)[3] << 24;

    char* out_str = (char *)malloc(33);
    out_str[32] = 0;
    memcpy(out_str, w2c_memory.data + out_ptr, 32);
    printf("%s\n", out_str);

    free(out_str);

    return 0;
}

This compilation is for exe

"D:/MinGW64/bin/gcc" -o match20 main.c wasm-rt-impl.c

Put the generated exe in the same directory as the py file


Perfect results

import requests
import time
import os
from urllib import parse

def main():
    sums = 0
    headers = {
        'cookie': '',
        'user-agent': 'yuanrenxue.project',
        'x-requested-with': 'XMLHttpRequest'
    }
    for page in range(1, 6):
        data = {
            'page': str(page),
            't': str(int(time.time())) + '000'
        }
        nodejs = os.popen('match20 "' + data['page'] + '|' + data['t'] + '"')
        data['sign'] = nodejs.read().replace('\n', '')
        nodejs.close()
        print(data)
        url = 'https://match.yuanrenxue.com/api/match/20?' + parse.urlencode(data)
        response = requests.get(url, headers=headers).json()
        print(response)

        for each in response['data']:
            sums += each['value']
    print(sums)
    # Total: 253014


if __name__ == '__main__':
    main()

Case 2: acquisition of a video ckey parameter

Sample address: https://match.yuanrenxue.com/match/20

New knowledge:

  1. Import numeric processing
  2. Detection and processing of import function environment with closure

Most of the js analysis process has been described in detail in the fourth part of the pre reading, so I won't say much. Here we go directly to the content of wasm

After creating the project, according to the code in the article, the import function includes getTotalMemory and_ get_unicode_str and others can be given NULL directly. How to fill in the details will be described later.

Then import memory and import table. The third part of the pre reading has been introduced in detail, so I'll skip it here

Finally, import the value. The imported value is violent. First, set it to NULL


Then modify it to the imported value in all references



In this way, the imported values are processed. Finally, there are the two import functions left above.

getTotalMemory is relatively simple. You can see in js that what is returned is a fixed value

u32 envZ_getTotalMemoryZ_iv(void){
  return 16777216;
}

_ get_unicode_str is troublesome. Its js function is as follows

function P() {
    function a(a) {
        return a ? a.length > 48 ? a.substr(0, 48) : a : ""
    }
    function b() {
        var b = document.URL
          , c = window.navigator.userAgent.toLowerCase()
          , d = "";
        document.referrer.length > 0 && (d = document.referrer);
        try {
            0 == d.length && opener.location.href.length > 0 && (d = opener.location.href)
        } catch (e) {}
        var f = window.navigator.appCodeName
          , g = window.navigator.appName
          , h = window.navigator.platform;
        return b = a(b),
        d = a(d),
        c = a(c),
        b + "|" + c + "|" + d + "|" + f + "|" + g + "|" + h
    }
    var c = b()
      , d = p(c) + 1
      , e = Pb(d);
    return o(c, e, d + 1),
    e
}

You can see that it obtains the document through the closure URL and window Navigator and so on. Then try to write the value that can be written to death here, document The URL can only be passed in, so how to solve it?

Then you can define a variable outside the function, and then assign a value to the variable before calling. The code is as follows

char *url;

void set_url(char *url_str){
    url = url_str;
}

u32 envZ__get_unicode_strZ_iv(void){
  int c_len = (int)strlen(url);
  u32 e = w2c__malloc(c_len + 75);
  memcpy(w2c_memory.data + e, url, c_len);
  memcpy(w2c_memory.data + e + c_len, "|mozilla/5.0 (windows nt 10.0; wow64) applewebkit||Mozilla|Netscape|Win32", 73);
  return e;
}

In this way, the parameters of the closure can be processed by the curve. The complete import function is processed as follows

static wasm_rt_memory_t w2c_memory;
static wasm_rt_table_t w2c___indirect_function_table;
char *url;

void set_url(char *url_str){
    url = url_str;
}

u32 envZ__get_unicode_strZ_iv(void){
  int c_len = (int)strlen(url);
  u32 e = w2c__malloc(c_len + 75);
  memcpy(w2c_memory.data + e, url, c_len);
  memcpy(w2c_memory.data + e + c_len, "|mozilla/5.0 (windows nt 10.0; wow64) applewebkit||Mozilla|Netscape|Win32", 73);
  return e;
}

u32 envZ_getTotalMemoryZ_iv(void){
return 16777216;
}

wasm_rt_memory_t (*Z_envZ_memory) = &w2c_memory;
wasm_rt_table_t (*Z_envZ_table) = &w2c___indirect_function_table;
u32 (*Z_envZ_enlargeMemoryZ_iv)(void) = NULL;
u32 (*Z_envZ_getTotalMemoryZ_iv)(void) = envZ_getTotalMemoryZ_iv;
u32 (*Z_envZ_abortOnCannotGrowMemoryZ_iv)(void) = NULL;
void (*Z_envZ_abortStackOverflowZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_iiZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_iiiiZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_vZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_viZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_viiiiZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_viiiiiZ_vi)(u32) = NULL;
void (*Z_envZ_nullFunc_viiiiiiZ_vi)(u32) = NULL;
void (*Z_envZ____lockZ_vi)(u32) = NULL;
void (*Z_envZ____setErrNoZ_vi)(u32) = NULL;
u32 (*Z_envZ____syscall140Z_iii)(u32, u32) = NULL;
u32 (*Z_envZ____syscall146Z_iii)(u32, u32) = NULL;
u32 (*Z_envZ____syscall54Z_iii)(u32, u32) = NULL;
u32 (*Z_envZ____syscall6Z_iii)(u32, u32) = NULL;
void (*Z_envZ____unlockZ_vi)(u32) = NULL;
void (*Z_envZ__abortZ_vv)(void) = NULL;
u32 (*Z_envZ__emscripten_memcpy_bigZ_iiii)(u32, u32, u32) = NULL;
u32 (*Z_envZ__get_unicode_strZ_iv)(void) = envZ__get_unicode_strZ_iv;
u32 (*Z_envZ_memoryBaseZ_i) = NULL;
u32 (*Z_envZ_tableBaseZ_i) = NULL;
u32 (*Z_envZ_DYNAMICTOP_PTRZ_i) = NULL;
u32 (*Z_envZ_tempDoublePtrZ_i) = NULL;
u32 (*Z_envZ_STACKTOPZ_i) = NULL;
u32 (*Z_envZ_STACK_MAXZ_i) = NULL;
f64 (*Z_globalZ_NaNZ_d) = NULL;
f64 (*Z_globalZ_InfinityZ_d) = NULL;

Then you can write your own export function. At this time, there is no difficulty, as mentioned earlier

#include <stdio.h>
#include <stdlib.h>
#include "txckey91.c"

extern void init_wasm(void);
extern char* get_ckey(int, char*, char*, char*, char*, char*, int);

void init_wasm(){
    init_func_types();
    init_globals();
    init_memory();
    init_table();
    init_exports();
}

char* get_ckey(int platform, char* url_str, char* appVer, char* vid, char* empty_str, char* guid, int tm){
    set_url(url_str);
    int appVer_len = (int)strlen(appVer);
    u32 appVer_ptr = w2c__malloc( appVer_len + 1);
    memcpy(w2c_memory.data + appVer_ptr, appVer, appVer_len + 1);
    int vid_len = (int)strlen(vid);
    u32 vid_ptr = w2c__malloc( vid_len + 1);
    memcpy(w2c_memory.data + vid_ptr, vid, vid_len + 1);
    int empty_str_len = (int)strlen(empty_str);
    u32 empty_str_ptr = w2c__malloc( empty_str_len + 1);
    memcpy(w2c_memory.data + empty_str_ptr, empty_str, empty_str_len + 1);
    int guid_len = (int)strlen(guid);
    u32 guid_ptr = w2c__malloc( guid_len + 1);
    memcpy(w2c_memory.data + guid_ptr, guid, guid_len + 1);
    u32 out_ptr = w2c__getckey(platform, appVer_ptr, vid_ptr, empty_str_ptr, guid_ptr, tm);
    char* out_str = (char *)malloc(512);
    memcpy(out_str, w2c_memory.data + out_ptr, 512);

    w2c__free(appVer_ptr);
    w2c__free(vid_ptr);
    w2c__free(empty_str_ptr);
    w2c__free(guid_ptr);

    return out_str;
}

int main(int argc,char *argv[]) {
    return 0;
}

Compile file as dll

"D:/MinGW64/bin/gcc" -shared -Os -s -o txckey91.dll main.c wasm-rt-impl.c

Try calling in python

The operation is normal, and the results are completely consistent with the browser results. end

import ctypes

def main():
    dll = ctypes.windll.LoadLibrary('txckey91.dll')
    dll.init_wasm()
    dll.get_ckey.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
    dll.get_ckey.restype = ctypes.c_char_p
    ckey = dll.get_ckey(ctypes.c_int(10201), ctypes.c_char_p(b"https://v.qq.com/x/cover/mzc00200mp8vo9b/x0041qq"),
                        ctypes.c_char_p(b"3.5.57"), ctypes.c_char_p(b"x0041qqe42w"), ctypes.c_char_p(b""),
                        ctypes.c_char_p(b"f13cfbab245307b814a9dad672908bc7"), ctypes.c_int(1643337028))
    print(ckey.decode())


if __name__ == '__main__':
    main()

Keywords: Python C Back-end wasm

Added by churdaddy on Fri, 28 Jan 2022 18:50:51 +0200