I still remember my first time reading AlephOne’s ‘Smashing the Stack for Fun and Profit’ – despite not having the proper knowledge to understand it at the time, it put the security bug in my head. It was truly a consciousness raising experience to get that first glimpse of my computer’s inner workings. One thing I did understand from it, loud and clear, is that bounds checking is a must when manipulating strings. Apparently, many programmers have not gotten the memo yet however, as a search on NVD for ‘stack overflow’ limited to just the past 3 months of published CVEs returns 29 matches! Whether you’re a developer, a tester, or a researcher, it is crucial to understand the principles of this category of attack. Much defensive advancement has been made in the form of OS and compiler mitigations but the best defense is to understand how to identify and eliminate the underlying programming errors. The focus of this blog then is to explore an example program which is ripe for stack based buffer overflow exploitation. The source code listing below describes a program which allows a user to initiate an Nmap TCP SYN scan of a specified target.
When compiled, this program will simply accept a single command line argument and then pass that argument to /usr/bin/nmap as a scan target. A compiled binary of this source code exists in the path.
As shown in the ‘ls’ output, the /usr/bin/synscan binary has the suid and sgid bits set and it is owned by root:root, which means that the execution context of synscan will have the effective permissions of root, so that the SYN scan can be initiated by any authenticated user. [Setting the sticky bit on nmap directly could allow all users to execute arbitrary commands as root.]
Unfortunately, for the administrator, there is a fatal flaw in the program logic, which can be exploited to gain a root shell. With a little knowledge regarding the stack, it should be clear that line 14 is ripe for exploitation. The use of sprintf() to copy the user-supplied hostname directly into a buffer on the stack is extremely dangerous. The destination buffer has 64 bytes allocated but no bounds checking is done to verify that argv[1] will fit in a 64 byte buffer. The C programming language leaves memory management up to the user making C as powerful as it is dangerous.
[Modern compilers actually do a lot to protect against memory corruption, but for the sake of this article we shall consider that the program is compiled without stack canaries and that the operating system does not provide other protection mechanisms. Exploration of these mitigation techniques is reserved for a future post.]