Cisco ASA调试可分为硬件调试和虚拟化调试,其中硬件调试方法可以支持所有版本的ASA系统,虚拟化调试目前只支持ASA 802和ASA 842两个版本,主要原因是只有这两个版本的系统支持虚拟化环境,下面分开讲解两种调试方法。




一、虚拟化调试

Cisco ASA 802:

binwalk -e asa802-k8.bin

1.jpg

解压的文件中只留下1228B0:

# rm -rf !(1228B0)

cpio解压文件:

cpio -i --make-directories < 1228B0  --no-absolute-filenames

# rm 1228B0

2.jpg


vim编辑etc/init.d/rcS文件,将最后一行注释后,改为/bin/sh

3.jpg


将vmlinuz文件copy出来作为GNS3中的核启动文件,然后把此目录下所有文件用cpio打包为gz文件

4.jpg

cp vmlinuz ../asa802-k8.kernel

find . | cpio -o -H newc | gzip -9 > ../asa802-k8.gz

将这两个文件导入到GNS3中,其中qemu使用0.13.0版本,Network type选择pcnet,并勾选是用legacy网络模式。高级设置如下图所示:

5.jpg


其中Kernel命令为: console=ttyS0,9600n8 bigphysarea=16384 auto nousb ide1=noprobe hda=980,16,32

Options: -hdachs 980,16,32 -vnc :1 -serial telnet:192.168.75.1:1111,server,nowait

注意:Options中的串口设置需要和gns3 server的IP地址一致。

配置好后启动,发现进入了ASA底层Linux系统:

6.jpg


Cisco ASA防火墙的整个加载流程如下图:

2.jpg


在这里我们更加关心的将是最后三个步骤,rcS作为一个shell脚本控制着lina_monitor的启动方式,如传入的参数控制,lina_monitor以创建子进程的方式启动lina程序,并对lina进程进行监视和管控。其中lina就是Cisco ASA防火墙的主程序,基本包含了ASA所有的业务逻辑代码,在系统内存中,linafork多个子进程协同工作,如下图所示:

3.jpg


对PID为221的进程进行调试,实验证明不能直接使用lina_monitor开启gdbserver,gdbserver会被attach到lina的子进程上,无法准确的接收到信号,需要在lina已经启动后,在用gdbserver attach到相关进程上,通过自己编写的程序辅助实现,代码如下:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUF_SIZE 10000

char *cmd = \
"en\n"
"\n"
"conf t\n"
"int e0/0\n"
"nameif outside\n"
"ip addr %s 255.255.255.224\n"
"no shut\n"
"exit\n"
"router rip\n"
"network 10.0.0.33\n"
"exit\n"

"snmp-server host outside 10.0.0.2 community public\n"
"snmp-server community public\n"
"snmp-server enable traps syslog\n"
"route outside 0 0 10.0.0.33\n"
"end\n"
"ping 10.0.0.2\n";

int main(int argc, char *argv[])
{
int i;
int pid;
int exitVal;
int rdPipe[2], wrPipe[2];
char *pBuf = NULL;
char *confBuf = NULL;
FILE *cmdResults = NULL;

confBuf = (char *)malloc(2000);
memset(confBuf, 0x00, 2000);

sprintf(confBuf, cmd, argv[1]);

printf("[+] cmdline: ");
for(i=0; i<argc; i++)
{
printf("%s ", argv[i]);
}
puts("");

//create pipe
if(pipe(rdPipe)!=0 || pipe(wrPipe)!=0)
{
printf("[-] Create pipe failed.\n");
return -1;
}

pid = fork();
if(pid == 0)
{
close(rdPipe[0]);
close(wrPipe[1]);

//dup2(rdPipe[1], STDOUT_FILENO);
dup2(wrPipe[0], STDIN_FILENO);

pid = fork();
if(pid == 0)
{
printf("[+] launch lina...\n");
execl("/asa/bin/lina", "/asa/bin/lina", "-s", NULL);
}

exit(0);
}

wait(&exitVal);

close(rdPipe[1]);
close(wrPipe[0]);

usleep(1000*1000*7);
printf("%s\n", confBuf);
write(wrPipe[1], confBuf, strlen(confBuf));

usleep(1000*1000*5);

pBuf = (char *)malloc(BUF_SIZE);
memset((void *)pBuf, NULL, BUF_SIZE);

cmdResults = popen("ls -l /proc/", "r");
fread(pBuf, sizeof(char), BUF_SIZE - 1, cmdResults);
printf("%s", pBuf);

fclose(cmdResults);
free(pBuf);
free(confBuf);

execl("/bin/gdbserver", "/bin/gdbserver", "/dev/ttyS1", "--attach", "222", NULL);


/*-------------------------------------------------------------------------------------------------*/

return 0;
}




