Application security

Building Custom Controls in C# - Part 1

Victor Marak
March 7, 2013 by
Victor Marak

ABSTRACT

Custom UI Controls require a good grasp of GDI+, collections, delegates/events and smart use of multithreading facilities provided by the C# language. Here, we will focus on 2D graphics that incorporates the System. Drawing and System.Drawing.Drawing2D namespaces. You will build a custom control as an exercise.

Custom controls are different from components in C# lingo, in that they have a user interface which can interact with the user who intends to use it. Therefore, the use of a keyboard, mouse, joystick and pen has to be supported. Think of a network visualizer screen, a custom mixer in a music software package, a 3D mesh control or a simple textbox with a zooming feature added – the possibilities are endless and very simple once you get the hang of it.

Finally, the visualizer looks like this: drag and drop any file on the main form. The title bar sets the filename of the input file, the entropy score is shown in a big font and the histogram distribution is shown at the right. In this picture, we see a sosex_64.zip package, which is a compressed file and its entropy value and byte distribution/histogram.

PREREQUISITES

Software Requirements: Visual Studio 2005 and above.

Elementary knowledge of C#.

Skill Level: Intermediate

DESIGN CONCEPTS

We will build an entropy visualizer and understand the construction steps.

We will also take a look at building a parallel axis graph control later.

Basically, to implement any sort of UI interactivity as graphics, you need to set boundaries in the visual area in terms of basic shapes or a series of points. Both of these can be generated in runtime or pre-calculated to your liking.Each of these bounded areas (series of closed points/shapes) has something called a regionthat is a data structure that encapsulates the area and the possible set of points that fall in that area. Thus, any sort of visual interaction done by the user is recorded and processed as the points that have been interacted with onscreen.

Every mouse movement can be traced as a set of coordinates on the visual area of the control and thus, a pre-demarcated or generated set of point based boundaries immediately resolve the proximity awareness part of handling visual boundaries. To illustrate, each figure in the sample UI windows form has its own set of area points and a clear boundary. A straight line will still occupy a set of points that enable its representation. This set of points will be both,its boundary and its constituent set of points. Thus, tracking movement within the control visual area and incorporating logic becomes a simple issue as long as we know what is the current point being pointed to by the user and what part of the visual area contains that point. This is a fundamental concept to understand before you build your next Rodin in C#.

To construct a simple entropy visualizer, we first need to understand what entropy is and find the best way to implement it in C# code. To summarize academic definitions, it is thedegree of chaos in a system. It is also the number of bits required to represent information. If you look at compressed files or packed code, the entropy is high – in a range of 0 to 8 (bits per byte), the entropy is 6.XX - 7.XX, where XX signifies decimal digits, as the value of entropy is calculated from the histogram of the whole data set, whereby the probability is calculated of each byte over the entire data set size and finally calibrated to accommodate a logarithmic scale.

The main formula is (in words):

ENTROPY = SUMMATION OFi= (0 TO N) [-PROBABILITY OF BYTE [i] * LOG BASE 2 (PROBABILITY OF BYTE[i])]

… where the probability is just the count of occurrence of each byte (histogram) divided by the total data set size, which gives us an idea of the distribution.

There are many reasons why entropy is important in information theory, but it mainly has to do with how we humans process data,patterns and the natural occurrence of chaotic systems that maintain harmony with each other.Let's say we take language as an example. Humans have an innate ability to gauge and recognize patterns in most things. We are creatures of habit and it comes from this. We like to categorize things and predict events. Written language is not different in that it always contains a set of elements called the alphabet from which words are constructed.

These words are woven into sentences and sentences are spun to describe everything we see and feel around us. This whole process seems iterative and repeatable. The most used letter in the English language is 'e'. It's a vowel and thus is very important in giving most words its sound.Other languages also have a set number of highly used words and sounds.

It follows that in a set of sentences that have a structure wherein the subject, verb and object are well placed – the entropy or the disorder is minimal in this context. Yet, if every sentence in another paragraph is not made of carefully constructed words and semantic sentences but rather a rather random set of characters (including punctuation) that don't conform to that language's rules and idioms – what you are looking at is a chaotic representation of that language's parameters.

Thus, in this case, you won't find the recurrence of a specific letter or a histogram that gives any indication that these sentences are constructed in the language's order. Neither the character frequency, nor word count or sentence semantics make sense. In fact, the histogram is going to be quite uniform even though the individual counts might be high, according to the size of the total data;this is entropy in action. Thus, randomly sampling a stream of characters within a minimum window size (which can be a buffer of characters/words/sentences – any logical unit) will not give you any meaningful result.

