Thursday, March 1, 2018

Range finder with a Laser and a Webcam in Visual Basic 6.0

The author of this ingenious project is a scientist, namely Dr. Todd Danko. I found Dr. Danko to be connected with General Electric, Lockheed Martin and DARPA. Nice to have such people in the VB6 community. Like every VB6 programmer, it's fluent in C ++, Java and ASM. From here I will let his text explain the project. 


There are many off the shelf range finding components available including ultrasonic, infrared, and even laser rangefinders. All of these devices work well, but in the field of aerial robotics, weight is a primary concern. It is desirable to get as much functionality out of each component that is added to an air-frame. Miniature robotic rotor craft for example can carry about 100g of payload. It is possible to perform machine vision tasks such as obstacle identification and avoidance though the use of a webcam (or mini wireless camera interfaced to a computer via USB adapter). Better yet, two webcams can provide stereo machine vision thus improving obstacle avoidance because depth can be determined. The drawback of this of course is the addition of the weight of a second camera. This page describes how a mini laser pointer can be configured along with a single camera to provide mono-machine vision with range information.

Theory of Operation

The diagram below shows how projecting a laser dot onto a target that is in the field of view of a camera, the distance to that target may be calculated. The math is very simple, so this technique works very well for machine vision applications that need to run quickly.

So, here is how it works. A laser-beam is projected onto an object in the field of view of a camera. This laser beam is ideally parallel to the optical axis of the camera. The dot from the laser is captured along with the rest of the scene by the camera. A simple algorithm is run over the image looking for the brightest pixels. Assuming that the laser is the brightest area of the scene (which seems to be true for my dollar store laser pointer indoors), the dots position in the image frame is known. Then we need to calculate the range to the object based on where along the y axis of the image this laser dot falls. The closer to the center of the image, the farther away the object is.

As we can see from the diagram earlier in this section, distance (D) may be calculated:

Of course, to solve this equation, you need to know h, which is a constant fixed as the distance between your laser pointer and camera, and theta. Theta is calculated:

Put the two above equations together, we get:

OK, so the number of pixels from the center of the focal plane that the laser dot appears can just be counted from the image. What about the other parameters in this equation? We need to perform a calibration to derive these.

To calibrate the system, we will collect a series of measurements where I know the range to the target, as well as the number of pixels the dot is from the center of the image each time. This data is below:

Calibration Data
pixels from centeractual D (cm)

Using the following equation, we can calculate the actual angle based on the value of h as well as actual distance for each data point.

Now that we have a Theta_actual for each value, we can come up with a relationship that lets us calculate theta from the number of pixels from image center. I used a linear relationship (thus a gain and offset are needed). This seems to work well even though it does not account for the fact that the focal plane is a plane rather than curved at a constant radius around the center of the lens.

From my calibration data, I calculated:

Offset (ro) = -0.056514344 radians

Gain (rpc) = 0.0024259348 radians/pixel


I solved for calculated distances, as well as error from actual distance from the calibration data:

Actual and Calculated Range Data
pixels from centercalc D (cm)actual D (cm)% error


There are not a lot of parts in my sample range finder. I used a piece of cardboard to hold a laser pointer to a webcam so that the laser pointer points in a direction that is parallel to that of the camera. The parts seen below are laid out on a one inch grid for reference.

My assembled range finder looks like this:


I have written software two ways, one using visual c++ and the other using visual basic. You will probably find that the visual basic version of the software is much easier to follow than the vc++ code, but with everything, there is a tradeoff. The vc++ code can be put together for free (assuming that you have visual studio), while the vb code requires the purchase of a third party software package (also in addition to visual studio).

Visual Basic

The visual basic code that I have written is available as a package named at the bottom of this page. For this code to work, you will need the VideoOCX ActiveX component installed on your computer. The code that describes the functions found in the main form is seen below:

Private Sub exit_Click()
    ' only if running...
    If (Timer1.Enabled) Then
        Timer1.Enabled = False  'Stop Timer
    End If
End Sub

Private Sub Start_Click() 'Init VideoOCX Control, allocate memory and start grabbing
    If (Not Timer1.Enabled) Then
        Start.Caption = "Stop"
        ' Disable internal error messages in VideoOCX
        VideoOCX.SetErrorMessages False
        ' Init control
        If (Not VideoOCX.Init) Then
            ' Init failed. Display error message and end sub
            MsgBox VideoOCX.GetLastErrorString, vbOKOnly, "VideoOCX Error"
            ' Allocate memory for global image handle
            capture_image = VideoOCX.GetColorImageHandle
            ' result_image = VideoOCX_Processed.GetColorImageHandle
            Timer1.Enabled = True 'Start capture timer
            ' Start Capture mode
            If (Not VideoOCX.Start) Then
                ' Start failed. Display error message and end sub
                MsgBox VideoOCX.GetLastErrorString, vbOKOnly, "VideoOCX Error"
            End If
        End If
        Start.Caption = "Start"
        Timer1.Enabled = False  'Stop Timer
    End If