编译命令为:gcc -o launcher launcher.c -Wl,--hash-style=sysv


ASA 842:

1.制作vmlinuz文件:

先将.bin文件导出为十六进制文件:

4.jpg


在文件中查找关键字“booting”:

5.jpg

定位到相应的行:

6.jpg

将上图所示的特征行作为vmlinuz文件的起始地址,记录下此地址。在从asa.hex文件中查找关键字“rootfs.img”:

7.jpg

定位到相应的行:

8.jpg

将上图所示的特征行作为vmlinuz文件的末尾地址(不包含此行),记录下地址,使用dd命令将此段文件内容导出为vmlinuz文件:

9.jpg


2.解压ASA系统:

使用binwalk -e解压出rootfs.img文件,将其它都删除:

10.jpg

使用下列命令解压出linux系统:

$ cpio -i --make-directories < rootfs.img --no-absolute-filenames

编辑“/asa/scripts/rcS”脚本,改动如下图:

11.jpg

最后使用下列命令打包:

$ find . | cpio -o -H newc | gzip -9 > ../asa842.gz

在GNS3中,模拟所需的命令如下:

Kernel cmdline: ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt

Options: -icount auto -hdachs 980,16,32 -vga none -vnc none -serial telnet:192.168.56.1:3333,server,nowait

进入Cisco ASA 842的linux系统后,使用下列命令正常启动ASA:

$ ./lina -t -e eth0 -e eth1 -e eth2 -e eth3

使用下列命令启动gdbserver:

$ gdbserver /dev/ttyS1 /asa/bin/lina -t -g -l -e eth0 -e eth1 -e eth2 -e eth3


Refer: 

http://www.bearmr.com/index.php/repack-gns3-asa842-asa904/






二、硬件调试
以下使用ASA 922-4版本的固件作为演示,主要流程可归为三步:
1)解压ASA固件中的Linux系统。
2)修改Linux系统中启动ASA的流程,使其进入到调试模式。
3)重新打包固件,刷入ASA硬件设备,通过串口调试。
详细过程如下:
1. 通过binwalk分析和解压固件文件结构:
$ binwalk -e asa922-4-k8.bin
1.png

2. 只保留解压后的rootfs.img文件,删除其余的:
$ rm –rf !(rootfs.img)
3. 使用cpio命令将rootfs.img中的linux系统文件解压出来:
$ cpio -i --make-directories < rootfs.img --no-absolute-filenames
2.png

4. 删除镜像文件rootfs.img:
$ rm rootfs.img
5. 此时需要修改ASA底层Linux系统中的引导顺序,编辑/asa/scripts/rcS脚本,添加进入console命令行的语句:
3.png

注释掉后续可能会影响我们控制流程的代码:

4.png

6. 在此Linux系统中需要写一个启动器来执行我们期望的流程,以下是一个写好的启动器,其中一些代码可能放在在不同版本的ASA中需要做一些修改:
1) 在进入gdbserver进行调试时,需要指明gdbserver地址,不同版本ASA的gdbserver可能不同。此外,启动ASA主程序lina后,允许向lina写入配置命令,可更改代码如下:

5.png
2) 进入gdbserver调试前需要关闭当前占用串口的进程,一般为ASA中控制流程的脚本和console进程,在不同版本的ASA中进程名可能不一样,需要做相应修改,关闭顺序和此列表中的顺序相同,名称必须和"/proc/pid/cmdline"中的内容相同,改动代码如下:
6.png
3) 使用launcher来控制ASA启动流程,有可选参数,”-d”参数可使ASA系统在后台运行,通过lina_monitor启动主程序lina,不同版本的lina_monitor传入参数不一定一样,需要做相关适配,启动时可修改传入参数:
7.png
4) launcher中”-o”参数允许直接在后台启动lina进程,不用通过lina_monitor启动lina,可以直接向lina中传入参数。在lina中有一个隐藏参数”-s”,可使lina进程中不创建多线程,仅以单线程工作,其中”-p”参数指定lina的父进程PID,部分版本的lina在发现父进程不是lina_monitor后会向父进程PID发送终止信号,造成父进程关闭,此处需要注意在调试时可能造成的其它问题。修改向lina传入参数的代码如下:

8.png

5) launcher不加参数直接运行会启动lina_monitor的调试模式对lina进行调试,这里可以有两种选择,使用lina_monitor启动gdbserver或直接启动gdbserver,部分版本必须要用gdbserver直接启动lina,可修改相关代码:

9.png

6) launcher使用”-a”参数可让gdbserver attach到指定PID的进程上进行调试,”-a”参数接收进程PID。
7) gcc编译launcher,在ASA 832版本之前linux的glibc版本为2.3.2,从ASA 842开始使用的glibc版本为2.9。在高版本的glibc环境中编译的程序可能无法在glibc 2.3.2环境中运行,原因是调用的库函数版本可能无法匹配,此外,在glibc 2.3.2中无栈保护选项,而在glibc 2.9中默认会开启栈保护,因此高版本的glibc编译的程序在低版本glibc环境中运行,除了保证函数调用符号表在低版本glibc中可被匹配到以外,还需要关闭栈保护选项。可用nm命令查看程序中的符号信息,以确认glibc版本是否匹配。使用以下命令进行编译:
$ gcc -o launcher launcher.c -Wl,--hash-style=sysv -zexecstack -fno-stack-protector
launcher在接管控制流程后,不会在console中输出日志信息,其输出流被重定向到/mnt/disk0/launcher.log和/mnt/disk0/debug.log文件中。
7. 将编译好的launcher放到/bin/目录下,使用cpio将linux系统文件进行打包:
$ find . | cpio -o -H newc > ../rootfs.img
10.png


8. 由于在linux系统中添加了文件,导致重新压缩此镜像后,压缩文件大小会增长,然而在ASA bin固件中,多处内部调用的地址已被写死,所以随意增大固件中的部分内容长度,会造成很多其它问题,因此不能增大压缩后的系统文件大小。经测试发现,对于镜像文件rootfs.img,ASA固件在制作过程中使用gzip压缩算法,默认压缩率为”-6”,并使用”--name”命令将文件名也添加到压缩包内。由于gzip最高压缩率可指定为”-9”,因此可以使用以下命令将rootfs.img压缩为比原本固件内的压缩文件小:
$ gzip –name -9 –c rootfs.img > gz
11.png

此外,还需要知道原本固件中系统压缩文件的大小,通过以下命令对binwalk初次解压出来的rootfs.img镜像压缩后可得到:
$ gzip –name –c rootfs.img > gz
12.png

因此可以得到在使用高压缩率生成的文件比ASA固件中的系统文件总大小小了131669字节,这部分需要用0x00字符填充,用以下Python脚本生成填充文件:
import sys
file = open("padding", "w")
padding_str = ["\x00" for i in range(int(sys.argv[1]))]
file.write("".join(padding_str))
file.close()
13.png