This also means that more sets of characters are needed to represent anything resembling information. This measure, in numerical form after calibration for the dataset parameters,will be the entropy value for this data set.

CONSTRUCTION

Open Visual Studio and create a new Windows Forms solution. Name it ENTROPY. Right click the project pane and create a new user control class.You will see a blank area by default. It's your job from here on out to get it working.

The first thing to do is to resize the area to something you can work with – just an estimate, as we will use code that can resize it if needed (a fixed size is also possible).

Let us create an entropy class that can modularize the functionality of calculating from any source. This is my implementation of the Shannon entropy; it works just fine.

[csharp]

namespace ENTROPY

{

classEntropyC{

publicstaticstring GetEntropy(byte[] c)

{

int[] numArray = newint[0x100];

byte[] buffer = c;

for (int i = 0; i < 0x100; i++)//initialize each element to zero

{

numArray[i] = 0;

}

for (int j = 0; j < (buffer.Length - 1); j++) // histogram of each byte

{

int index = buffer[j];

numArray[index]++;

}

int length = buffer.Length;

floatentropy = 0f;

for (int k = 0; k < 0x100; k++)

{

if ((numArray[k] != 0) && (k != 0))

{

entropy += (-float.Parse(numArray[k].ToString()) / float.Parse(length.ToString())) * float.Parse(Math.Log((double) (float.Parse(numArray[k].ToString()) / float.Parse(length.ToString())), 2.0).ToString());

}

}

returnentropy.ToString();

}

}

}

[/csharp]

THE VISUALIZER

The bulk of the code for the visualizer involves running in the paint loop and using the Graphics object instance of the current display surface and calling methods from it that can perform various screen drawing functions.

I used Photoshop to give the bar its gradient background by exporting a .png file and setting the user control background to it. This is open to artistic creativity.

Once the visualizer is compiled, you will see a new control in the toolbox. Hereafter, you can drag and drop it in the main form and arrange it as you want.

The visualizer code is just elementary geometry stuff really. The setBuffer() method takes in a byte array and proceeds to take a histogram or distribution of the byte array. After which, it draws rectangles for each byte histogram value. The mouse events are handled in mouseLeave and mouseMove. For each probability value, which is the total Count of a particular value/total size of the input byte array (the input filesize in bytes), multiplication by 100 results in a percentage value, right? This value is used to calibrate the height of each horizontal bar in the entropy display for each byte value.

The main drawing classes that are used to emulate pen and paper: LinearGradientBrush/Font/SolidColor

The DrawString() method takes in a string and prints it on the screen at a specified location.

The DrawRectangle() is used to draw a thin rectangle to simulate a line.

Rectangle areas are stored in a data list generated in runtime to keep an in-memory record of the rectangle areas, so that the mouse hover interactions – called brushing, which can be combined with appropriate data from the same data set.

Point structures are used as-is, provided by the framework.

Invalidate() refreshes the screen and fires the paint event whenever a value is changed.

Another part of the display logic involves moving the byte count text in accordance with the cursor position in the bar for better interactivity. This is a simple set of arithmetic adjustments so that the histogram feels more responsive.

[csharp]

using System;

using System.ComponentModel;

using System.Drawing;

using System.Windows.Forms;

using System.Collections.Generic;

using System.Collections;

using System.Drawing.Drawing2D;

namespace ENTROPY