End Sub

Private Sub Timer1_Timer()
    ' Timer for capturing - handles videoOCXTools
    Dim matrix As Variant
    Dim height, width As Integer
    Dim r, c As Integer
    Dim max_r, max_c As Integer
    Dim max_red As Integer
    Dim gain, offset As Variant
    Dim h_cm As Variant
    Dim range As Integer
    Dim pixels_from_center As Integer
    ' Calibrated parameter for pixel to distance conversion
    gain = 0.0024259348
    offset = -0.056514344
    h_cm = 5.842
    max_red = 0
    ' Capture an image
    If (VideoOCX.Capture(capture_image)) Then
        ' VideoOCX.Show capture_image
        ' Matrix transformation initialization
        matrix = VideoOCX.GetMatrix(capture_image)
        height = VideoOCX.GetHeight
        width = VideoOCX.GetWidth
        ' Image processing code
        ' The laser dot should not be seen above the middle row (with a little pad)
        For r = height / 2 - 20 To height - 1
            ' Our physical setup is roughly calibrated to make the laser
            ' dot in the middle columns...dont bother lookng too far away
            For c = width / 2 - 25 To width / 2 + 24
                ' Look for the largest red pixel value in the scene (red laser)
                If (matrix(c, r, 2) > max_red) Then
                    max_red = matrix(c, r, 2)
                    max_r = r
                    max_c = c
                End If
            Next c
        Next r
        ' Calculate the distance for the laser dot from middle of frame
        pixels_from_center = max_r - 120

        ' Calculate range in cm based on calibrated parameters
        range = h_cm / Tan(pixels_from_center * gain + offset)

        ' Print laser dot position row and column to screen
        row_val.Caption = max_r
        col_val.Caption = max_c
        ' Print range to laser illuminated object to screen
        range_val.Caption = range
        ' Draw a red vertical line to intersect target
        For r = 0 To height - 1
            matrix(max_c, r, 2) = 255
        Next r
        ' Draw a red horizontal line to intersect target
        For c = 0 To width - 1
            matrix(c, max_r, 2) = 255
        Next c
        VideoOCX.ReleaseMatrixToImageHandle (capture_image)
    End If
    VideoOCX.Show capture_image

End Sub

Screen shots from this code can be seen below:

Visual C++

