01-29-2025, 05:34 PM
(This post was last modified: 01-29-2025, 05:56 PM by Anne Wilson.)
This program will detect if it is being run in a Virtual Box Virtual Machine VM.
Note that hackers will place your programs to run in a VM so that they can
pirate or hack your programs.
This is to detect whether the user is using a Virtual Box VM and to do the
necessary counter action.
Please let me know if you encounter issue with this program.
Note that hackers will place your programs to run in a VM so that they can
pirate or hack your programs.
This is to detect whether the user is using a Virtual Box VM and to do the
necessary counter action.
Please let me know if you encounter issue with this program.
Code:
' Detect VirtualBox2.bas
' The program checks the SystemProductName value in the
' HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\BIOS registry key.
' If the value contains the string "VIRTUALBOX", it
' assumes the program is running inside a VirtualBox virtual machine.
' This method relies on the registry value, which can be modified
' by advanced hackers or customized VirtualBox installations.
' Additional checks :
' Detect VirtualBox with additional checks such as
' VirtualBox Guest Additions:
' The program checks for the presence of the
' C:\Program Files\Oracle\VirtualBox Guest Additions directory.
' If this directory exists, it is a strong indicator that
' VirtualBox Guest Additions are installed.
' VirtualBox Graphics Adapter:
' The program checks the registry for the DriverDesc value under the
' SYSTEM\CurrentControlSet\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000 key.
' If the value contains "VirtualBox", it indicates the presence of the
' VirtualBox Graphics Adapter.
' VirtualBox Shared Folders Service:
' The program checks for the VBoxSF service in the registry under
' SYSTEM\CurrentControlSet\Services\VBoxSF. If this key exists, it
' indicates that the VirtualBox Shared Folders service is installed.
' Some checks may require administrative privileges to access
' certain registry keys or files.
' VirtualBox environments can be customized by hackers, so these checks
' may not work in all cases.
#COMPILE EXE
#DIM ALL
#INCLUDE "Win32Api.inc"
' Constants for registry access
%KEY_QUERY_VALUE = &H0001
%KEY_WOW64_64KEY = &H0100
'=========================
FUNCTION PBMAIN() AS LONG
IF IsVirtualBox() THEN
? "Running inside VirtualBox."
ELSE
? "Not running inside VirtualBox."
END IF
END FUNCTION
'===================================
FUNCTION IsVirtualBox() AS LONG
' Check for VirtualBox specific hardware
LOCAL hvbKey AS LONG
LOCAL lResult AS LONG
LOCAL dwType AS LONG
LOCAL dwSize AS LONG
LOCAL szBuffer AS ASCIIZ * 256
LOCAL tmpVBfind AS LONG
' Indicator to signify that Virtual Box is found
tmpVBfind = 0
' Check for VirtualBox in the registry for "HARDWARE\DESCRIPTION\System\BIOS" key
lResult = RegOpenKeyEx(%HKEY_LOCAL_MACHINE, HwBios , _
0, %KEY_QUERY_VALUE OR %KEY_WOW64_64KEY, hvbKey)
IF lResult = 0 THEN
dwSize = SIZEOF(szBuffer)
' search for "SystemProductName" in the registry key
lResult = RegQueryValueEx(hvbKey, StSysPNam , 0 , _
dwType, BYVAL VARPTR(szBuffer), dwSize)
IF lResult = 0 THEN
' checks for presence of "VIRTUALBOX"
IF INSTR(UCASE$(szBuffer), StVbox ) > 0 THEN
' Running inside VirtualBox
tmpVBfind = 1
END IF
END IF
RegCloseKey(hvbKey)
END IF
IF tmpVBfind > 0 THEN
' inside Virtual Box, we exit
FUNCTION = 1
EXIT FUNCTION
END IF
' Check for VirtualBox Guest Additions folder
' C:\Program Files\Oracle\VirtualBox Guest Additions
IF GetFileAttributes(StGuestAdd) <> _
%INVALID_FILE_ATTRIBUTES THEN
' VirtualBox Guest Additions folder found
tmpVBfind = 2
END IF
IF tmpVBfind > 0 THEN
' inside Virtual Box, we exit
FUNCTION = 1
EXIT FUNCTION
END IF
' Check for VirtualBox Graphics Adapter in the registry
' SYSTEM\CurrentControlSet\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000
lResult = RegOpenKeyEx(%HKEY_LOCAL_MACHINE, _
VboxReg , 0, %KEY_QUERY_VALUE OR %KEY_WOW64_64KEY, hvbKey)
IF lResult = 0 THEN
dwSize = SIZEOF(szBuffer)
' DriverDesc
lResult = RegQueryValueEx(hvbKey, DrvDesc , 0, _
dwType, BYVAL VARPTR(szBuffer), dwSize)
IF lResult = 0 THEN
' checks for presence of "VIRTUALBOX"
IF INSTR(UCASE$(szBuffer), StVbox ) > 0 THEN
' VirtualBox Graphics Adapter found
tmpVBfind = 3
END IF
END IF
RegCloseKey(hvbKey)
END IF
IF tmpVBfind > 0 THEN
' inside Virtual Box, we exit
FUNCTION = 1
EXIT FUNCTION
END IF
' Check for VirtualBox Shared Folders service
' SYSTEM\CurrentControlSet\Services\VBoxSF
lResult = RegOpenKeyEx(%HKEY_LOCAL_MACHINE, VBoxSF ,_
0, %KEY_QUERY_VALUE OR %KEY_WOW64_64KEY, hvbKey)
IF lResult = 0 THEN
' VirtualBox Shared Folders service found
tmpVBfind = 4
RegCloseKey(hvbKey)
END IF
IF tmpVBfind > 0 THEN
'inside Virtual Box, we exit
FUNCTION = 1
EXIT FUNCTION
END IF
' Not running inside VirtualBox
FUNCTION = 0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' SYSTEM\CurrentControlSet\Services\VBoxSF
FUNCTION VBoxSF() AS STRING
' Text is 40 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(40)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 40
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 40
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 79,184,216,160,164,135,207,62,1,240,202,123,92,83,181,95
! db 45,190,211,214,162,219,235,86,157,31,187,119,158,43,215,78
! db 122,59,140,231,178,30,78,170,0
#ALIGN 4
paddlabel:
! db 28,225,139,244,225,202,147,125,116,130,184,30,50,39,246,48
! db 67,202,161,185,206,136,142,34,193,76,222,5,232,66,180,43
! db 9,103,218,165,221,102,29,236,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' DriverDesc
FUNCTION DrvDesc() AS STRING
' Text is 10 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(10)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 10
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 10
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 95,19,30,205,113,174,192,70,177,188,0
#ALIGN 4
paddlabel:
! db 27,97,119,187,20,220,132,35,194,223,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' SYSTEM\CurrentControlSet\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000
FUNCTION VboxReg() AS STRING
' Text is 82 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(82)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 82
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 82
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 249,4,13,80,88,97,132,5,24,111,213,141,243,173,242,206
! db 189,198,195,6,254,205,26,36,221,142,19,135,64,61,151,78
! db 153,171,45,85,38,106,176,110,168,138,35,114,191,211,155,93
! db 173,26,92,124,246,134,68,157,34,41,54,12,4,160,71,157
! db 217,220,127,46,171,7,143,39,93,53,250,48,59,101,31,82
! db 71,223,0
#ALIGN 4
paddlabel:
! db 170,93,94,4,29,44,216,70,109,29,167,232,157,217,177,161
! db 211,178,177,105,146,158,127,80,129,205,124,233,52,79,248,34
! db 197,232,65,52,85,25,236,21,156,206,16,68,250,234,173,101
! db 128,95,111,78,195,171,117,172,97,108,27,78,66,227,118,176
! db 233,228,79,30,153,69,202,22,109,6,203,8,70,57,47,98
! db 119,239,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' C:\Program Files\Oracle\VirtualBox Guest Additions
FUNCTION StGuestAdd() AS STRING
' Text is 50 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(50)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 50
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 50
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 7,220,75,224,3,68,53,168,252,239,117,24,148,135,223,200
! db 247,24,180,35,112,47,153,150,228,9,220,195,146,241,190,213
! db 5,52,58,119,39,226,64,247,210,250,250,22,125,81,204,206
! db 154,127,0
#ALIGN 4
paddlabel:
! db 68,230,23,176,113,43,82,218,157,130,85,94,253,235,186,187
! db 171,87,198,66,19,67,252,202,178,96,174,183,231,144,210,151
! db 106,76,26,48,82,135,51,131,242,187,158,114,20,37,165,161
! db 244,12,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' HARDWARE\DESCRIPTION\System\BIOS
FUNCTION HwBios() AS STRING
' Text is 32 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(32)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 32
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 32
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 6,160,141,231,80,254,21,81,26,141,42,230,212,123,191,161
! db 135,127,23,20,125,4,86,219,249,27,41,173,135,79,226,31,0
#ALIGN 4
paddlabel:
! db 78,225,223,163,7,191,71,20,70,201,111,181,151,41,246,241
! db 211,54,88,90,33,87,47,168,141,126,68,241,197,6,173,76,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' SystemProductName
FUNCTION StSysPNam() AS STRING
' Text is 17 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(17)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 17
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 17
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 13,86,169,118,27,35,11,70,75,176,244,132,4,124,41,63
! db 53,0
#ALIGN 4
paddlabel:
! db 94,47,218,2,126,78,91,52,36,212,129,231,112,50,72,82
! db 80,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
' VIRTUALBOX
FUNCTION StVbox() AS STRING
' Text is 10 bytes excluding the terminating zero
#REGISTER NONE
LOCAL src AS DWORD
LOCAL dst AS DWORD
LOCAL outpt$
src = CODEPTR(datalabel)
outpt$ = NUL$(10)
dst = STRPTR(outpt$)
' -------------------
' copy data to string
' -------------------
! mov esi, src
! mov edi, dst
! mov ecx, 10
! rep movsb
src = CODEPTR(paddlabel)
' -----------------------------
' xor string data to unique pad
' -----------------------------
! mov esi, dst
! mov ebx, 10
! mov edi, src
! add esi, ebx
! add edi, ebx
! neg ebx
lbl0:
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jz lbl1
! movzx eax, BYTE PTR [edi+ebx]
! xor [esi+ebx], al
! add ebx, 1
! jnz lbl0
lbl1:
FUNCTION = outpt$
EXIT FUNCTION
#ALIGN 4
datalabel:
! db 44,250,255,129,228,77,15,253,221,22,0
#ALIGN 4
paddlabel:
! db 122,179,173,213,177,12,67,191,146,78,0
END FUNCTION
' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