{

/// <summary>

/// Description of EntropyViewer.

/// </summary>

publicpartialclassEntropyViewer : UserControl

{

public EntropyViewer()

{

//

// The InitializeComponent() call is required for Windows Forms designer support.

//

InitializeComponent();

//

// TODO: Add constructor code after the InitializeComponent() call.

//

}

bool running = false;

List distribution = newList();

List distributionReal = newList();

publicvoid setBuffer(byte[] c)

{

byte[] buffer;

int[] counter = newint[256];

distribution.Clear();

buffer = c;

for (int i = 0; i < 256; i++)

{

counter[i] = 0;

}

for (int y = 0; y < buffer.Length; y++)

{

int tmp = (int)buffer[y];

counter[tmp]++;

}

int great = counter[0];

for (int h = 1; h < 256; h++)

{

if (great < counter[h])

{

great = counter[h];

}

}

for (int k = 0; k < 256; k++)

{

distribution.Add((counter[k] * 100) / (great));

}

for (int k = 0; k < 256; k++)

{

distributionReal.Add((counter[k] * 100) / (c.Length));

}

running = true;

Invalidate();

}

float graphDiv; float w = 0;

List _entRect = newList();

//List _drawList=new List();

struct<span style="color: #48b4d2;">distList

{

int distributionPercentage;

RectangleF r;

string Val;

PointF A;

PointF B;

internal distList(int a, RectangleF b, string c, PointF d, PointF e)

{

distributionPercentage = a;

r = b;

Val = c;

A = d;

B = e;

}

publicint DistList

{

get { return distributionPercentage; }

}

publicRectangleF Rectangle

{

get { return r; }

}

publicString ByteVal

{

get { return Val; }

}

publicPointF LineStart

{

get { return A; }

}

publicPointF LineEnd

{

get { return B; }

}

}

publicvoid LineWidth(int a)

{

lWd = a; Invalidate();

}

publicvoid fontSize(int a)

{

fWd = a; Invalidate();

}

int fWd = 9; //default

int lWd = 2; //default

PointF k; PointF ksub;

void EntropyViewerPaint(object sender, PaintEventArgs e)

{

if (running == true)

{

_entRect.Clear(); //_drawList.Clear();

graphDiv = ((float)this.Height - 1) / (float)256;

int cnt = 0;

LinearGradientBrush lb = newLinearGradientBrush(newPoint(0, 0), newPoint(0, 5), Color.Teal, Color.White);

lb.SetBlendTriangularShape(0.4f, 0.5f);

for (float h = 0; h < (float)this.Height; h += graphDiv)

{

//e.Graphics.DrawLine(new Pen(new SolidBrush(Color.SeaGreen),2),new PointF(h,(float)(this.Height-1)), new PointF(h,(float)this.Height-(distribution[cnt]*this.Height)/100));

e.Graphics.DrawLine(newPen(lb, lWd), newPointF((float)(this.Width - 1), h), newPointF(((float)this.Width - (distribution[cnt] * this.Width) / 100), h));

RectangleF r = newRectangleF(newPointF(0, h), newSizeF((float)(this.Width), h));

//RectangleF r2=new RectangleF(new PointF(0,h),new SizeF((float)(this.Width),lWd));

_entRect.Add(newdistList(distribution[cnt], r, (cnt.ToString("X2")), newPointF((float)(this.Width - 1), h), newPointF(((float)this.Width - (distribution[cnt] * this.Width) / 100), h)));

//_drawList.Add(new distList(distribution[cnt],r2,(cnt.ToString("X2")),

//new PointF((float)(this.Width-1),h),new PointF(((float)this.Width-(distribution[cnt]*this.Width)/100),h)

//));

if (cnt < 255) { cnt++; } else { break; }

}

if (OnDisp == true)
{ //for brushing info

if ((tmp.Height) ((float)((3 / 4) * this.Height)) && tmp.Height (this.Height - 30) && tmp.Height < (this.Height - 1)))

{

k = newPointF(1, this.Height - 50);

ksub = newPointF(1, this.Height - 37);

}

e.Graphics.DrawString(displayValue.Split(',')[0], newFont("Arial", 8), newSolidBrush(Color.White), k);

e.Graphics.DrawString(displayValue.Split(',')[1], newFont("Arial", 8), newSolidBrush(Color.White), ksub);

//e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.Yellow),lWd),new Rectangle(new Point((int)tmp2.Location.X,(int)tmp2.Location.Y),new Size((int)tmp2.Width,(int)tmp2.Height)));

LinearGradientBrush lb2 = newLinearGradientBrush(newPoint(0, 0), newPoint(0, 5), Color.Yellow, Color.Yellow);

lb.SetBlendTriangularShape(0.4f, 0.5f);

e.Graphics.DrawLine(newPen(lb2, 2), LineSta, EndSta);

//lb.Dispose();

}

//e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.Gray)),new Rectangle(new Point(0,0),new Size(this.ClientRectangle.Width-1,this.ClientRectangle.Height-1)));

e.Graphics.DrawString("0", newFont("Arial", fWd), newSolidBrush(Color.White), newPoint(0, 0));

SizeF s = e.Graphics.MeasureString("255", newFont("Arial", 9));

e.Graphics.DrawString("255", newFont("Arial", fWd), newSolidBrush(Color.White), newPoint(0, this.Height - (int)s.Height - 1));

}

else

{ //e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.Gray)),new Rectangle(new Point(0,0),new Size(this.ClientRectangle.Width-1,this.ClientRectangle.Height-1)));

e.Graphics.DrawString("0", newFont("Arial", 9), newSolidBrush(Color.White), newPoint(0, 0));

SizeF s = e.Graphics.MeasureString("255", newFont("Arial", 9));

e.Graphics.DrawString("255", newFont("Arial", 9), newSolidBrush(Color.White), newPoint(0, this.Height - (int)s.Height - 1));//this.Width-(int)s.Width-1

}

//e.Dispose();

}

PointF mm; string displayValue = String.Empty; bool OnDisp = false; RectangleF tmp; PointF LineSta; PointF EndSta;

void EntropyViewerMouseMove(object sender, MouseEventArgs e)

{

string val = String.Empty;

mm = newPointF((float)e.X, (float)e.Y);

foreach (distList j in _entRect)

{

if (j.Rectangle.Contains(mm) == true)

{ //chk for point in var md hence required

tmp = j.Rectangle; LineSta = j.LineStart; EndSta = j.LineEnd;

//val = j.DistList.ToString();

displayValue = j.DistList.ToString() + " % " + "," + j.ByteVal;

OnDisp = true;

}

Invalidate();

}

}

void EntropyViewerMouseLeave(object sender, EventArgs e)

{

OnDisp = false;

Invalidate();

}

}

}

