Git Product home page Git Product logo

b2r2's Introduction

alt text

Build status Build Status

B2R2

B2R2 is a collection of useful algorithms, functions, and tools for binary analysis, written purely in F# (in .NET lingo, it is purely managed code). B2R2 has been named after R2-D2, a famous fictional robot appeared in the Star Wars. In fact, B2R2's original name was B2-R2, but we decided to use the name B2R2 instead, because .NET does not allow dash (-) characters in identifiers (or namespaces). The name essentially represents "binary" or "two": "binary" itself means "two" states anyways. "B" and "2" mean "binary", and "R" indicates reversing.

B2R2?

  1. B2R2 is analysis-friendly: it is written in F#, which provides all the syntactic goodies for writing program analyzers, such as pattern matching, algebraic data types, and etc.

  2. B2R2 is fast: it has a fast and efficient front-end engine for binary analysis, which is written in a functional-first way. Therefore, it naturally supports pure parallelism for binary disassembling, lifting and IR optimization.

  3. B2R2 is easy to play with: there is absolutely no dependency hell for B2R2 because it is a fully-managed library. All you need to do is to install .NET SDK, and you are ready to go! Native IntelliSense support is another plus!

  4. B2R2 is OS-Independent: it works on Linux, Mac, Windows, and etc. as long as .NET core supports it.

  5. B2R2 is interoperable: it is not bound to a specific language. Theoretically, you can use B2R2 APIs with any CLI supported languages.

Features?

B2R2 supports instruction parsing, binary disassembly, assembly, control-flow recovery, and many more. B2R2 also comes with several user-level command-line tools that are similar to readelf and objdump, although our tools are platform-agnostic. B2R2 currently supports four binary file formats: ELF, PE, Mach-O, and WebAssembly.

Below is a list of features that we currently support. Some of them are work in progress, but we look forward to your contributions! Feel free to write a PR (Pull Request) while making sure that you have read our contribution guideline.

Feature x86 x86-64 ARMv7 ARMv8 MIPS32 MIPS64 EVM TMS320C600 AVR PPC SPARC SH4 RISC-V
Instruction Parsing πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ” πŸŒ•
Disassembly πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ” πŸŒ•
Lifting πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ‘ πŸŒ• πŸŒ‘ πŸŒ• πŸŒ‘ πŸŒ•
CFG Recovery πŸŒ• πŸŒ• πŸŒ“ πŸŒ“ πŸŒ“ πŸŒ“ πŸŒ• πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘
Data-Flow πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘
Instruction Emulation πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ• πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘
Assembly πŸŒ• πŸŒ• πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘
REPL πŸŒ• πŸŒ• πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘
ROP Compilation πŸŒ• πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘ πŸŒ‘

Dependencies?

B2R2 relies on a tiny set of external .NET libraries, and our design principle is to use a minimum number of libraries. Below is a list of libraries that we leverage.

API Documentation

We currently use fsdocs to generate our documentation: https://b2r2.org/APIDoc/.

Example

Let's try to use B2R2 APIs.

  1. First we create an empty directory DIRNAME:

    mkdir DIRNAME
    cd DIRNAME
    
  2. We then create an empty console project with dotnet command line:

    $ dotnet new console -lang F#
    
  3. Add our nuget package B2R2.FrontEnd to the project:

    $ dotnet add package B2R2.FrontEnd.BinInterface
    
  4. Modify the Program.fs file with your favorite editor as follows:

    open B2R2
    open B2R2.FrontEnd.BinInterface
    
    [<EntryPoint>]
    let main argv =
      let isa = ISA.OfString "amd64"
      let bytes = [| 0x65uy; 0xffuy; 0x15uy; 0x10uy; 0x00uy; 0x00uy; 0x00uy |]
      let hdl = BinHandle.Init (isa, bytes)
      let ins = BinHandle.ParseInstr (hdl, 0UL)
      ins.Translate hdl.TranslationContext |> printfn "%A"
      0
  5. We then just run it by typing: dotnet run. You will be able see lifted IR statements from your console. That's it! You just lifted an Intel instruction with only few lines of F# code!

Build

Building B2R2 is fun and easy. All you need to do is to install .NET 8 SDK or above. Yea, that's it!

  • To build B2R2 in release mode, type make release or dotnet build -c Release in the source root.

  • To build B2R2 in debug mode, type make, or dotnet build in the source root.

For your information, please visit the official web site of F# to get more tips about installing the development environment for F#: http://fsharp.org/.

Credits

Members in SoftSec Lab. @ KAIST developed B2R2 in collaboration with Cyber Security Research Center (CSRC) at KAIST. See Authors for the full list.

Citation

If you plan to use B2R2 in your own research. Please consider citing our paper:

@INPROCEEDINGS{jung:bar:2019,
  author = {Minkyu Jung and Soomin Kim and HyungSeok Han and Jaeseung Choi and Sang Kil Cha},
  title = {{B2R2}: Building an Efficient Front-End for Binary Analysis},
  booktitle = {Proceedings of the NDSS Workshop on Binary Analysis Research},
  year = 2019
}

Publications

Here are papers using our work. Please create a PR if you want to add yours.

  • FunProbe: Probing Functions from Binary Code through Probabilistic Analysis, FSE 2023
  • How'd Security Benefit Reverse Engineers? The Implication of Intel CET on Function Identification, DSN 2022 (PDF)
  • Smartian: Enhancing Smart Contract Fuzzing with Static and Dynamic Data-Flow Analyses, ASE 2021 (PDF)
  • NTFuzz: Enabling Type-Aware Kernel Fuzzing on Windows with Static Binary Analysis, Oakland 2021 (PDF)

b2r2's People