9. 将ASA固件中的内容可分为三个部分:head、gz和tail,其中gz部分就是Linux系统,现gz部分已经制作好,head和tail可直接从ASA固件中进行提取。在图一中,使用binwalk分析出ASA固件的文件结构,从起始处到”rootfs.img”标识处,此部分数据用dd命令导出后作为head,命令如下:
$ dd if=asa922-4-k8.bin of=head bs=1 count=1501296
tail部分的起始地址在binwalk中定位的并不准确,因此通过head长度+原本gz的长度来确定tail的起始地址,同样通过dd命令导出为单独文件,命令如下:
$ dd skip=30391972 if=asa922-4-k8.bin of=tail bs=1
最后使用cat命令拼接成完整的ASA bin文件:
14.png

10. 通过tftp上传制作好的ASA固件(不可上传与当前ASA上运行版本相同的固件!),发现有校验和限制:

15.png

其中第一行MD5是写在ASA固件中的值,第二行是计算出的值,在ASA固件中查找这串MD5值,然后将其改为计算出的MD5值,并将MD5的CRC校验和改为0x0000,如下图:

16.png

保存后重新上传,返回结果如下:

17.png

此时发现文件MD5值已经匹配了,CRC校验值也被ASA计算好了,直接写入到固件的相应位置即可,注意小端字节顺序,再次上传可通过验证。
11. 启动后可使用launcher进入调试模式,如下图:
18.png

在minicom中关闭串口连接,在gdb中远程连接到串口上进行调试,如下:

gdb.jpg

在ASA上进行远程gdb调试的大致方式如下图:

gdbserver step.jpg

在使用IDA进行远程GDB调试时,需要处理两个信号,如下图:

搜狗截图20161125120307.png

附:
硬件调试中launcher程序完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <signal.h>
#include <sys/stat.h>
#include <limits.h>
#include <fcntl.h>

#define GDBSERV_PATH "/bin/gdbserver"
#define INPUT_CMD "en\nadmin\n123\nshow kernel process\n   \n"

char *gl_KillProcList[] = {"/bin/sh", "/etc/init.d/rcS", "/etc/init.d/S99asastart", "/asa/scripts/rcS", NULL};

enum
{
	E_Debug = 0x01,
	E_Daemon,
	E_Orphan,
	E_Attach,
};

int trav_dir(const char *const dirPath, const int maxDeep, void (*handler)(char *, char *), char *param)
{
	DIR *dp = NULL;
	struct dirent *pEntry = NULL;
	struct stat statBuf;
	char filePath[256];
	char newDirPath[256];

	if(maxDeep == 0) return 0;
	if(dirPath == NULL || strlen(dirPath) == 0 || handler == NULL) return -1;

	dp = opendir(dirPath);
	if(dp == NULL)
	{
		printf("Can't open dir,'%s'\n", dirPath);
		return -1;
	}

	while((pEntry = readdir(dp)) != NULL)
	{
		memset(filePath, 0x00, sizeof(filePath));
		strcat(filePath, dirPath);
		if(filePath[strlen(filePath) - 1] != '/') strcat(filePath, "/");
		strcat(filePath, pEntry->d_name);

		lstat(filePath, &statBuf);
		if(S_ISDIR(statBuf.st_mode))
		{
			//dir
			if(pEntry->d_name[0] == '.') continue;

			memset(newDirPath, 0x00, sizeof(newDirPath));
			strcat(newDirPath, filePath);
			trav_dir(newDirPath, maxDeep - 1, handler, param);
		}
		else
		{
			//file
			handler(filePath, param);
		}
	}
	closedir(dp);

	return 0;
}

int strrncmp(const char *m_str, const char *c_str, int len)
{
	int mStrLen;
	int cStrLen;
	int index;

	mStrLen = strlen(m_str);
	cStrLen = strlen(c_str);

	if(cStrLen < len) return -1;

	for(index = 1; index <= len; ++index)
	{
		if(m_str[mStrLen - index] != c_str[cStrLen - index])
			return -1;		
	}

	return 0;
}

