DEV Community

Cover image for Fake C++ Compiler with Node.js - Part 1
Gurigraphics
Gurigraphics

Posted on • Edited on

Fake C++ Compiler with Node.js - Part 1

Why fake?

Let's use the system Embedding Files in an Executable to turn Node.js into a Fake C++ Compiler.

Fake because it will change the content of resources directly from binary or hexadecimal.

What's the use of that?

For a server to compile a C++ program we need to install several gigabytes of libraries, a dedicated Linux or Windows server, a reasonable amount of RAM and CPU to do the processing.

To just modify an executable we will only need a server node.js.
We can even do this with javascript on the client-side frontend.

Part 1 - C++

1.1) Create main.cpp

#include <iostream>
#include "resources.h"

int main() {
    std::cout << GetResource() << std::endl;
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

1.2) Create resources.h

#include <windows.h>

#define IDI_MYICON  101
#define TEXTFILE   256
#define IDR_DATA1  255

void LoadFileInResource(int name, int type, DWORD& size, const wchar_t*& data){
    HMODULE handle = ::GetModuleHandleW(NULL);
    HRSRC rc = ::FindResourceW(handle, MAKEINTRESOURCEW(name), MAKEINTRESOURCEW(type));
    HGLOBAL rcData = ::LoadResource(handle, rc);
    size = ::SizeofResource(handle, rc);
    data = static_cast<const wchar_t*>(::LockResource(rcData));
}

std::string GetResource(){
    DWORD size = 0;
    const char* data = NULL;
    LoadFileInResource(IDR_DATA1, TEXTFILE, size, reinterpret_cast<const wchar_t*&>(data));  
    std::string content(data, size);
    return content;
}

Enter fullscreen mode Exit fullscreen mode

1.3) Create resources.rc

#include <windows.h>
#include "resources.h"

IDI_MYICON ICON "favicon.ico"
IDR_DATA1 TEXTFILE "data.txt"

VS_VERSION_INFO     VERSIONINFO
FILEVERSION       1,0,0,1
PRODUCTVERSION    1,0,0,1
FILEFLAGSMASK 0x3fL

#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x2L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "CompanyName", "CompanyName"
            VALUE "LegalCopyright", "(c) CompanyName. All Rights reserved."
            VALUE "ProductName", "ProductName"
            VALUE "ProductVersion", "1,0,0,1"
            VALUE "FileDescription", "ProductName"
            VALUE "InternalName", "ProductName"
            VALUE "OriginalFilename", "main.exe"
            VALUE "ShellIntegrationVersion", "2"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0416, 1200
    END
END
Enter fullscreen mode Exit fullscreen mode

1.4) Download 1 favicon.ico

https://favicon.io/emoji-favicons/

1.5) Create data.txt

<a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5>
Enter fullscreen mode Exit fullscreen mode

1.6) Generate resources.res

windres.exe -i "resources.rc" --input-format=rc -o "resources.res" -O coff
Enter fullscreen mode Exit fullscreen mode

1.7) Compile

g++ main.cpp -o main.exe -I./ -static-libgcc -static-libstdc++ resources.res 
Enter fullscreen mode Exit fullscreen mode

1.8) Test

main
Enter fullscreen mode Exit fullscreen mode

Console Output

<a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5>
Enter fullscreen mode Exit fullscreen mode

1.9) Search hex

Open main.exe in an editor hexadecimal or run

certutil -encodehex main.exe hex.txt
Enter fullscreen mode Exit fullscreen mode

1.10) Get address

Search by

<a1
Enter fullscreen mode Exit fullscreen mode

The position of "<" I found in the column 9 of line 000a8db0
This is the beginning of the resources region

0xa8db8
Enter fullscreen mode Exit fullscreen mode

Bytes positions

Search by

5>
Enter fullscreen mode Exit fullscreen mode

The position of ">" I found in the column 16 of line 000a8dc8
This is the end of the resources region

0xa8dd7
Enter fullscreen mode Exit fullscreen mode

For now just save these addresses

Part 2 - Node.js

1. Node start

mkdir node_cpp
cd node_cpp
mkdir uploads
npm init -y
Enter fullscreen mode Exit fullscreen mode

2. Copy "main.exe" to folder "node_cpp\uploads"

copy ..\main.exe .\uploads
Enter fullscreen mode Exit fullscreen mode

3. Install

npm i fs
npm i path
Enter fullscreen mode Exit fullscreen mode

3. Create path_join.js

This file will be used to resolve the directories path

const path = require('path')

const path_join = function( file, folder ){
  if( folder ) return path.join(__dirname, `/${folder}/${ file }`)
  return path.join(__dirname, `/${ file }`)
}