void CTripodDlg::doMyImageProcessing(LPBITMAPINFOHEADER lpThisBitmapInfoHeader)
 // doMyImageProcessing:  This is where you'd write your own image processing code
 // Task: Read a pixel's grayscale value and process accordingly

 unsigned int W, H;   // Width and Height of current frame [pixels]
 unsigned int    row, col;  // Pixel's row and col positions
 unsigned long   i;   // Dummy variable for row-column vector
 unsigned int max_row;  // Row of the brightest pixel
 unsigned int max_col;  // Column of the brightest pixel
        BYTE  max_val = 0;         // Value of the brightest pixel

 // Values used for calculating range from captured image data
 // these values are only for a specific camera and laser setup
 const double gain = 0.0024259348; // Gain Constant used for converting
      // pixel offset to angle in radians
 const double offset = -0.056514344; // Offset Constant
 const double h_cm = 5.842;  // Distance between center of camera and laser
        double  range;          // Calculated range 
 unsigned int pixels_from_center; // Brightest pixel location from center
      // not bottom of frame
 char  str[80];         // To print message
 CDC  *pDC;   // Device context need to print message

        W = lpThisBitmapInfoHeader->biWidth; // biWidth: number of columns
        H = lpThisBitmapInfoHeader->biHeight; // biHeight: number of rows
 for (row = 0; row < H; row++) {
  for (col = 0; col < W; col++) {

   // Recall each pixel is composed of 3 bytes
   i = (unsigned long)(row*3*W + 3*col);
   // If the current pixel value is greater than any other, 
                        // it is the new max pixel
   if (*(m_destinationBmp + i) >= max_val) 
    max_val = *(m_destinationBmp + i);
    max_row = row;
    max_col = col;
 // After each frame, reset max pixel value to zero
        max_val = 0;

 for (row = 0; row < H; row++) {
  for (col = 0; col < W; col++) {

   i = (unsigned long)(row*3*W + 3*col);
   // Draw a white cross-hair over brightest pixel in the output display
   if ((row == max_row) || (col == max_col)) 
    *(m_destinationBmp + i) = 
    *(m_destinationBmp + i + 1) = 
    *(m_destinationBmp + i + 2) = 255; 

 // Calculate distance of brightest pixel from center rather than bottom of frame
        pixels_from_center = 120 - max_row;

 // Calculate range in cm based on bright pixel location, and setup specific constants
 range = h_cm / tan(pixels_from_center * gain + offset);

 // To print message at (row, column) = (75, 580)
 pDC = GetDC(); 

 // Display frame coordinates as well as calculated range
 sprintf(str, "Max Value at x= %u, y= %u, range= %f cm    ",max_col, max_row, range);
 pDC->TextOut(75, 580, str);

My complete code for this project is available as a package named at the bottom of this page.  Note, to run this executable, you will need to have both qcsdk and the qc543 driver installed on your computer.  Sorry, but you are on your own to find both of these. Below are two examples of the webcam based laser range finder in action. Note how it looks like there are two laser dots in the second example. This "stray light" is caused by internal reflections in the camera. The reflected dot loses intensity as it bounces within the camera so it does not interfere with the algorithm that detects the brightest pixel in the image.

Future Work

One specific improvement that can be made to this webcam based laser range finder is to project a horizontal line rather than a dot onto a target. This way, we could calculate the range for each column of the image rather than just one column. Such a setup would be able to be used to locate areas of maximum range as places that a vehicle could steer towards. Likewise, areas of minimum range would be identified as obstacles to be avoided.


Quick Sort, Selection Sort and Bubble Sort algorithm in VB6

Here we have an application that measures execution times for the three sorting algorithms: Quick Sort, Selection Sort and Bubble Sort.  A visual interface shows what these algorithms do in real time. The implementation is made in algorithm in Visual Basic 6.0 and the source code is shown below:

Download from me

The quicksort algorithm was developed in 1959 by Tony Hoare while in the Soviet Union, as a visiting student at Moscow State University. At that time, Hoare worked in a project on machine translation for the National Physical Laboratory. As a part of the translation process, he needed to sort the words of Russian sentences prior to looking them up in a Russian-English dictionary that was already sorted in alphabetic order on magnetic tape. After recognizing that his first idea, insertion sort, would be slow, he quickly came up with a new idea that was Quicksort. He wrote a program in Mercury Autocode for the partition but couldn't write the program to account for the list of unsorted segments. On return to England, he was asked to write code for Shellsort as part of his new job. Hoare mentioned to his boss that he knew of a faster algorithm and his boss bet sixpence that he didn't. His boss ultimately accepted that he had lost the bet. Later, Hoare learned about ALGOL and its ability to do recursion that enabled him to publish the code in Communications of the Association for Computing Machinery, the premier computer science journal of the time.

Quicksort gained widespread adoption, appearing, for example, in Unix as the default library sort subroutine. Hence, it lent its name to the C standard library subroutine qsort and in the reference implementation of Java.

Robert Sedgewick's Ph.D. thesis in 1975 is considered a milestone in the study of Quicksort where he resolved many open problems related to the analysis of various pivot selection schemes including Samplesort, adaptive partitioning by Van Emden as well as derivation of expected number of comparisons and swaps. Bentley and McIlroy incorporated various improvements for use in programming libraries, including a technique to deal with equal elements and a pivot scheme known as pseudomedian of nine, where a sample of nine elements is divided into groups of three and then the median of the three medians from three groups is chosen. Jon Bentley described another simpler and compact partitioning scheme in his book Programming Pearls that he attributed to Nico Lomuto. Later Bentley wrote that he used Hoare's version for years but never really understood it but Lomuto's version was simple enough to prove correct. Bentley described Quicksort as the "most beautiful code I had ever written" in the same essay. Lomuto's partition scheme was also popularized by the textbook Introduction to Algorithms although it is inferior to Hoare's scheme because it does three times more swaps on average and degrades to O(n2) runtime when all elements are equal.

In 2009, Vladimir Yaroslavskiy proposed the new dual pivot Quicksort implementation. In the Java core library mailing lists, he initiated a discussion claiming his new algorithm to be superior to the runtime library’s sorting method, which was at that time based on the widely used and carefully tuned variant of classic Quicksort by Bentley and McIlroy. Yaroslavskiy’s Quicksort has been chosen as the new default sorting algorithm in Oracle’s Java 7 runtime library after extensive empirical performance tests.

Selection sort is a sorting algorithm, specifically an in-place comparison sort. It has O(n2) time complexity, making it inefficient on large lists, and generally performs worse than the similar insertion sort. Selection sort is noted for its simplicity, and it has performance advantages over more complicated algorithms in certain situations, particularly where auxiliary memory is limited.

The algorithm divides the input list into two parts: the sublist of items already sorted, which is built up from left to right at the front (left) of the list, and the sublist of items remaining to be sorted that occupy the rest of the list. Initially, the sorted sublist is empty and the unsorted sublist is the entire input list. The algorithm proceeds by finding the smallest (or largest, depending on sorting order) element in the unsorted sublist, exchanging (swapping) it with the leftmost unsorted element (putting it in sorted order), and moving the sublist boundaries one element to the right.

Bubble sort, sometimes referred to as sinking sort, is a simple sorting algorithm that repeatedly steps through the list to be sorted, compares each pair of adjacent items and swaps them if they are in the wrong order. The pass through the list is repeated until no swaps are needed, which indicates that the list is sorted. The algorithm, which is a comparison sort, is named for the way smaller or larger elements "bubble" to the top of the list. Although the algorithm is simple, it is too slow and impractical for most problems even when compared to insertion sort. It can be practical if the input is usually in sorted order but may occasionally have some out-of-order elements nearly in position.


Public Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)

Public Sub QuickSort(vArray As Variant, L As Long, R As Long)
  '             Array            , LBound()     , UBound()
  Dim i As Long
  Dim j As Long
  Dim X
  Dim Y

  i = L
  j = R
  X = vArray((L + R) / 2)

  Do While (i <= j)
    Do While (vArray(i) < X And i < R)
      i = i + 1

    Do While (X < vArray(j) And j > L)
      j = j - 1

    If (i <= j) Then
      Y = vArray(i)
      vArray(i) = vArray(j)
      vArray(j) = Y
      i = i + 1
      j = j - 1
    End If

  If (L < j) Then QuickSort vArray, L, j
  If (i < R) Then QuickSort vArray, i, R
End Sub

Public Sub QuickSort33(vArray As Variant, AccordingTo As Integer, Dimension2Size As Integer, L As Integer, R As Integer)
  '   name of array,   sorting according to which dimension?,   size of second dimension,   lbound(),   ubound()
  Dim a As Integer, i As Integer, j As Integer  ' counters
  Dim X, Y, z   ' temporary values

  i = L
  j = R
  X = vArray((L + R) / 2, AccordingTo)
  Do While (i <= j)
    Do While (vArray(i, AccordingTo) < X And i < R)
      i = i + 1
    Do While (X < vArray(j, AccordingTo) And j > L)
      j = j - 1
    If (i <= j) Then
      Y = vArray(i, AccordingTo)
      vArray(i, AccordingTo) = vArray(j, AccordingTo)
      vArray(j, AccordingTo) = Y
      For a = 0 To AccordingTo - 1
        z = vArray(i, a)
        vArray(i, a) = vArray(j, a)
        vArray(j, a) = z
      Next a
      For a = AccordingTo + 1 To Dimension2Size
        z = vArray(i, a)
        vArray(i, a) = vArray(j, a)
        vArray(j, a) = z
      Next a
      i = i + 1
      j = j - 1
    End If

  If (L < j) Then QuickSort33 vArray, AccordingTo, Dimension2Size, L, j
  If (i < R) Then QuickSort33 vArray, AccordingTo, Dimension2Size, i, R
End Sub

Public Sub SelectionSort(vArray, L As Integer, R As Integer)
'    name of array,    lbound(),    ubound()
Dim i As Integer
Dim j As Integer
Dim best_value  ' smallest value in array
Dim best_j As Integer
    ' loop from left to right
    For i = L To R - 1
        ' initialize lowest value
        best_value = vArray(i)
        best_j = i  ' initialize lowest value array location
        For j = i + 1 To R
            ' find the lowest value in the array in this loop
            If vArray(j) < best_value Then
                best_value = vArray(j)
                best_j = j
            End If
        Next j
        ' put the smallest value at the from (left) of the array
        ' and put the value on the left of the array in the smallest
        ' value's previous position
        vArray(best_j) = vArray(i)
        vArray(i) = best_value
    Next i
End Sub

Public Sub QuickSortBars(vArray As Variant, L As Integer, R As Integer, Optional SleepTime As Long = 0)
  Dim i As Integer    ' counter
  Dim j As Integer    ' counter
  Dim BarVal1         ' temporary bar value
  Dim BarVal2         ' temporary bar value

  i = L
  j = R
  BarVal1 = vArray((L + R) / 2)

  Do While (i <= j)
    If SleepTime > 0 Then
      Sleep SleepTime
    End If
    Do While (vArray(i) < BarVal1 And i < R)
      i = i + 1

    Do While (BarVal1 < vArray(j) And j > L)
      j = j - 1

    If (i <= j) Then
      BarVal2 = vArray(i)
      vArray(i) = vArray(j)
      vArray(j) = BarVal2
      frmMain.Bar(i).Value = vArray(i)
      frmMain.Bar(j).Value = vArray(j)
      i = i + 1
      j = j - 1
    End If

  If (L < j) Then QuickSortBars vArray, L, j, SleepTime
  If (i < R) Then QuickSortBars vArray, i, R, SleepTime
End Sub

Public Sub SelectionSortBars(vArray, L As Integer, R As Integer, Optional SleepTime As Long = 0)
  '    name of array,    lbound(),    ubound()
  Dim i As Integer    ' counter
  Dim j As Integer    ' counter
  Dim best_value  ' smallest value in array
  Dim best_j As Integer

  ' loop from left to right
  For i = L To R - 1
    If SleepTime > 0 Then
      Sleep SleepTime
    End If
    ' initialize lowest value
    best_value = vArray(i)
    best_j = i  ' initialize lowest value array location
    For j = i + 1 To R
      ' find the lowest value in the array in this loop
      If vArray(j) < best_value Then
        best_value = vArray(j)
        best_j = j
      End If
    Next j
    ' put the smallest value at the from (left) of the array
    ' and put the value on the left of the array in the smallest
    ' value's previous position
    vArray(best_j) = vArray(i)
    vArray(i) = best_value
    frmMain.Bar(best_j) = vArray(best_j)
    frmMain.Bar(i) = vArray(i)
  Next i
End Sub


Thursday, February 1, 2018

Http Recon Security Scanner – Advanced Web Server Fingerprinting


HTTP Recon is an advanced web server fingerprinting system made in Visual Basic 6.0. We are accustomed to advanced VB6 projects, however, few are open source and most of them are sold for impressive amounts of money. Here we have to thank a security engineer named Marc Ruef. The httprecon project is doing some research in the field of web server fingerprinting, also known as http fingerprinting. The goal is the highly accurate identification of given httpd implementations. This is very important within professional vulnerability analysis.
Besides the discussion of different approaches and the documentation of gathered results also an implementation for automated analysis is provided. This software shall improve the easyness and efficiency of this kind of enumeration. Traditional approaches as like banner-grabbing, status code enumeration and header ordering analysis are used. However, many other analysis techniques were introduced to increase the possibilities of accurate web server fingerprinting. Some of them were already discussed in the book Die Kunst des Penetration Testing (Chapter 9.3, HTTP-Fingerprinting, pp. 530-550).

Here is the source code:


The application works very straight forward. After the user has defined the target service which shall be fingerprinted, a common tcp connection is opened to the destination port. If the connection could be established, the http requests are sent to the target service. This one will shall react with responses. These could be dissected to identify some specific fingerprint elements. Those elements are looked up in the local fingerprint database. If there is a match, the according implementation is flagged as "identified". All these flags were counted so httprecon is able to determine which implementation has the best match rate.


The following picture illustrates the architecture of the whole framework. The scan engine uses nine different requests which are sent to the target web server. These shall provoke the response which can be used for the fingerprinting. There were different kind of requests used. Some of them are very common and legitimate (e.g. GET / HTTP/1.1) and others are usually not accepted due to their malicious nature (e.g. a very long URI in a GET request).

The dissection of the responses is handled by the parsing and fingerprint engine. As you can see many different fingerprint elements are looked up (e.g. statuscode, banner, Etag length, header-order, etc.). These elements are saved in the local fingerprint database which allows the sum of the matches. All data is correlated which will result in the final fingerprint scan report.


These are the main features of the current implementation of httprecon which makes this solution better than other tools of this kind:
  • Many test-cases: There are nine test-cases possible
  • HTTPS/SSL support: Secure web servers can be tested too
  • Advanced result analysis: Different methods for the analysis of results is provided
  • Many fingerprint details: The analysis is based on many fingerprint elements
  • Plaintext Database: The fingerprint data is saved in a file-based plaintext database
  • Fingerprint Wizard: Fingerprints can be saved and updated within the GUI
  • IDS evasion mechanism: The configuration settings allow to use IDS evasion mechanisms
  • Reporting: XML, HTML and TXT reporting is provided for professional testers
  • Autoupdate: An autoupdate feature informs about new releases
  • Open-source (GPLv3): Everyone can improve the application for themselves
There are differen applications for http fingerprinting available. This Excel sheet is comparing the four most popular HTTP fingerprinting tools (httprecon, httprint, hmap, and WebserverFP).

Key Analysis Index

Most web server implementations come with a Key Analysis Index (KAI), a very special and dominant behaviour which allows a very quick identification. The following list shall demonstrate the KAI for some popular implementations:
  • Apache: Every generation of Apache web servers usually introduces these three values first in an http response header: Date, Server, and X-Powered-By (optional). The length of the ETag values varies between 17 and 34 bytes and they are usually surrounded by double-quotes. It is very typical for an Apache installation to announce PHP/x.x.x within the X-Powered-By line (it is also common for Abyss). It is also common that an Apache web server reacts with the statuscode 403 (Forbidden) if a very long URI was proposed within the request. Usually the supported http methods are announced as: GET, HEAD, POST, OPTIONS, and TRACE.
  • Microsoft IIS: The length of the ETag values varies between 18 and 23 bytes. This web server is the only one so far which is announcing ASP.NET within the X-Powered-By line.
  • Oracle Application Server: The length of the ETag values varies between 15 and 30 bytes and they are usually surrounded by double-quotes. Usually the supported http methods are announced as: OPTIONS, TRACE, GET, and HEAD. In some cases also an additional line similar to Allow is used and defined as Public.
  • Sun One Web Server: The implemenation by Sun Microsystems Inc. usually starts with the values Server, {Date|Content-type}.
  • Netscape Enterprise Server: This implementation usually uses these three values within a response header: Server, Date, and Content-type.
  • Compaq HTTP Server: Old implementations of the generation 5.x always propose HTTP/1.0 instead of HTTP/1.1 as protocol. A very special behaviour is the statustext "Ok" instead of full capitalized "OK" for a successful processing. They also use uncapitalized letters is a response line uses some dash (e.g. Content-type and Content-length). And the response header always consists of: Date, Server, Content-type, Content-length, and Set-Cookie. A Compaq HTTP Server sends the http statuscode 200 (Ok) even a very long URI was proposed within the request (also common for LANCOM DSL router).
  • Zyxel: The embedded web server of Zyxel devices proposes usually the same http response header structure: Content-Type, Date, Pragma, Expires, Transfer-Encoding, Server, and EXT. Very special in this case is the header line EXT.
  • 4D WebSTAR: Versions prior 4.x always announce the MIME-Version as first element of the http response header. The version 4.x do not use this value anymore and rely on Server as first element. And in the later releases 5.x the Date announcement moved the Server announcement to the second line. A request for a non-existing ressource returns the statuscode "File Not Found" instead of the more common "Not Found".
  • Roxen: The length of the ETag values is always set to 34 bytes and they are usually surrounded by double-quotes.
  • OmniHTTPd: Another special behaviour for successful requests is the status text "Document Follows" (similar to TclHttpd) where usually an "OK" is used. The response headers usually contain the values Content-Length, Content-Type, Date and the header is ended by the value Server.
  • TclHttpd: Another special behaviour for successful requests is the status text "Data follows" (similar to OmniHTTPd) where usually an "OK" is used.
  • Gatling: A very special behaviour for successful requests is the status text "Coming Up" where usually an "OK" is used.
  • Squid: Common http get requests always produce the announcement of HTTP/1.0 instead of HTTP/1.1 as protocol.


The possibility of fingerprinting is not a vulnerability in a traditional way which allows to compromise a host. It is more a flaw or exposure which may provide the foundation for further enumeration and specific attack scenarios.

Nevertheless, applying some counter-measures to harden a service is always a good idea. Preventing fingerprinting 100 % is not possible due to the nature of interaction between network software. But there are possibilities to decrease the accuracy of such an analysis. These are illustrated in the diagram and listed below:

Change or supression of banner

The most accepted and widely known approach to defend against fingerprinting is the manipulation or change of the application banner. Within web responses the line Server announces the name of the given implementation. Some web servers allow the change of this value within a configuration file.
Apache supports downstripping the announcement with the ServerToken directive. Downstripping requires the definition of Prod which would announce "Apache" only (see the ServerSignature directive too). To change this value really some manipulation of the file /src/include/httpd.h within the source-code (AP_SERVER_BASEVENDOR, AP_SERVER_BASEPRODUCT, AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER, AP_SERVER_PATCHLEVEL_NUMBER) is required:
* The below defines the base string of the Server: header. Additional
* tokens can be added via the ap_add_version_component() API call.
* The tokens are listed in order of their significance for identifying the
* application.
* "Product tokens should be short and to the point -- use of them for 
* advertizing or other non-essential information is explicitly forbidden."
* Example: "Apache/1.1.0 MrWidget/0.1-alpha" 
#define AP_SERVER_BASEVENDOR "Apache Software Foundation"
#define AP_SERVER_BASEPROJECT "Apache HTTP Server"


#define AP_SERVER_ADD_STRING     "-dev"
#define AP_SERVER_ADD_STRING     ""
Microsoft IIS requires some hex hack in the library W3SVC.DLL to change the Server-output. There is a freeware named MS IIS/PWS Banner Edit Tool available which automates this manipulation. IISBanner is a well-known ISAPI filter which can be used to safely remove or disguise the IIS server header by editing the INI file. Microsoft suggests the use of URLscan which introduces the same advantages.
thttpd allows some minor changes within the file config.h which steers some of the settings during compilation (e.g. ERR_APPEND_SERVER_INFO for the announcement of the server name within server generated error pages or the default charset of iso-8859-1 in DEFAULT_CHARSET). Furthermore it is possible to change SHOW_SERVER_VERSION which suppresses the version number announcement in the Server line. To change or suppress the real server name a modification of EXPOSED_SERVER_SOFTWARE within libhttpd.c is required:
The open-source web server fnord does not support any configuration settings or constant mutation within the source code to modify the application behavior easily. The announcement of the web server as FNORD is realized within the separated replies created by buffer_puts() in httpd.c. This includes the application banner, status messages and header order. However, enhanced search and replace modifications might improve the obscurity without touching the architecture of the application. Further improvements as like introduction of new http methods (by default only GET, POST and HEAD are suppoted in version 1.10) require some deeper modifications.
Some modules (e.g. PHP and SSH) announce themselves within the Server line. In most cases this can be prevented with a configuration setting for the according module. For PHP in the file php.ini the value expose_php must be set to Off.

Change statuscode and statustext

Web servers include implementation dependent statuscodes and statustexts in their responses. Changeing them prevents most of todays web server fingerprinting. Only a few http daemons allow such change of basic behaviour within run-time configuration.
Apache requires some changes within the source code and re-compilation. In /src/include/httpd.h the statuscodes are defined as integer constants:
* The size of the static array in http_protocol.c for storing
* all of the potential response status-lines (a sparse table).
* A future version should dynamically generate the apr_table_t at startup.

#define HTTP_CONTINUE 100
#define HTTP_OK 200
#define HTTP_CREATED 201
#define HTTP_ACCEPTED 202
#define HTTP_NO_CONTENT 204
#define HTTP_SEE_OTHER 303
#define HTTP_USE_PROXY 305
#define HTTP_BAD_REQUEST 400
#define HTTP_FORBIDDEN 403
#define HTTP_NOT_FOUND 404
#define HTTP_CONFLICT 409
#define HTTP_GONE 410
#define HTTP_LOCKED 423
#define HTTP_BAD_GATEWAY 502
And in /src/modules/http/http_protocol.c the statustexts are defined as string constants:
static const char * const status_lines[RESPONSE_CODES] =
    "100 Continue",
    "101 Switching Protocols",
    "102 Processing",
#define LEVEL_200 3
    "200 OK",
    "201 Created",
    "202 Accepted",
    "203 Non-Authoritative Information",
    "204 No Content",
    "205 Reset Content",
    "206 Partial Content",
    "207 Multi-Status",
#define LEVEL_300 11
    "300 Multiple Choices",
    "301 Moved Permanently",
    "302 Found",
    "303 See Other",
    "304 Not Modified",
    "305 Use Proxy",
    "306 unused",
    "307 Temporary Redirect",
#define LEVEL_400 19
    "400 Bad Request",
    "401 Authorization Required",
    "402 Payment Required",
    "403 Forbidden",
    "404 Not Found",
    "405 Method Not Allowed",
    "406 Not Acceptable",
    "407 Proxy Authentication Required",
    "408 Request Time-out",
    "409 Conflict",
    "410 Gone",
    "411 Length Required",
    "412 Precondition Failed",
    "413 Request Entity Too Large",
    "414 Request-URI Too Large",
    "415 Unsupported Media Type",
    "416 Requested Range Not Satisfiable",
    "417 Expectation Failed",
    "418 unused",
    "419 unused",
    "420 unused",
    "421 unused",
    "422 Unprocessable Entity",
    "423 Locked",
    "424 Failed Dependency",
/* This is a hack, but it is required for ap_index_of_response
* to work with 426.
    "425 No code",
    "426 Upgrade Required",
#define LEVEL_500 46
    "500 Internal Server Error",
    "501 Method Not Implemented",
    "502 Bad Gateway",
    "503 Service Temporarily Unavailable",
    "504 Gateway Time-out",
    "505 HTTP Version Not Supported",
    "506 Variant Also Negotiates",
    "507 Insufficient Storage",
    "508 unused",
    "509 unused",
    "510 Not Extended"
An easier way to change the status reaction for specific request types (e.g. unsupported http methods as like DELETE) is the use of re-write rules. Instead of react with the expected error message 405 a less usefull forwarding to a 404 error site is possible. Most web servers support such definitions within .htaccess files. The following example is redirecting the unwanted requests to 403 Forbidden instead to 405 Method Not Allowed if the Apache web server has mod_rewrite enabled:
RewriteRule .* - [F]

Change header-values and order

Some web server fingerprinting tools regard header values and header order. Changeing this within the web server usually requires some deep impact to the source code too. This requires a very high level of understanding the given application. The rate of errors might be very high with such an intrusive change.
In Microsoft IIS new custom header values can be added which changes the overview fingerprint of the header order. Just by adding one new header line (usually web browsers ignore those which start with X, e.g. X-Garbage) the possibilities of successful fingerprints can be reduced. This is possible very easily in the tab HTTP headers in the web site properties. Those can be reached within the context menu of the according web site in the Internet Information Services (IIS) Manager. In some cases it is possible to overwrite some other header values (e.g. the location in 302 moved messages). However, this is not possible for the Server banner itself.
Add Custom HTTP Header in MS IIS
However, some scripting languages as like PHP allow the web developer to have some influence to the headers with the function header(). For example a new header with the call header("X-Powered-By: ASP.NET 2.0") can be used althought no ASP.NET is used at all. This compromises the fingerprint analysis, especially if it is very static and pattern-based, in any way. In ASP.NET the function Response.AppendHeader() is used for the same purposes.
And in JSP different methods of the response object defined by the javax.servlet.http.HttpServletResponse interface might be used: response.setHeader() to set a header value, response.addHeader() to add a new header value, response.setIntHeader() to set an header with an integer value and response.setDateHeader() to set a header with a date value (e.g. from System.currentTimeMillis()).
The ColdFusion Markup Language (CFML) uses the tag cfheader to define headers and their values. Status codes can be changed with a statement like and new header lines introduced with a statement like .

Redirect known attack scripts

Another way of defending against fingerprinting utilities is to redirect attack scripts as like httprecon. Within the following .htaccess example the well-known user-agents are detected and redirected to the attackers own computer:
RewriteCond %{HTTP_USER_AGENT} ^Nikto [OR]
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/4.75 [OR]
RewriteCond %{HTTP_USER_AGENT} ^httprecon
RewriteRule ^(.*)$ http://%{REMOTE_HOST}:80 [R=301,L]
This introduces several advantages. First, the attacker is consuming more of his resources which might slow down the scan approach. Second, most of the attack scripts do not recognize the redirect and think the final destination host - which is the attackers own computer - shall be fingerprinted. Thus, in some cases wrong results might be gathered.
However, this blacklist technique only works as long as the attack scripts are detected properly. If the attacker is going to change the approach and behavior of the scanning software, no further redirection might be possible.

Httprecon is a http fingerprinting tool for Windows. The results are based on the analysis of 9 requests (see detailed requests below):
  1. GET existing
  2. GET long request
  3. GET non-existing
  4. GET wrong protocol
  5. HEAD existing
  6. OPTIONS common
  7. DELETE existing
  8. TEST method
  9. Attack Request

Detailed tests

Here is the list of the requests (with default options) sent by the tool:

GET existing

GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Connection: Keep-Alive
Cache-Control: no-cache

GET long request

GET /nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn HTTP/1.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Connection: Keep-Alive
Cache-Control: no-cache

GET non-existing

GET /dX6cZ6.html HTTP/1.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Connection: Keep-Alive
Cache-Control: no-cache

GET wrong protocol

GET / HTTP/9.8
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Connection: Keep-Alive
Cache-Control: no-cache

HEAD existing

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache

OPTIONS common

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache

DELETE existing

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache

TEST method

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache

Attack Request

GET /etc/passwd?format=%%&xss="><script>alert('xss');</script>&traversal=../../&sql='%20OR%201; HTTP/1.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv: Gecko/20071127 Firefox/
Connection: Keep-Alive
Cache-Control: no-cache


Our tests have been run against an Apache 2.2.17 installed on a Debian 5 box. Here is the full signature on the server:
$ /usr/local/apache2/bin/httpd -V
Server version: Apache/2.2.17 (Unix)
Server built:   Dec 27 2010 20:54:46
Server's Module Magic Number: 20051115:25
Server loaded:  APR 1.4.2, APR-Util 1.3.10                                                           
Compiled using: APR 1.4.2, APR-Util 1.3.10                                                           
Architecture:   32-bit                                                                               
Server MPM:     Prefork                                                                              
  threaded:     no                                                                                   
    forked:     yes (variable process count)                                                         
Server compiled with....                                                                             
 -D APACHE_MPM_DIR="server/mpm/prefork"                                                              
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D HTTPD_ROOT="/usr/local/apache2"
 -D SUEXEC_BIN="/usr/local/apache2/bin/suexec"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_LOCKFILE="logs/accept.lock"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

Trivia and Fun Stuff

During the development of httprecon and the use of the software in professional penetration tests several funny things could be observed:
  • Wordpress is using the header "X-nananana: Batcache" (@ChrisJohnRiley) (09/22/2010)
  • Just one of the major banks in Switzerland is deleting the Server line entirely. (04/25/2009)
  • The host has a mispelled header line which reads "epKe-Alive" instead of "Keep-Alive". (04/16/2009)
  • A popular swiss travel agency defined the Server line as "Game Cube" which is abviously not true. (11/24/2007)