Contributors

0xdkay avatar 18jminkim avatar a-mehdi avatar abbasly avatar aliahmed36 avatar cheonhoojeon avatar cyclon2 avatar daramg avatar dohki avatar enkomio avatar hestati63 avatar jchoi2022 avatar jidoc01 avatar khsdo95 avatar kimdora avatar ksalpha avatar luxroot avatar ly-xthunder avatar mickowale avatar sangkilc avatar sijung07 avatar silee-k avatar soomin-kim avatar sugeunji avatar tathanhdinh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

b2r2's Issues

Symbolic Execution component #question

In section VI.C of the paper there is an interesting discussion on the implementation of a Symbolic Execution prototype. Is this code available? Having something for doing symbolic execution would be very interesting (something like the Sandbox concept in Miasm).

I see in the code an Eval module but it is marked as Obsolete in the comment. Also, I see two interesting interfaces: IAbstractInterpreter and DomainInterface but no implementation is provided. Have you any plan on how to implement them? In my opinion is a good part were we can provide contribution

Thanks

AND instruction not correctly lifted

Describe the bug
The following x86 instruction:

and eax, 0xFFFF0000

is lifted to the following statements:

T_0:I32 := (EAX & 0xFFFF0000:I32)
EAX := T_0:I32
OF := 0x0:I1
CF := 0x0:I1
AF := Undefined expression (AF is undefined.)

To Reproduce
Consider the following F# program:

let handler = 
	BinHandler.Init(
		ISA.OfString "x86", 
		ArchOperationMode.NoMode, 
		FileFormat.RawBinary, 
		Addr.MinValue, 
		[|0x25uy; 0x00uy; 0x00uy; 0xFFuy; 0xFFuy|] // and eax, 0xFFFF0000
	)

let instruction = BinHandler.ParseInstr handler 0UL
let statements = BinHandler.LiftInstr handler instruction

let statementString = LowUIR.Pp.stmtToString statements.[5]
Console.WriteLine(statementString)

It produces the following output:

AF := Undefined expression (AF is undefined.)	

Expected behavior
A correct LowUIR statement should be composed

Environment (please complete the following information):

  • OS: Windows
  • B2R2 version: 0.1.0

Additional context
The test was done by using .NET Framework 4.7.

Error in parse EAT

Describe the bug
νŠΉμ • νŒŒμΌμ„ BinHandler둜 λ‘œλ”©ν•˜λŠ” κ³Όμ •μ—μ„œ μ•„λž˜μ™€ μ—λŸ¬κ°€ λ°œμƒν•©λ‹ˆλ‹€.

C:\b2r2-dlls>ipy32 b2r2.py
./test/DeviceUxRes.dll
./test/DeviceUxRes.dll
Traceback (most recent call last):
  File "b2r2.py", line 30, in <module>
IndexError: μΈλ±μŠ€κ°€ λ°°μ—΄ λ²”μœ„λ₯Ό λ²—μ–΄λ‚¬μŠ΅λ‹ˆλ‹€.

To Reproduce

import clr
import os, sys

sys.path.append(os.path.abspath(r'./build_release/'))
clr.AddReferenceToFile(r'B2R2.Core.dll')
clr.AddReferenceToFile(r'B2R2.FrontEnd.Core.dll')
clr.AddReferenceToFile(r'B2R2.FrontEnd.Library.dll')

from B2R2 import *
from B2R2.FrontEnd import *


def search(dirname):
	result = []
	
	filenames = os.listdir(dirname)
	for filename in filenames:
		full_filename = os.path.join(dirname, filename)
		ext = os.path.splitext(full_filename)[-1]
		if ext == '.exe' or ext == ".dll": 
			result.append(full_filename)
	return result



result = search(r'./test/')
for name in result:
	try:
		print name
		handler = BinHandler.Init(ISA.OfString("i386"),name)
		for s in handler.FileInfo.GetSections():
				print "   %s:%s [%s]"%(hex(s.Address),hex(s.Address+s.Size),s.Name)
	except:
		print name
		raise
		#pass

μœ„ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜μ˜€μ„λ•Œ, μ •μƒμ μœΌλ‘œ λ™μž‘ν•œλ‹€λ©΄ μ„Ήμ…˜λ“€μ˜ 정보가 좜λ ₯λ˜μ–΄μ•Όκ² μ§€λ§Œ, 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€.

(λ¬Έμ œκ°€ λ°œμƒ ν•˜λŠ” νŒŒμΌμ€ μ—¬κΈ° μ—μ„œ λ‹€μš΄λ‘œλ“œ 받을 수 μžˆμŠ΅λ‹ˆλ‹€.)

Additional context

PEHelper의 parseEAT μ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•˜λŠ”λ°,

let offset = edt.ExportAddressTableRVA |> getRawOffset headers

이 μ½”λ“œμ—μ„œ edt.ExportAddressTableRVAκ°€ 0을 κ°€λ₯΄ν‚€κ³ , getRawOffset의 μ•„λž˜ λΆ€λΆ„μ—μ„œ

let sHdr = headers.SectionHeaders.[idx]

-1의 인덱슀λ₯Ό μ°Έμ‘°ν•˜μ—¬ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€.

이 νŒŒμΌμ€ μ•„λž˜ κ·Έλ¦Όκ³Ό 같이 Export Directoryκ°€ μ‘΄μž¬ν•˜κ³ , Name도 μžˆμ§€λ§Œ, λ‚˜λ¨Έμ§€ 값듀이 0인데, 이 κ²½μš°μ—λŠ” EATλ₯Ό νŒŒμ‹±ν•˜μ§€ μ•Šλ„λ‘ μˆ˜μ •ν•˜λ©΄ 될 것 κ°™μŠ΅λ‹ˆλ‹€.