void file_handler(char *filePath, char *procName)
{
	char *pStart = NULL;
	FILE *file = NULL;
	char readBuf[1024];
	int i, pid;

	for(i = strlen(filePath) - 1; i >= 0; i--)
	{
		if(filePath[i] == '/')
		{
			pStart = &filePath[i] + 1;
			break;
		}
	}
	if(pStart == NULL) return;
	if(strcmp(pStart, "cmdline")) return;

	file = fopen(filePath, "rt");
	if(file == NULL) return;

	memset(readBuf, 0x00, sizeof(readBuf));
	if(fread(readBuf, sizeof(char), sizeof(readBuf) - 1, file) <= 0)
	{
		fclose(file);
		return;
	}

	if(strrncmp(readBuf, procName, strlen(procName)) == 0)
	{
		printf("Found process: %s\t", readBuf);
	}
	else
	{
		fclose(file);
		return;
	}

	memcpy(pStart, "stat", strlen("stat"));
	*(pStart + strlen("stat")) = 0x00;

	fclose(file);
	file = fopen(filePath, "rt");
	if(file == NULL) return;
	/*
	if(fscanf(file, "%d", &pid) != 1)
	{
		fclose(file);
		return;
	}*/
	memset(readBuf, 0x00, sizeof(readBuf));
	if(fread(readBuf, sizeof(char), sizeof(readBuf) - 1, file) <= 0)
	{
		fclose(file);
		return;
	}
	for(pStart = readBuf; *pStart < '0' || *pStart > '9'; ++pStart);
	for(i = 0; pStart[i] >= '0' && pStart[i] <= '9'; ++i);
	pStart[i] = 0x00;

	pid = atoi(pStart);	
	kill(pid, 9);
	printf("Kill pid: %d\n", pid);
	fclose(file);

	return;
}

int kill_proc()
{
	int i;

	for(i = 0; gl_KillProcList[i] != NULL; i++)
	{
		trav_dir("/proc", 2, file_handler, gl_KillProcList[i]);
	}

	return 0;
}

char *itoa(int num)
{
	static char numStr[100];
	char swap;
	int i;

	memset(numStr, 0x00, sizeof(numStr));

	for(i = 0; num ; ++i)
	{
		numStr[i] = num % 10 + '0';
		num /= 10;
	}

	for(i = 0; i < strlen(numStr) / 2; ++i)
	{
		swap = numStr[i];
		numStr[i] = numStr[strlen(numStr) - i - 1];
		numStr[strlen(numStr) - i - 1] = swap;
	}

	return numStr;
}