[/csharp]

THE MAIN FORM:

This is the container form for putting the control above on a windows form. We could have directly used this form for the display as the above, but that would defeat the purpose of building a modular control, as the code here would be cumbersome to transfer to another form or project when required. Keeping the essential features only, we enable drag and drop code so that any file can be checked for entropy and byte distribution. This involves 3 steps – set the AllowDrop property in the property page for this form to true.

Set the DragEnter and DragDrop events from the events pane and Handle these events using code. Here, filedrop is a DataFormat type that is queried and converted to a string array. The first index – 0 is used to access the file path of the input file from the drag and drop operation. Finally, the DrawString Method is used to draw the entropy value from the byte buffer passed as an argument to the entropy class GetEntropyC() method, which returns a string representation of the floating point value. The length of the value is checked for > 4 and only the substring is extracted from a longer floating point string for display purposes.

[csharp]

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.IO;

using System.Drawing.Drawing2D;

namespace ENTROPY

{

publicpartialclassForm1 : Form

{

public Form1()

{

InitializeComponent();

this.Paint += newPaintEventHandler(Form1_Paint);

}

void Form1_Paint(object sender, PaintEventArgs e)

{

e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

e.Graphics.DrawString(EntropyValue, newFont("Arial", 44), newSolidBrush(Color.Blue), newPoint(25, this.Height/2 - 50));

}

privatevoid Form1_DragEnter(object sender, DragEventArgs e)

{

if (e.Data.GetDataPresent(DataFormats.FileDrop))

{

e.Effect = DragDropEffects.All;

}

else { e.Effect = DragDropEffects.None; }

}

string EntropyValue = "0.0";

privatevoid Form1_DragDrop(object sender, DragEventArgs e)

{

string[] f = (string[])e.Data.GetData(DataFormats.FileDrop);

byte[] b = File.ReadAllBytes(f[0]);

this.Text =Path.GetFileName(f[0]);

entropyViewer2.setBuffer(b);

EntropyValue = EntropyC.GetEntropy(b);

if (EntropyValue.Length > 4)

{

EntropyValue = EntropyValue.Substring(0, 4);

}

Invalidate();

}

privatevoid Form1_DragEnter_1(object sender, DragEventArgs e)

{

Form1_DragEnter(sender, e);

}

privatevoid Form1_DragDrop_1(object sender, DragEventArgs e)

{

Form1_DragDrop(sender,e)

}

}

}

[/csharp]

CONCLUSION:

We have seen how to quickly code and compile a custom user control for our daily programming purposes, be it security research quick fixes or the next CAD environment. The simplicity of C# can handle any graphics code with ease while incorporating user interactivity. The simple APIs and functions exposed in .NET under GDI+ involve wrapping a lot of complex interfaces (native API, WIN32 API, GDI32.DLL, USER32.DLL, WIN32K.SYS, GDI (old)/CLR Layer) in a user friendly programming API.

From here, you could build a directory entropy scanner, enabled with filetype detectors and their custom format parsers so that each type is searched for entropy reporting in a specific manner. For example, sections in a PE file or encryption key in a malware set. This could be further embellished with more graphs and algorithm choices; the sky is the limit.In the next part, we will build a little more involved graphing utility.

Victor Marak
Victor Marak

Victor SR Marak is a security researcher and electronic musician. He likes to make and break software. He was into music production prior to joining the AV industry recently. He plays the piano and loves composing music of all genres.His debut EP 'My Never Ending Wish' under moniker 'Emotion Coder' is out soon on Beatport Exclusive Releases on 30th October 2012. He can be reached at victormarak.wordpress.com