Environment (please complete the following information):

  • OS: Windows 7
  • .NET Core version: 2.2.104
  • B2R2 version: lastest from github

Bug in GetSymbols

Describe the bug
Static ν˜•νƒœμ˜ 심볼을 가진 νŒŒμΌμ—μ„œ μ£Όμ†Œλ₯Ό κ°€μ Έμ˜¬λ•Œ μ‹€μ œ μ£Όμ†Œκ°€ μ•„λ‹Œ μ„Ήμ…˜μœΌλ‘œ λΆ€ν„°μ˜ μ˜€ν”„μ…‹λ§Œ κ°€μ Έμ˜΅λ‹ˆλ‹€.

To Reproduce
Steps to reproduce the behavior:
μ•„λž˜ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λ©΄ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import clr
import os, sys

sys.path.append(os.path.abspath(r'./build/'))
clr.AddReferenceToFile(r'B2R2.Core.dll')
clr.AddReferenceToFile(r'B2R2.BinFile.dll')
clr.AddReferenceToFile(r'B2R2.FrontEnd.Core.dll')
clr.AddReferenceToFile(r'B2R2.FrontEnd.Library.dll')

from B2R2 import *
from B2R2.FrontEnd import *
import time


def search(result,dirname):
	filenames = os.listdir(dirname)
	for filename in filenames:
		full_filename = os.path.join(dirname, filename)
		if os.path.isdir(full_filename):
			search(result,full_filename)
		else:
			ext = os.path.splitext(full_filename)[-1]
			if ext == '.so' or ext == ".o": 
				result.append(full_filename)
	return result
def main():
	result = search([],r'libc_test\0ab7d53ae2c1f7d4a2e3535d422ea434\a\extracted')
	#result = search([],r'libc\0ab7d53ae2c1f7d4a2e3535d422ea434\lib\aarch64-linux-gnu')
	
	NameList_bss = ['global_max_fast','save_arena'] #bss
	NameList_data = ['main_arena'] #main_arena
	
	NameList = NameList_bss + NameList_data
	
	print len(result)
	for name in result:
		handler = BinHandler.Init(ISA.DefaultISA, name)
		for s in handler.FileInfo.GetSymbols():
			if s.Kind == BinFile.SymbolKind.ObjectType:   
				for name in NameList:
					if s.Name == name:
						print name, s
			
if __name__ == "__main__":
	main()

Expected behavior
A clear and concise description of what you expected to happen.
μ£Όμ†Œλ₯Ό κ°€μ Έμ˜¬λ•Œ μ•„λž˜μ™€ 같은 값을 가져와야 ν•œλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

κ·ΈλŸ¬λ‚˜ μ„Ήμ…˜ λ² μ΄μŠ€κ°€ 빠진 값인 각각 0,0,8 을 μ–»μ–΄μ˜΅λ‹ˆλ‹€.

Environment (please complete the following information):

  • OS: Windows 7
  • .NET Core version: 2.2.204
  • B2R2 version latest from github

Additional context
μ‚¬μš©λœ νŒŒμΌμ€ μ—¬κΈ°μ—μ„œ λ‹€μš΄λ‘œλ“œ 받을 수 μžˆμŠ΅λ‹ˆλ‹€.

Improve function detection algorithm

Hi,
I use regularly this awesome framework and I would like to suggest a possible improvement to the function identification algorithm. In particular, I see that the code is not able to identify indirect calls (I read the code from function foldStmts in BinCorpus).

I came across a malware that uses static objects. In C++, a static object constructor is called from the _initterm function. This function uses a table of function pointers that are used to initialize the static objects. Since this table is in the .rdata section, the call is done indirectly. Here an excerpt of the code calling all the functions inside the table:

.text:004217B1                 mov     ecx, eax
.text:004217B3                 call    ds:___guard_check_icall_fptr
.text:004217B9                 call    [ebp+function_pointer]

A possible solution would be to apply the algorithm described in https://mistakenot.net/papers/eurosp-2017.pdf, in particular section 3.2.2 Unreachable/Indirectly Called Functions.