int main(int argc, char *argv[])
{
	int pid, parentPid;
	int inPipe[2], outPipe[2], errPipe[2];
	char readBuf[PIPE_BUF + 1];
	int readSize;
	FILE *file = NULL;
	struct timeval timeo;
	fd_set fdRead;
	int exitVal;
	int i;
	int sw;
	char *pAttPid = NULL;

	if(argc < 1 || argc > 3) return -1;
	if(argc > 1)
	{
		if(!strcmp("-d", argv[1]))
		{
			sw = E_Daemon;
		}
		else if(!strcmp("-o", argv[1]))
		{
			sw = E_Orphan;
		}
		else if(!strcmp("-a", argv[1]))
		{
			sw = E_Attach;
			pAttPid = argv[2];
		}
		else
		{
			return -1;
		}
	}
	else
	{
		sw = E_Debug;
	}

	//////////////////////////////////////////////////////////////////
	if(sw == E_Daemon)
	{
		printf("The lina_monitor process will start.\n");
	}
	else if(sw == E_Orphan)
	{
		printf("The lina process will start.\n");
	}
	else
	{
		printf("Please disconnect the serial port!\ngdbserver will start.\n");
		//printf("Press Ctrl/C to cancel in.\n");
		for(i = 5; i > 0; i--)
		{
			printf("%d ", i);
			fflush(stdout);
			usleep(1000*1000);
		}
		puts("");
		fflush(stdout);
	}

	//////////////////////////////////////////////////////////////////
	pid = fork();
	if(pid != 0)
	{
		usleep(1000);
		exit(0);
	}
	setsid();
	parentPid = getpid();

	//////////////////////////////////////////////////////////////////
	if(sw == E_Orphan || sw == E_Daemon)
	{
		file = fopen("/mnt/disk0/launcher.log", "w");
		if(file == NULL)
		{
			//puts("Create launcher.log failed.");
		}
	}
	else
	{
		file = fopen("/mnt/disk0/debugger.log", "w");
		if(file == NULL)
		{
			//puts("Create debugger.log failed.");
		}
	}

	pipe(inPipe);
	pipe(outPipe);
	pipe(errPipe);
	dup2(inPipe[0], STDIN_FILENO);
	dup2(outPipe[1], STDOUT_FILENO);
	dup2(errPipe[1], STDERR_FILENO);

	printf("============Log===========\n");
	
	///////////////////////////////////////////////////////////////////
	if(sw == E_Daemon)
	{
		pid = fork();
		if(pid == 0)
		{
			close(inPipe[1]);
			close(outPipe[0]);
			close(errPipe[0]);

			execl("/asa/bin/lina_monitor", "/asa/bin/lina_monitor", NULL);
		}
		close(inPipe[0]);
		write(inPipe[1], INPUT_CMD, strlen(INPUT_CMD));
	}
	else if(sw == E_Orphan)
	{
		pid = fork();
		if(pid == 0)
		{
			close(inPipe[1]);
			close(outPipe[0]);
			close(errPipe[0]);
			parentPid = getpid();

			pid = fork();
			if(pid == 0)
			{
				setsid();
				execl("/asa/bin/lina", "/asa/bin/lina", "-p", itoa(parentPid), "-s", NULL);
			}
			while(1) usleep(1000);
			exit(0);
		}
		close(inPipe[0]);
		write(inPipe[1], INPUT_CMD, strlen(INPUT_CMD));
	}
	else
	{
		kill_proc();
	}

	//////////////////////////////////////////////////////////////////
	if(sw == E_Debug || sw == E_Attach)
	{
		usleep(1000*1000*2);
		pid = fork();
		if(pid == 0)
		{
			close(inPipe[1]);
			close(outPipe[0]);
			close(errPipe[0]);

			printf("gdbserver will start...\n");
			if(sw == E_Debug)
			{
				//execl("/asa/bin/lina_monitor", "/asa/bin/lina_monitor", "-g", "-d", "-s", "/dev/ttyS0", NULL);
				execl(GDBSERV_PATH, GDBSERV_PATH, "/dev/ttyS0", "/asa/bin/lina", "-p", itoa(parentPid), "-s", NULL);
			}
			else
			{
				printf("%s attach %s\n", GDBSERV_PATH, pAttPid);
				execl(GDBSERV_PATH, GDBSERV_PATH, "/dev/ttyS0", "--attach", pAttPid, NULL);
			}
			exit(0);
		}
	}

	////////////////////////////////////////////////////////////////////
	while(file != NULL)
	{
		memset(&timeo, 0x00, sizeof(timeo));
		timeo.tv_usec = 100;

		FD_ZERO(&fdRead);
		FD_SET(outPipe[0], &fdRead);
		FD_SET(errPipe[0], &fdRead);
		if(select((outPipe[0] > errPipe[0] ? outPipe[0] : errPipe[0]) + 1, &fdRead, NULL, NULL, &timeo) <= 0) continue;

		if(FD_ISSET(outPipe[0], &fdRead))
		{
			memset(readBuf, 0x00, sizeof(readBuf));
			if((readSize = read(outPipe[0], readBuf, sizeof(readBuf) - 1)) <= 0)
			{
				continue;
			}
			fwrite(readBuf, sizeof(char), readSize, file);
			fflush(file);
		}
		else
		{
			memset(readBuf, 0x00, sizeof(readBuf));
			if((readSize = read(errPipe[0], readBuf, sizeof(readBuf) - 1)) <= 0)
			{
				continue;
			}
			fwrite(readBuf, sizeof(char), readSize, file);
			fflush(file);
		}
	}
	while(1) usleep(1000*1000);

	return 0;
}



另一种调试方式:
https://community.rapid7.com/community/metasploit/blog/2016/06/14/asa-hack
这种方法只支持ASA 924版本,通过修改内核启动命令进入console,然后使用lina_monitor进入调试模式。