module.exports = path_join; 
Enter fullscreen mode Exit fullscreen mode

4. Create slicer.js

This file will be used to cut the executable into 3 parts: header, middle, footer

Middle is the part that we want to modify and update using just node.js.

const fs = require('fs')

const slicer = function( exe_path, result_path, entry_start, entry_end ){

  var file_data;

  // Set positions
  const file_start = parseInt( entry_start, 16 );
  const file_end = parseInt( entry_end, 16 );

  // Get exe
  const input_data = fs.readFileSync( exe_path );

  // Slice
  if( entry_end == "end" ){
    file_data = input_data.slice( file_start )
  }else{
    file_data = input_data.slice( file_start, file_end )
  }  

  // Write
  fs.writeFileSync( result_path, file_data );

  console.log('Binary file sucessfull created!');
} 

module.exports = slicer; 
Enter fullscreen mode Exit fullscreen mode

5. Create data.txt in "node_cpp/uploads"

This will be the content we will replace
Attention: this content cannot be greater than the previous content

1234567890
Enter fullscreen mode Exit fullscreen mode

6. Create replaceBinaryText.js

This file will be used to take the contents of middle.exe and generate a new_middle.exe with the new contents. If the new content is smaller, it will fill with 0x0000 until it reaches the original size.

const fs = require('fs')

const replaceBinaryText = function( data_path, input_path, output_path ){

  // Get bin
  const exe = fs.readFileSync( input_path );
  const min_size = exe.length;

  // Get text
  const input_data = fs.readFileSync( data_path );
  const data_size = input_data.length;

  if( data_size > min_size ){

    console.log(`File Error. Maximum size is ${min_size}`);

  }else{

    // Fill
    const padding_buffer = Buffer.alloc(min_size - data_size, 0x00);

    // Concat
    const output_buffer = Buffer.concat([input_data, padding_buffer]);

    // Write
    fs.writeFileSync(output_path, output_buffer, 'binary');

    console.log('Binary file sucessfull created!');
  }
}

module.exports = replaceBinaryText; 
Enter fullscreen mode Exit fullscreen mode

7. Create merge3binary.js

This file will be used to join the 3 binaries: header.exe, middle.exe and footer.exe to create the final.exe.

const fs = require('fs')

const merge3binary = function( header, middle, footer, output_path ){

  const header_data = fs.readFileSync( header );
  const middle_data = fs.readFileSync( middle );
  const footer_data = fs.readFileSync( footer );

  const merged_buffer = Buffer.concat([header_data, middle_data, footer_data]);

  fs.writeFileSync(output_path, merged_buffer, 'binary');

  console.log('Binary file sucessfull created!');
}

module.exports = merge3binary; 
Enter fullscreen mode Exit fullscreen mode

8. Create main.js

Finally let's put it all together. Now let's use those addresses 0xa8db8 and 0xa8dd7 + 1

var slicer = require('./slicer')
var path_join = require('./path_join')
var merge3binary = require('./merge3binary')
var replaceBinaryText = require('./replaceBinaryText')

const main = function(){

  var folder = "uploads"

  var input_path = path_join( "main.exe", folder )
  var output_path = path_join( "final.exe", folder )

  var header_path = path_join( "main_header.exe", folder )
  var middle_path = path_join( "main_middle.exe", folder )  
  var footer_path = path_join( "main_footer.exe", folder )

  var data_path = path_join( "data.txt", folder )
  var new_path = path_join( "main_middle_updated.exe", folder )

  var adress_start = "0xa8db8"
  var adress_end = "0xa8dd8" // 0xa8dd7 + 1

  // -------- Generate 3 slices
  slicer( input_path, header_path, "0x00000", adress_start)
  slicer( input_path, middle_path, adress_start, adress_end)
  slicer( input_path, footer_path, adress_end, "end")


  // -------- Update resources region
  replaceBinaryText( data_path, middle_path, new_path )


  // Merge 3 binary
  merge3binary(header_path, new_path, footer_path, output_path)
}

main()
Enter fullscreen mode Exit fullscreen mode

9. Compile

node main.js
Enter fullscreen mode Exit fullscreen mode

10. Run

final
Enter fullscreen mode Exit fullscreen mode

Console Output

1234567890
Enter fullscreen mode Exit fullscreen mode

Now whenever you want to modify it, you just need to modify the content of data.txt, and run node main.js and finish.

Fake C++ Compiler with Node.js - Part 2

https://dev.to/gurigraphics/fake-c-compiler-with-nodejs-server-part-2-eag

Top comments (0)