目录
笔者在安全客上的两篇对Cisco ASA军火库逆向分析的文章:
https://www.anquanke.com/post/id/84614
https://www.anquanke.com/post/id/85030
以及一个ASA攻击框架的项目:https://github.com/weizn11/Cisco_ASA_Backdoor
但前置条件是要有对ASA动态调试的能力,本文是笔者当时使用的ASA动态调试方法。
Cisco ASA调试可分为硬件调试和虚拟化调试,其中硬件调试方法可以支持所有版本的ASA系统,虚拟化调试目前只支持ASA 802和ASA 842两个版本,主要原因是只有这两个版本的系统支持虚拟化环境,下面分开讲解两种调试方法。
一、虚拟化调试
Cisco ASA 802:
# binwalk -e asa802-k8.bin
解压的文件中只留下1228B0:
# rm -rf !(1228B0)
cpio解压文件:
# cpio -i –make-directories < 1228B0 –no-absolute-filenames
# rm 1228B0
vim编辑etc/init.d/rcS文件,将最后一行注释后,改为/bin/sh:
将vmlinuz文件copy出来作为GNS3中的内核启动文件,然后把此目录下所有文件用cpio打包为gz文件:
# cp vmlinuz ../asa802-k8.kernel
# find . | cpio -o -H newc | gzip -9 > ../asa802-k8.gz
将这两个文件导入到GNS3中,其中qemu使用0.13.0版本,Network type选择pcnet,并勾选是用legacy网络模式。高级设置如下图所示:
其中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系统:
Cisco
ASA防火墙的整个加载流程如下图:
在这里我们更加关心的将是最后三个步骤,rcS作为一个shell脚本控制着lina_monitor的启动方式,如传入的参数控制,lina_monitor以创建子进程的方式启动lina程序,并对lina进程进行监视和管控。其中lina就是Cisco ASA防火墙的主程序,基本包含了ASA所有的业务逻辑代码,在系统内存中,lina将fork多个子进程协同工作,如下图所示:
对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文件导出为十六进制文件:
在文件中查找关键字“booting”:
定位到相应的行:
将上图所示的特征行作为vmlinuz文件的起始地址,记录下此地址。在从asa.hex文件中查找关键字“rootfs.img”:
定位到相应的行:
将上图所示的特征行作为vmlinuz文件的末尾地址(不包含此行),记录下地址,使用dd命令将此段文件内容导出为vmlinuz文件:
2.解压ASA系统:
使用binwalk -e解压出rootfs.img文件,将其它都删除:
使用下列命令解压出linux系统:
$ cpio -i –make-directories < rootfs.img –no-absolute-filenames
编辑“/asa/scripts/rcS”脚本,改动如下图:
最后使用下列命令打包:
$ 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分析和解压固件文件结构:
2. 只保留解压后的rootfs.img文件,删除其余的:
$ rm –rf !(rootfs.img)
3. 使用cpio命令将rootfs.img中的linux系统文件解压出来:
$ cpio -i –make-directories < rootfs.img –no-absolute-filenames
4. 删除镜像文件rootfs.img:
$ rm rootfs.img
5. 此时需要修改ASA底层Linux系统中的引导顺序,编辑/asa/scripts/rcS脚本,添加进入console命令行的语句:
注释掉后续可能会影响我们控制流程的代码:
6. 在此Linux系统中需要写一个启动器来执行我们期望的流程,以下是一个写好的启动器,其中一些代码可能放在在不同版本的ASA中需要做一些修改:
1) 在进入gdbserver进行调试时,需要指明gdbserver地址,不同版本ASA的gdbserver可能不同。此外,启动ASA主程序lina后,允许向lina写入配置命令,可更改代码如下:
2) 进入gdbserver调试前需要关闭当前占用串口的进程,一般为ASA中控制流程的脚本和console进程,在不同版本的ASA中进程名可能不一样,需要做相应修改,关闭顺序和此列表中的顺序相同,名称必须和”/proc/pid/cmdline”中的内容相同,改动代码如下:
3) 使用launcher来控制ASA启动流程,有可选参数,”-d”参数可使ASA系统在后台运行,通过lina_monitor启动主程序lina,不同版本的lina_monitor传入参数不一定一样,需要做相关适配,启动时可修改传入参数:
4) launcher中”-o”参数允许直接在后台启动lina进程,不用通过lina_monitor启动lina,可以直接向lina中传入参数。在lina中有一个隐藏参数”-s”,可使lina进程中不创建多线程,仅以单线程工作,其中”-p”参数指定lina的父进程PID,部分版本的lina在发现父进程不是lina_monitor后会向父进程PID发送终止信号,造成父进程关闭,此处需要注意在调试时可能造成的其它问题。修改向lina传入参数的代码如下:
5) launcher不加参数直接运行会启动lina_monitor的调试模式对lina进行调试,这里可以有两种选择,使用lina_monitor启动gdbserver或直接启动gdbserver,部分版本必须要用gdbserver直接启动lina,可修改相关代码:
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
8.由于在linux系统中添加了文件,导致重新压缩此镜像后,压缩文件大小会增长,然而在ASA bin固件中,多处内部调用的地址已被写死,所以随意增大固件中的部分内容长度,会造成很多其它问题,因此不能增大压缩后的系统文件大小。经测试发现,对于镜像文件rootfs.img,ASA固件在制作过程中使用gzip压缩算法,默认压缩率为”-6”,并使用”–name”命令将文件名也添加到压缩包内。由于gzip最高压缩率可指定为”-9”,因此可以使用以下命令将rootfs.img压缩为比原本固件内的压缩文件小:
$ gzip –name -9 –c rootfs.img > gz
此外,还需要知道原本固件中系统压缩文件的大小,通过以下命令对binwalk初次解压出来的rootfs.img镜像压缩后可得到:
$ gzip –name –c rootfs.img > gz
因此可以得到在使用高压缩率生成的文件比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()
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文件:
10. 通过tftp上传制作好的ASA固件(不可上传与当前ASA上运行版本相同的固件!),发现有校验和限制:
其中第一行MD5是写在ASA固件中的值,第二行是计算出的值,在ASA固件中查找这串MD5值,然后将其改为计算出的MD5值,并将MD5的CRC校验和改为0x0000,如下图:
保存后重新上传,返回结果如下:
此时发现文件MD5值已经匹配了,CRC校验值也被ASA计算好了,直接写入到固件的相应位置即可,注意小端字节顺序,再次上传可通过验证。
在minicom中关闭串口连接,在gdb中远程连接到串口上进行调试,如下:
在ASA上进行远程gdb调试的大致方式如下图:
在使用IDA进行远程GDB调试时,需要处理两个信号,如下图:
附:
硬件调试中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进入调试模式。
3 comments
强
您能留个联系方式吗
请问asa842的gz和kernel都导入到qemu后,在gns3中可以启动,但是右键console进不了命令行