文章总结: 本文分析了main.asm的入口代码,通过汇编与C语言对比讲解了程序初始化、主循环及重连机制。核心在于构建无限死循环以维持连接,利用Sleep函数降低CPU占用。文章对比了阻塞IO与轮询的区别,解释了重连逻辑的必要性,为红队工具开发或恶意代码分析提供了基础框架,帮助理解底层运行逻辑。 综合评分: 95 文章分类: 二进制安全,恶意软件,红队,安全开发,逆向分析
第四篇:分析项目入口 – main.asm
原创
RabbitQ RabbitQ
安全研究站
2026年1月25日 18:43 北京
3.1 整体流程图
3.2 主要功能
完整代码我会放到github上:code/main/main.asm(汇编版本)和 code/main/main.c(C语言版本),这里我会通过C语言与汇编语言对照来讲清楚具体都在干嘛
准备工作
汇编代码:
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx, [rel msg_debug_main]
call printf
C 语言代码:
int main() {
printf("[*] Main start....\r\n");
在汇编中,程序开始时需要建立函数栈帧。这三条指令的作用是:
push rbp:将上一个函数的 rbp 保存到栈中,这样函数返回时可以恢复mov rbp, rsp:将当前栈指针赋值给基址指针,作为当前函数的基准sub rsp, 32:分配32字节的栈空间,这是Windows x64调用约定要求的”影子空间”
而在 C 语言中,这些工作完全由编译器自动完成,程序员不需要关心,这就是高级语言的优势之一。
主循环
汇编代码:
main_loop:
; 打印循环调试信息
lea rcx, [rel msg_debug_loop]
call printf
; 检查连接
cmp byte [rel is_connected], 0
je reconnect
; 接收和处理命令(模拟)
lea rcx, [rel msg_debug_recv]
call printf
lea rcx, [rel msg_debug_exec]
call printf
lea rcx, [rel msg_debug_send]
call printf
; 休息
mov rcx, 1000
call Sleep
jmp main_loop
C 语言代码:
main_loop:
printf("[*] Main loop iteration\r\n");
// 检查连接状态
if (is_connected == 0) {
goto reconnect;
}
// 接收和处理命令(模拟)
printf("[*] Receiving command...\r\n");
printf("[*] Executing command...\r\n");
printf("[*] Sending response...\r\n");
// 等待1秒(避免占满CPU)
Sleep(1000);
// 跳回循环开始
goto main_loop;
主循环是整个程序的核心,它的执行流程如下:
- 打印调试信息:首先输出一条消息,表示程序正在运行。这对调试很有帮助,可以确认程序是否正常工作。
- 检查连接状态:使用
cmp指令比较is_connected变量的值。如果值为0(表示未连接),则跳转到reconnect标签进行重连。 - 接收和处理命令:在正常连接的情况下,程序会依次调用三个函数来模拟命令处理流程:
- 接收命令
- 执行命令
- 发送响应
- 睡眠1秒:调用
Sleep(1000)让程序暂停1秒。这样做有几个好处:
- 避免100%占用CPU
- 降低程序被发现的风险
- 为系统节省资源
- 跳回循环开始:使用
jmp main_loop无条件跳转回循环开始,形成一个无限循环。
这个循环会一直运行,直到程序被强制终止(如按Ctrl+C),接下来讲讲这个死循环是咋实现的,先看汇编代码:
main_loop: ; ← 这是标签
; 做点事...
jmp main_loop ; ← 跳回标签
然后是C代码:
main_loop:
// 做点事...
goto main_loop; // ← 跳回标签
这里其实就是放那一个标签,就这玩意就像书签,jmp(或 goto)就是”跳到那个书签”,注意昂,我们在正常的写代码的时候,这玩意一般这么写:
while(1){
// 做点事...
}
有个问题不知道你们看代码的时候有没有琢磨,为啥要休眠,直接放代码,汇编代码:
mov rcx, 1000 ; 参数:1000毫秒 = 1秒
call Sleep ; 调用Windows API
C 语言代码:
Sleep(1000); // 1000毫秒 = 1秒
如果不睡眠,死循环会一直空转,CPU占用会达到100%。这会导致系统变慢,其他程序响应迟缓,睡眠可以让程序让出CPU时间片,降低占用,给其他进程运行的机会。我的粉丝里面搞红队的可能比较多,可能会有人问我,为啥CS在执行sleep 0之后不会有我刚说的问题呢,其实这里是它使用了阻塞式网络通信(如ReadFile、ConnectNamedPipe),这些API在等待数据时会将线程挂起而不占用CPU,即使循环中有Sleep(0),线程大部分时间都处于阻塞等待状态,不会像普通程序那样疯狂轮询。简单来说:普通程序没有真正的阻塞点所以会空转占CPU,而Beacon在等网络数据时线程是挂起的,不占CPU时间。
重连
汇编代码:
reconnect:
; 打印重连消息
lea rcx, [rel msg_debug_reconnect]
call printf
; 模拟重连(简化版)
mov byte [rel is_connected], 1
mov rcx, 5000
call Sleep
jmp main_loop
C 语言代码:
reconnect:
printf("[*] Connection lost, attempting to reconnect...\r\n");
// 模拟重连(这里简化处理)
is_connected = 1;
// 等5秒后重试
Sleep(5000);
// 跳回主循环
goto main_loop;
重连逻辑用于处理连接断开的情况,其执行步骤如下:
- 打印重连消息:向用户输出一条消息,告知程序正在尝试重新连接服务器。这在实际运行时可以帮助了解程序的状态。
- 模拟重连:在完整版本中,这里会调用网络函数重新建立与服务器的连接。但在这个简化版本中,我们直接将
is_connected变量设置为1,表示连接已恢复。这样可以演示程序逻辑,而不需要实际的网络通信。 - 等待5秒:使用
Sleep(5000)让程序暂停5秒。设置这个延迟有两个重要原因:
- 避免程序在短时间内频繁尝试重连,这可能会对服务器造成压力
- 给网络环境一个恢复的时间,如果服务器只是暂时不可用,稍后再重连可能会成功
- 跳回主循环:使用
jmp main_loop返回到主循环,让程序重新检查连接状态并继续正常工作。
这种自动重连机制就是为了防止网络出现波动、Server临时重启等原因导致连接断开的情况,即使出现问题程序也能自动恢复运行。
退出
汇编代码:
exit_implant:
lea rcx, [rel msg_debug_exit]
call printf
; 清理资源(简化版省略)
mov rcx, 0
call ExitProcess
ret
C 语言代码:
exit_implant:
printf("[*] Shutting down...\r\n");
// 清理资源(简化版)
// 无代码
// 退出程序
ExitProcess(0);
return 0;
退出处理部分的执行步骤如下:
- 打印退出消息:输出一条关闭消息,通知用户程序即将结束。这有助于确认程序正常终止。
- 清理资源:在完整版本中,这里会执行以下清理操作:
-
关闭网络套接字连接
-
释放动态分配的内存
-
清理其他系统资源
但在简化版本中,这些步骤被省略了,因为我们没有实际创建这些资源。
- 调用 ExitProcess:使用 Windows API 的
ExitProcess(0)函数终止程序。参数0表示程序正常退出(非0通常表示错误)。注意:ExitProcess是 Windows 特有的函数,它会立即终止整个进程,不会返回。 - return 语句:在 C 语言代码中的
return 0;语句实际上不会执行,因为ExitProcess已经终止了程序。但按照 C 语言的规范,main 函数应该返回一个整数值,所以这里还是保留了这个语句。
在当前的程序结构中,这个退出代码实际上永远不会被执行到,因为主循环是无限循环。程序只能通过外部方式(如按 Ctrl+C 或任务管理器结束进程)来终止,在完整的实现中,会添加一个特殊的退出命令(我用的是exit,你随便叫什么,叫aaa都行,这里后面再说)来让程序正常退出。
补充
虽然之 前讲过函数调用,但是还是再说一嘴吧,call 做了什么:
- 把”下一条指令的地址”压栈(返回地址)
- 跳转到
receive_command receive_command执行完后retret从栈中取出返回地址,跳回来
大概是下面这个情况:
主程序:
[指令1]
call func ← 这里
[指令2] ← 返回到这
函数:
func:
[函数体]
ret ← 返回
而我们在写C编译器自动处理这些细节,只需要写函数名加括号
3.3 实际运行示例
使用我之前发的build.bat编译 code/main.asm 后运行,屏幕输出:
使用gcc main.c编译 code/main.c 后运行,屏幕输出:
3.4 本章总结
这一章把 main.asm 的核心结构讲完了,其实就几件事:
先初始化建好栈帧,然后进入主循环,主循环就是死循环,一直检查连接、收命令、执行、发结果,最后睡一秒再继续。连接断了有重连逻辑,等5秒再试。退出代码现在用不到,但以后加个 exit 命令就能用上了。
几个关键点:
- 无限循环不是bug,是feature,这样才能一直等着接收命令
- Sleep(1000) 很重要,不然CPU直接干到100%,系统都卡死
- 重连机制让程序更稳定,网络抖一下自己就能恢复
好了第三章就到这,下一章讲工具函数~
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全研究站 RabbitQ RabbitQ《第四篇:分析项目入口 – main.asm》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论