An example of this malware is: 8bf390bbf31d99d50d719ea8f413338ce5a1e1b3dac4ec58468a707cb7b8e914 (https://www.virustotal.com/gui/file/8bf390bbf31d99d50d719ea8f413338ce5a1e1b3dac4ec58468a707cb7b8e914/detection).

[Question] Any plan for supporting a SMT?

Is there any plan in the B2R2 roadmap to implement a SMT solver?

This feature would be interesting to have in order to "compete" with other binary analysis projects (like Triton, Angr, ...).

Relative calls lifting

Describe the bug
The LowIR of relative calls may not be correct.

To Reproduce
The instruction

e8 e9 fa ff ff        call -0x517

at the concrete address 0 is lifted into

-------------ISMark (0, 5)-------------
T_0:I64 := 0xFFFFFFFFFFFFFAEE:I64
T_1:I64 := 0x5:I64
RSP := (RSP - 0x8:I64)
[RSP] := T_1:I64
RIP := T_0:I64
-------------IEMark (5)-------------

Expected behavior
The instruction is a relative call, so the target PC should be relative to the next instruction, i.e. RIP := RIP - 0x517 + 5 (where 5 is the size of the instruction). Moreover, the value being pushed into the stack should be calculated relatively with RIP, i.e. [RSP] = RIP + 5.

Environment:

  • Windows 7 64 bit
  • .NET Core version: 2.2.200
  • B2R2 version: master:HEAD

Additional context
PR #5 is a dirty fix where B2R2 lifts this instruction to:

-------------ISMark (0, 5)-------------
T_0:I64 := (RIP + 0xFFFFFFFFFFFFFAF3:I64)
T_1:I64 := (RIP + 0x5:I64)
RSP := (RSP - 0x8:I64)
[RSP] := T_1:I64
RIP := T_0:I64
-------------IEMark (5)-------------

Thoughts
B2R2 currently computes directly the target using the current "concrete" address of the instruction and the containing basic block (then there is no need to use a symbolic PC).

In this call instruction, B2R2 pushes the returned "concrete PC" into the stack (e.g. [RSP] := 0x5) as well as compute the next "concrete PC".

The PR #5 makes B2R2 symbolizes the PC, then [RSP] := PC + 0x5. I think it would be a "more expected" behaviour in situations where we need a symbolique PC (e.g. when we need to formally prove something which should always be true regardless of the value of PC).

PS. The issue is just a personal point of view and may not a bug but I cannot set the label myself, sorry.

Disassembly of relative operands

Currently, B2R2 uses the current instruction address as base to present the relative target of a jmp or call, for example 48 31 d8 eb fd will be disassembled into

0x0        48 31 d8        xor rax, rbx
0x3        eb fd           jmp -0x1 ; 0x2

the value -0x1 means the target is at offset 0x1 w.r.t the current instruction (and this is correct).

But Intel assembly says that the offset should be computed w.r.t the address of the next instruction, so the result should be displayed as:

0x0        48 31 d8        xor rax, rbx
0x3        eb fd           jmp -0x3 ; 0x2

(since 0xfd = -0x3).

This is just a question to know whether it's the default behaviour of B2R2 (then it's not a bug, just B2R2 assembly is differ from Intel's)

I want to lift .so file but an error occurs

Describe the bug
I want to lift a .so file which is from Android APK file.
But an error occurs.
Other common .so files also occur the same issue.
How can I lift .so file?

To Reproduce
Steps to reproduce the behavior:

  1. I used F# script below

open B2R2
open B2R2.FrontEnd

[]
let main argv =
let isa = ISA.OfString "armv8a64"
let handler = BinHandler.Init (isa, "libapp.so")
let ins = BinHandler.ParseInstr handler 0UL
ins.Translate handler.TranslationContext |> printfn "%A"
0

  1. $ dotnet build
  2. $ dotnet run
  3. Build is successful but an error occurs like below.

Screenshots
image

Environment (please complete the following information):

  • OS: Windows11
  • .NET Core version: 7.0.201
  • B2R2 version 0.6.1

[Question] Assembling LowUIR to Assembly (x86/x64/...)

Hi,

I was wondering if you have planned a feature to compile LowUIR code to Assembly (x86/x64/...). I'm aware that is a complex feature but it would be of invaluable importance for the analysis.

My use case is the creation of an obfuscator that is able to clean a strongly obfuscated binary. An example of obfuscated binary is the malware Emotet (https://cert.grnet.gr/en/blog/reverse-engineering-emotet/) that recently came back. I can deobfuscate the binary by modifying the LowUIR code. What is missing is the binary rebuilding phase, by translating LowUIR to binary code (x86).

At this time, most of the deobfuscators are based on LLVM, which is capable of rebuilding the binary.

Updates?

Hi,

B2R2 is my preferred binary analysis framework, so I'm a bit sad to see that the last update in almost 1 year old. Any plan on releasing a new version?

Opcode and Operands accessibility

I want to write a custom pattern base on Opcode and Oprands of IntelInstruction but it is a field in internal type. I cannot access it from my library. Can I access that fields? Any problem if I publish InsInfo type?

[<NoComparison; CustomEquality>]
type InsInfo = internal {
  /// Prefixes.
  Prefixes        : Prefix
  /// REX Prefix.
  REXPrefix       : REXPrefix
  /// VEX information.
  VEXInfo         : VEXInfo option
  /// Opcode.
  Opcode          : Opcode
  /// Operands.
  Operands        : Operands
  /// Instruction size information.
  InsSize         : InsSize
}

Error when import function does not exist in wasm binary

Hello, I'm looking at wasm binary parsing module and it seems there is a bug.

In WebAssembly, import function is not necessary.

You can see an example. This example is in WebAssembly text format and it can directly convert to wasm binary using wat2wasm.

However, please look at the code. If the import function section does not exist, the lengths of the arrays can be different.

// src/FrontEnd/BinFile/WasmParser.fs

  let importedFuncs, impSecOff =
    match wm.ImportSection with
    | Some sec ->
      match sec.Contents with
      | Some conts ->
        conts.Elements
        |> Array.filter (fun ie ->
          match ie.Desc with
          | ImpFunc _ -> true
          | _ -> false
        ), sec.Offset
      | None -> [||], 0
    | None -> [||], 0 // importedFuncs can be [||].
  let lastIdx =
    let len = Array.length importedFuncs
    if len = 0 then 0u // lastIdx can be 0u.
    else uint32 (len - 1)
  let impFuncsIdxMap = // And here is a bug!
    importedFuncs
    |> Array.map2 (fun idx ifun ->
      makeFuncIdxInfo impSecOff idx ifun.Offset) [| 0u .. lastIdx |]

When importedFuncs is [||], lastIdx is 0u. And in impFuncsIdxMap, [| 0u .. lastIdx |] will be [| 0u |]. So there is an error like

Error: λ°°μ—΄μ˜ 길이가 μ„œλ‘œ λ‹€λ¦…λ‹ˆλ‹€.
array1.Length = 1, array2.Length = 0 (Parameter 'array1')

I think we can fix like below.

  let impFuncsIdxMap =
    if Array.isEmpty importedFuncs then [||]
    else
      importedFuncs
      |> Array.map2 (fun idx ifun ->
        makeFuncIdxInfo impSecOff idx ifun.Offset) [| 0u .. lastIdx |]

Does it take three to four seconds for each instruction to be decompiled? Is there any way to improve the efficiency?

I have deployed B2R2 successfully and used the following example to process instructions and output their IR.

open B2R2
open B2R2.FrontEnd
[<EntryPoint>]
let main argv =
  let isa = ISA.OfString "amd64"
  let bytes = [| 0x65uy; 0xffuy; 0x15uy; 0x10uy; 0x00uy; 0x00uy; 0x00uy |]
  let handler = BinHandler.Init (isa, bytes)
  let ins = BinHandler.ParseInstr handler 0UL
  ins.Translate handler.TranslationContext |> printfn "%A"
  0

However, I have observed that it takes around three to four seconds to process each instruction.

Considering that an average ELF file typically contains tens of thousands of instructions, and if we need to process hundreds of thousands of such ELF files, the computational time required becomes substantial.

Even with the utilization of multi-threading, it remains difficult to effectively address this issue.

Therefore, I would like to inquire if there are any recommended methods to solve this problem.

x86/64 lifting loop instruction

Describe the bug
The loop instruction contains unnecessary internal (intra) jumps as discussed in #15.

To Reproduce

0x19146D:    E2 FB                          loop -0x3 ; 0x19146A

By lifting the above instruction, we get:

-------------ISMark (19146D, 2)-------------
T_710:I32 := ECX
-------------LMark (Loop)-------------
T_710:I32 := (T_710:I32 - 0x1:I32)
if(T_710:I32 != 0x0:I32) then Jmp (Continue, 127) else Jmp (End, 128)
-------------LMark (Continue)-------------
EIP := (EIP + 0x19146A:I32)
Jmp (Loop, 126)
-------------LMark (End)-------------
-------------IEMark (19146F)-------------

Expected behavior
The loop instruction in x86 should be handled the same as in jcc instructions. But currently we are treating loop in a particularly different way.

Additional context

See #15 for more discussion.

Incorrect disassembly

(many thanks for this new framework, I'm just discovering it)

Describe the bug
Incorrect disassembly of instruction

44 0f 20 c0    mov rax, cr8

To Reproduce

  1. Run this piece of code
open B2R2
open B2R2.FrontEnd

[<EntryPoint>]
let main argv =
  let isa = ISA.OfString "amd64"
  let bytes = [| 0x44uy; 0x0fuy; 0x20uy; 0xc0uy |]
  let handler = BinHandler.Init (isa, bytes)
  let ins = BinHandler.ParseInstr handler 0UL
  ins
  |> fun i -> i.Disasm ()
  |> printfn "%s"
  0
  1. See output
mov eax, cr0

Expected behavior

mov rax, cr8

Environment (please complete the following information):

  • OS: Windows 7 x64
  • .NET Core version: 2.2.104
  • B2R2 version: latest from Github

Incorrect disassembly

Describe the bug
Incorrect disassembly
(Although amd64 is described as an example, there are similar bugs in x86)

To Reproduce

  1. run this code
import clr
import os, sys

clr.AddReferenceToFile(r'B2R2.Core.dll')
clr.AddReferenceToFile(r'B2R2.FrontEnd.Core.dll')
clr.AddReferenceToFile(r'B2R2.FrontEnd.Library.dll')

from B2R2 import *
from B2R2.FrontEnd import *


##Expected behavior : fnstcw word ptr [ds:rdx]
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('d93a')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #fstcw dword ptr [rdx]

##Expected behavior : fdivr qword ptr [rdi]
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('dc3f')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #fdivr dword ptr [rdi]

##Expected behavior : fsub qword ptr [rdi]
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('dc27')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #fsub dword ptr [rdi]

##Expected behavior : fcomp qword ptr [r9]
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('41dc19')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #fcomp dword ptr [r9]

##Expected behavior : out dx, al
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('ee')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #out al, dx

##Expected behavior : in eax,dx
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('4fed')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #in rax,dx

##Expected behavior : mov eax, ss
isa = ISA.OfString("amd64")
binary = ByteArray.ofHexString('8cd0')
handler = BinHandler.Init(isa, binary)
ins = handler.ParseInstr(handler, 0)
print ins.Disasm() #mov ax, ss

2.result

fstcw dword ptr [rdx]
fdivr dword ptr [rdi]
fsub dword ptr [rdi]
fcomp dword ptr [r9]
out al, dx
in rax, dx
mov ax, ss

Expected behavior

fnstcw word ptr [ds:rdx]
fdivr qword ptr [ds:rdi]
fsub qword ptr [ds:rdi]
fcomp qword ptr [ds:r9]
out dx, al
in eax, dx
mov eax, ss

Environment (please complete the following information):

  • OS: Windows 7 x64
  • .NET Core version: 2.2.104
  • B2R2 version : 0.2.0 (latest release)

repz lifting question

Hi,

I noticed that if we consider the following instruction:

0x40102E:    F3 A4                          repz movsb

it is lifted to the following statements:

-------------ISMark (40102E, 2)-------------
if(ECX = 0x0:I32) then Jmp (Exit, 0) else Jmp (Continue, 1)
-------------LMark (Continue)-------------
[EDI] := [ESI]:I8
ESI := (ite (DF) ((ESI - 0x1:I32)) ((ESI + 0x1:I32)))
EDI := (ite (DF) ((EDI - 0x1:I32)) ((EDI + 0x1:I32)))
ECX := (ECX - 0x1:I32)
EIP := 0x40102E:I32
-------------LMark (Exit)-------------
EIP := 0x401030:I32
-------------IEMark (401030)-------------

This is conceptually correct but it is a bit weird, it is like if we are executing something as

jmp $

until some condition happens (in this case ECX = 0). Would it be better to have something like this one?

-------------ISMark (40102E, 2)-------------
-------------LMark (If)-------------
if(ECX = 0x0:I32) then Jmp (Exit, 0) else Jmp (Continue, 1)
-------------LMark (Continue)-------------
[EDI] := [ESI]:I8
ESI := (ite (DF) ((ESI - 0x1:I32)) ((ESI + 0x1:I32)))
EDI := (ite (DF) ((EDI - 0x1:I32)) ((EDI + 0x1:I32)))
ECX := (ECX - 0x1:I32)
Jmp (If, _)
-------------LMark (Exit)-------------
-------------IEMark (401030)-------------

Error in getRawOffset

Describe the bug
νŠΉμ • νŒŒμΌμ„ νŒŒμ‹±ν•˜λŠ” κ³Όμ •μ—μ„œ getRawOffset 이 μ œλŒ€λ‘œ λ™μž‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

Traceback (most recent call last):
  File "b2r2.py", line 31, in <module>
IndexError: μΈλ±μŠ€κ°€ λ°°μ—΄ λ²”μœ„λ₯Ό λ²—μ–΄λ‚¬μŠ΅λ‹ˆλ‹€.

To Reproduce

이전에 올렸던 μ΄μŠˆμ—μ„œ μ‚¬μš©λœ μ½”λ“œμ™€ λ™μΌν•˜κ³  파일만 λ‹€λ¦…λ‹ˆλ‹€.

λ‹€μš΄λ‘œλ“œ

Environment (please complete the following information):

  • OS: Windows7
  • .NET Core version: 2.2.204
  • B2R2 version: latest from github

Additional context
이 λ²„κ·ΈλŠ” B2R2의 버그라기 λ³΄λ‹€λŠ” PE파일의 κ΄΄λž„ν•œμ (각 μ„Ήμ…˜μ˜ VirtualSizeλŠ” PEνŒŒμΌμ„ λ‘œλ”©ν•˜λŠ”λ° μ‚¬μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ VirtualSizeκ°€ μ„€λ Ή μ΄λŸ°μ‹μ˜ ν˜•νƒœλ₯Ό λ„κ³ μžˆλ‹€ 할지라도 λ©€μ •ν•˜κ²Œ λ™μž‘ν•©λ‹ˆλ‹€. )κ³Ό .NET Core의GetContainingSectionIndex κ΅¬ν˜„μ— κΈ°μΈν•©λ‹ˆλ‹€.

μœ„ 사진은 .net core의 GetContainingSectionIndex κ΅¬ν˜„μΈλ°, 이 파일의 경우 λͺ¨λ“  μ„Ήμ…˜μ˜ VAκ°€ 0인 κ΄΄λž„ν•œ ν˜•νƒœμž…λ‹ˆλ‹€. λ”°λΌμ„œ -1을 λ¦¬ν„΄ν•˜κ²Œ 되고, b2r2κ°€ μ œλŒ€λ‘œ λ™μž‘ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

b2r2μ‚¬μš©μ— 영ν–₯을 쀄 수 μžˆλŠ” 만큼, ν˜„μž¬μ„Ήμ…˜(i)의 VA와 λ‹€μŒμ„Ήμ…˜(i+1)의 VirtualAddress 사이에 λ³€ν™˜ν•˜λŠ”κ²ƒμ„ μ›ν•˜λŠ” RVA값이 μžˆλŠ”μ§€ ν™•μΈν•˜λŠ”μ‹μœΌλ‘œ, b2r2μ—μ„œ 자체적으둜 κ΅¬ν˜„ν•˜λŠ”κ²Œ μ–΄λ–»μŠ΅λ‹ˆκΉŒ?

Does the B2R2 support PPC32 or Sparc?

I have successfully built b2r2 and tested it for decompiling languages on the amd64, ARM, and MIPS architectures. However, when I change the "isa" parameter to "ppc32" or "sparc," I encounter errors. Here are examples and the error messages.

CODE:

open B2R2
open B2R2.FrontEnd

[<EntryPoint>]
let main argv =
  let isa = ISA.OfString "ppc32"
  let bytes = [| 0x48uy; 0x00uy; 0x00uy; 0x64uy|]
  let hdl = BinHandle.Init (isa, bytes)
  let ins = BinHandle.ParseInstr hdl 0UL
  ins.Translate hdl.TranslationContext |> printfn "%A"
  0

or

open B2R2
open B2R2.FrontEnd

[<EntryPoint>]
let main argv =
  let isa = ISA.OfString "sparc"
  let bytes = [| 0x90uy; 0x10uy; 0x20uy; 0x0cuy|]
  let hdl = BinHandle.Init (isa, bytes)
  let ins = BinHandle.ParseInstr hdl 0UL
  ins.Translate hdl.TranslationContext |> printfn "%A"
  0

The same error messages are shown as follows:
ERROR MESSAGE

Unhandled exception. B2R2.InvalidISAException: Exception of type 'B2R2.InvalidISAException' was thrown.
   at B2R2.ISA.OfString(String s)
   at Program.main(String[] argv) in C:\Program Files\dotnet\test1\Program.fs:line 9

I suspect that b2r2 does not support these two architectures, which is why it is throwing this error
But I noticed that there is relevant code for handling the PPC and SPARC architectures in the given GitHub source code. The path is as follows:
src/FrontEnd/BinLifter/PPC32
src/FrontEnd/BinLifter/Sparc64

So now I don't know how to solve this problem. Can anyone please help me?

[Question] Question on analyzing the ARMv8 aarch32 binary

Hi, I was trying to analyze the binary of ARMv8 aarch32 using the following code.

let bytes = File.ReadAllBytes("xxx.bin")
let isa = ISA.OfString "armv8a32"
let hdl = BinHandle.Init (isa, bytes) //line 15

However, I got the following result.
image

Does the B2R2 support the analysis on ARMv8 aarch32 binary?

pcmpistri lifting infine loop

Describe the bug
Lifting of Intel instruction PCMPESTRI causes an infinite loop.

To Reproduce
The following code triggers the infinite loop.

let handler = BinHandler.Init(isa, ArchOperationMode.NoMode, false, 0UL, [|0x066uy; 0x00fuy; 0x03auy; 0x063uy; 0x0c1uy; 0x041uy|])
let stmts = 
  match BinHandler.LiftBBlock handler 0UL with
  | Ok (stmts, addr) -> stmts
  | Error (stmts, addr) -> stmts
Console.WriteLine("This code is never reached")

Function genOutput seems to be the cause of the infinite loop.

Expected behavior
Valid LowUIR statements are generated

Environment (please complete the following information):

  • OS: Windows
  • .NET Core version: 3.1
  • B2R2 version 0.3.1

Additional context
The Intel instruction used for the test is: pcmpistri xmm0, xmm1, 41h

Behavior change of FormatDetector.Detect()

Describe the bug
B2R2 0.1.x used to return a tuple of FileFormat and ISA in FormatDetector.Detect() method.
However, the method was changed to return only FileFormat in the 0.2.x. Is it intentional? If it is, where can I find the substitute for the ISA detector method?

I also have looked through the docs page. It claims that FormatDetector.Detect() returns a tuple of (B2R2.FileFormat and B2R2.ISA), but the signature of the method shows only FileFormat parameter (val detect: file:string -> FileFormat).

To Reproduce
In a C# project, copy and paste this code.
Go to the NuGet package manager and downgrade/upgrade B2R2 package, and see which method is allowed per version.

// B2R2 0.1.x
(FileFormat format, ISA isa) = FormatDetector.Detect(targetFile);
// B2R2 0.3.0 - where did `ISA isa` go?
FileFormat format = FormatDetector.Detect(targetFile);

I believe it is easily reproducible in F#, too.

Expected behavior
If the change was intentional, the docs page should be corrected.
If the ISA detection was not dropped, the docs page should let users know about the alternative API.

Screenshots
With B2R2.FrontEnd 0.1.1: First method is allowed.
image

With B2R2.FrontEnd 0.3,0: Second method is allowed.
image

Docs page has contradicting description and method signature.
image

Environment (please complete the following information):

  • OS: Linux Mint 19.2, Windows 10 v1909
  • .NET Core version: 3.0.101
  • B2R2 version 0.3.0
  • Language : C#

Additional context
I originally reviewed applying LowUIR into my project, but had to dismiss the idea because the project was so integrated into the VEX-IR. So for now, I am mainly using B2R2 as a binary Format and ISA detector. The software capable of detecting binary information is quite a few, alongside (string-parsing-required) libmagic.

P.S. B2R2 ν”„λ‘œμ νŠΈμ˜ 결과물을 λͺ¨λ‘κ°€ μ“Έ 수 있게 κ³΅κ°œν•΄ μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€.

EDIT: Fixed a few typos.

How to update the binary handler content?

Hi,

I'm creating a simple emulator for LowUIR and during my test I need to modify the content of the Binary Handler (as in a self modifying program).

As far as I understood the only way in doing this is by using the BinHandler.UpdateCode function, that return a new handler. The function accepts an array of bytes that represent the full content of the new handler. This is a bit annoying if I have to modify just a couple of bytes (but I understand that you want to maintain BinHandler immutable).

My current strategy is:

  • Convert the VA with a raw offset inside the buffer
  • Read the handler buffer and modify the given bytes at the calculated offset
  • create an handler with the update buffer

Is there any other easier way of doing this or what I described is the proper way?

I think I found a B2R2 LowUIR bug.

Describe the bug
μ•ˆλ…•ν•˜μ„Έμš”. κ΅μˆ˜λ‹˜,
μ œκ°€ 버그λ₯Ό 찾은 κ±° κ°™μ•„ λ¬Έμ˜λ“œλ¦½λ‹ˆλ‹€.

https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/BL--Branch-with-Link-

μœ„ ARM λ¬Έμ„œμ— λ”°λ₯΄λ©΄ 16μ§„μˆ˜ 기계어 μ½”λ“œ 94000007λŠ” ν˜„μž¬ μ£Όμ†Œμ—μ„œ 7 * 4 = 28(0x1C)만큼 λ”ν•œ κ°’μœΌλ‘œ μ ν”„ν•˜λΌλŠ” μ˜λ―ΈμΈλ°μš”.
λ°”μ΄λ„ˆλ¦¬λ₯Ό B2R2 LowUIR둜 λ¦¬ν”„νŒ…ν•΄λ³΄λ©΄ 2EDB80: Jmp (PC + [0x2EDB9C:I64]:I64)라고 λ‚˜μ˜΅λ‹ˆλ‹€.
0x2EDB80 + 0x1C = 0x2EDB9C μ£Όμ†Œλ‘œ 점프λ₯Ό ν•΄μ•Όν•˜κΈ° λ•Œλ¬Έμ— μ•„λž˜κ°€ λ§žλŠ” ν‘œν˜„κ°™μ€λ°μš”.
2EDB80: Jmp (PC + 0x1C:I64):I64

To Reproduce
[<EntryPoint>]
let main argv =
let address = 0x2EDB80UL
let isa = ISA.OfString "aarch64"
let hdl = BinHandler.Init (isa, "default_flutter_3.so")
let ins = BinHandler.ParseInstr hdl address
let statements = BinHandler.LiftInstr hdl ins
statements |> Seq.iteri (fun i stmt ->
let stmtStr = B2R2.BinIR.LowUIR.Pp.stmtToString stmt
printf "%X: %A\n" address stmtStr)
0

μ•„λž˜λŠ” so파일 λ§ν¬μž…λ‹ˆλ‹€.
https://github.com/sanghwa95/tmp/blob/main/default_flutter_3.so

Environment (please complete the following information):

  • OS: Windows11
  • .NET Core version: 7.0.201
  • B2R2 version: 0.6.1

Additional context
AST둜 좜λ ₯ν•˜λŠ” μ½”λ“œλ₯Ό LowUIR둜 좜λ ₯ν•˜λ„λ‘ μ½”λ“œ μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€.

ARMv7: incorrect disassembly of VCVT

Describe the bug
The sequence of bytes "E00AB7EE" (little-endian) should decode to

"vcvt.f64.f32 d0, s1"

But the ARM32 decoder will decode to

"vcvt.f64.f32 s0, s1"

It seems that the helper "getRegAL" is incorrect for VCVT.

To Reproduce

Try to decode "E00AB7EE" for ARMv7.

Expected behavior

The sequence of bytes should decode to

"vcvt.f64.f32 d0, s1".

InterJmp pretty-printing

Describe the bug
Currently there's no difference between Put to a PC register and an InterJmp statement. That is, both show the same pp: EIP := .... We should clearly distinguish two statements.

Additional context
See discussion in #15.

Error in parse export for DLL profapi.dll

Describe the bug
When a BinHandler is created, the code in charge for parsing the Export table raise an exception. The problematic file is C:\Windows\SysWOW64\profapi.dll (MD5: 2545F5AA189DF907BC4A6E000E231A6D).

To Reproduce
Steps to reproduce the behavior:

  1. Run code: BinHandler.Init(ISA.OfString "x86", @"C:\Windows\SysWOW64\profapi.dll")
  2. See error in PEHelper.getRawOffset, exception message: System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'

Environment (please complete the following information):

  • OS: Windows
  • B2R2 version: 0.1.1 (2019-03-22)

Additional context
The DLL contains a uncommon Export directory table, an example of export is:

Ordinal        Function RVA   Name Ordinal    Name RVA    Name
00000066	00004560	N/A	      N/A	  N/A

This will raise an exception at the following code:
let sHdr = headers.SectionHeaders.[idx]
where idx has value -1.

[BinHandler] Error in section reading

Describe the bug
I have a PE with the following sections:

Sec name | Virtual Size | Virtual Address | Raw Size | Raw Address
.text | 0x12A56 | 0x401000 | 0x12C00 | 0x400
.data | 0x1278 | 0x414000 | 0x200 | 0x13000
.reloc | 0x980 | 0x416000 | 0xA00 | 0x13200

If I try to read the content of the data section I receive the following error:

System.ArgumentException: 'Invalid address or size is given.
Parameter name: ReadBytes'

To Reproduce
Steps to reproduce the behavior:

  1. Consider the following source code:
        handler.FileInfo.GetSections()
        |> Seq.iter(fun section ->
            Console.Write("Read all data from section: {0} => ", section.Name)
            handler.ReadBytes(section.Address, int32 section.Size) |> ignore
            Console.WriteLine("DONE")
        )
  1. See error
Read all data from section: .text => DONE
Read all data from section: .data =>

System.ArgumentException: 'Invalid address or size is given.
Parameter name: ReadBytes'

Expected behavior
The full content of the section is read.

Environment (please complete the following information):

  • OS: Windows
  • B2R2 version 0.2.0

Additional context
This problem seems to be caused by a check on invalid range. It wasn't present in previous version.

Improve Segmentation Semantics for x86

Describe the bug
LowIR for instructions accessing segmented memory is "probably" incorrect, e.g.

2e 8b 02    mov eax, cs:[edx]

To Reproduce

  1. Run this piece of code
open B2R2
open B2R2.FrontEnd

[<EntryPoint>]
let main argv =
  let isa = ISA.OfString "x86"
  let bytes = [| 0x2euy; 0x8buy; 0x02uy; |]
  let handler = BinHandler.Init (isa, bytes)
  let ins = BinHandler.ParseInstr handler 0UL
  ins
  |> fun i -> i.Translate handler.TranslationContext
  |> printfn "%A"
  0
  1. See output
[|ISMark (0UL,3u);
  Put
    (Var (32,8,"EAX",IntelRegisterSet<1, 0, 0, 0>),
     Load
       (Little,32,
        BinOp
          (ADD,32,Var (32,1793,"CSBase",IntelRegisterSet<8000000, 0, 0, 0>),
           Var (32,11,"EDX",IntelRegisterSet<8, 0, 0, 0>),
           {HasLoad = false;
            VarInfo = IntelRegisterSet<8000008, 0, 0, 0>;
            TempVarInfo = set [];},None),
        {HasLoad = true;
         VarInfo = IntelRegisterSet<8000008, 0, 0, 0>;
         TempVarInfo = set [];},None)); IEMark 3UL|]

Expected behavior
In the Intel's segmented memory model, the semantics of cs:[edx] is not simply as loading [cs + edx].

Environment:

  • OS: [e.g. iOS]
  • .NET Core version: [e.g. 2.1.502]
  • B2R2 version [e.g. 0.1.0]

Additional context
I'm reading the paper but it does not discuss about how the memory is modelled.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.