Windows系统下C语言中使用自定义包的方法:
首先明确GOPATH的路径,执行go env | findstr GOPATH
1 | C:\Users\CHORDER>go env | findstr GOPATH |
接着在GOPATH
目录中创建相应的自定义包,这里定义一个example
包,并创建两个源码文件src1.go
和src2.go
,源码内容如下:
1 | /* C:\Users\CHORDER\go\src\chorder.net\example\src1.go */ |
1 | /*C:\Users\CHORDER\go\src\chorder.net\example\src2.go*/ |
编写代码调用这两个包中的方法:
packge_test.go
1 | package main |
执行结果:
1 | >go run packge_test.go |
在典型的IPv4网络下,由于NAT技术的广泛使用,端口映射是一种刚需。(下文所涉及“端口转发”、“端口映射”均指同一种技术。)
经典的端口映射方法例如iptables端口转发、SSH端口转发、webshell流量转发(regeorg)、socks代理等技术,是比较常见的用于建立内网通道的技术。本文分享一种利用内网网关设备开放的upnp协议进行端口映射的方法。
关于upnp协议的解释,参考百度百科-UPNP
upnp协议的使用场景很多,端口转发是upnp其中一个功能,但是想要使用该功能,需要网关设备的支持。
使用upnp进行端口转发的场景,以下图网络拓扑为例:
内网服务器Server:内网地址192.168.1.2,开放端口1234
网关设备Gateway:内网地址192.168.1.1,公网地址x.x.x.x
常规情况下,如果需要通过公网地址x.x.x.x访问192.168.1.2中的开放端口1234,则可以在Gateway中配置端口转发,但此时至少要有192.168.1.1的(部分)权限。
如果Gateway具备unpn功能,并且启用了unpn服务,则可以在Gateway外部通过upnp指令,打开Gateway上的转发通道,从而完成端口转发。
Gateway开启upnp示例(一般位于路由等设备的配置界面中):
Metasplioit中有upnp相关的插件,但是不太好用,姿势问题?
插件是:
可以自行探索之
miranda-upnp是之前在Github上找到的一个工具,基于python2编写,可以用于内网upnp端口转发通道的建立。
也可以根据upnp协议自行实现相关工具,协议使用可以参考这篇文章
以第2节中的拓扑为例,在内网IP为192.168.1.2的Server中运行miranda-upnp,打开转发通道,在Gateway的x.x.x.x:1234与Server 192.168.1.2:1234之间建立映射。
运行miranda-upnp后,首先执行msearch
:
1 | upnp> msearch |
此时已经获得Gateway中的SSDP描述文件,Ctrl-C停止SSDP搜寻。
执行host list
,列出扫描到的主机,并执行host get 0
选中主机0(Gateway):
1 | upnp> host list |
执行deviceList
和services
命令分别查看设备列表和设备中的服务:
1 | upnp> host info 0 deviceList InternetGatewayDevice services |
常规的端口转发功能我们要使用的是WANConnectionDevice
设备的WANIPConnection
服务,执行actions
查看其动作,这里可以看到存在一个名为AddPortMapping
的action,我们只要调用这个action,并填写相应参数,即可完成端口转发。
1 | upnp> host info 0 deviceList WANConnectionDevice services WANIPConnection actions |
可以看到此时Gateway中已经激活了相应的转发通道:
在Windows中创建服务一般需要先编写服务程序的代码,然后使用SCM注册这个服务。
手动注册一个Windows服务的方法是:
1 | # 注册服务 |
这里实现了一个最基本的Windows服务程序,服务启动之后监听本地TCP 8888端口,接收和发送数据。
1 | /*service.cpp*/ |
1 | /*server.cpp*/ |
1 | /*utils.cpp*/ |
1 | /*service.h*/ |
开发 Windows 服务应用
C语言开发Windows服务
C++编写Windows服务
C++编写Windows服务
Windows 服务程序(一)
Windows 服务程序(二)
windows环境下C语言socket编程
因为很多私事,复习停滞了两个半月,真是太惭愧了。不说了,菜鸡抽空继续学。
Service是一种被称为守护进程(daemon)的程序。它通常一旦启动后就在后台持续运行,通常在等待什么输入或者监控系统有什么变化。例如Apache服务器有一个叫httpd的守护进程监听80端口以等待http请求。
Service程序通常通过service命令来启动或停止等。例如apache服务的启动可通过
service httpd start
来启动。通过
service --status-all
可以查看系统当前所有service服务的状态。
通过以下样例程序,结合前期复习的socket相关知识,以及最基本的Linux系统调用,实现了一个运行在Linux中的服务程序(Daemon)。
这个程序会在后台监听当前主机的4444端口,当tcp客户端连接上时,会接受客户端的输入,并作为shell来执行,实现最基本的C/S架构的BD功能。
(后面也将继续基于这个例子进一步改良和优化这个程序。)
1 | /* |
Linux平台下的service程序编写指南
Linux中创建Daemon进程的三种方法
守护进程详解及创建,daemon使用
DNS
利用C语言实现DNS查询和DNS响应包的解析。
1 | /* |
HTTP
利用C语言实现HTTP请求,可以采用原生Socket往端口发包,根据HTTP协议自行构造请求的方式。
但更方便的方式是使用libcurl库。自行构造Socket请求的方式既不能保证性能,又无法保证安全。
libcurl库至少是相对完善的第三方库。
详细用法,参考libcurl官方文档以及libcurl官方样例。
SMTP
SMTP一例
1 | /* |
其他协议,后续学到再补充。
在前一夜的复习中实现了一个C/S架构的TCP套接字demo。今天首先通过创建一个UDP的套接字例子来区分UDP Socket和TCP Socket的使用区别。
客户端:
1 | /* |
服务端:
1 | /* |
本例以一个简单的http server来演示socket server构建:
1 | /* |
在阻塞的套接字中,服务端接收到的请求需要排队对其进行处理,这就形成了套接字阻塞。为了解决套接字阻塞的问题,可以创建非阻塞的服务端模型。
一个在网上找的非阻塞套接字的例子:
1 | /*************************************************************************** |
待后续补充。
Linux socket 编程,第一部分
Linux socket 编程,第二部分
Linux下的socket演示程序
编写一个单进程非阻塞多客户的套接字客户端
没有人逼你学C语言,但它在复杂的计算世界中,曲径通幽。
sockaddr_in
sockaddr_in结构体定义在/usr/include/netinet/in.h中,它的定义是这样的:
1 | /* Structure describing an Internet socket address. */ |
这个结构体中又涉及到另外几个类型和宏定义,包括__SOCKADDR_COMMON、in_port_t、in_addr。
__SOCKADDR_COMMON
__SOCKADDR_COMMON定义在bits/sockaddr.h当中,它的作用是将sin_和family参数拼接为对应的sa_family_t类型的地址家族。
1 | /* POSIX.1g specifies this type name for the `sa_family' member. */ |
也就是说这个预处理宏定义的意思是将sin_和family拼接在一起构成sa_family_t类型的sin_family。
sa_family_t就是短整形,长度16 bits的整数。
所以sockaddr_in结构体的第一个元素其实是sa_family_t类型的sin_family参数。
in_port_t
in_port_t实际上是uint16_t,16位的无符号整数。
1 | typedef uint16_t in_port_t; |
uint16_t其实就是__uint16_t,表示unsigned short int,它的长度是2个字节,能表示的范围是0-65535。
in_addr
这个显而易见,就是表示一个IPv4的地址:
1 | /* Internet address. */ |
而uint32_t也就是__uint32_t,表示unsigned int,一共4个字节。IP地址一般是拆成点分十进制看的,一个字节能表示的范围是0-255,所以的in_addr也就是能表示成0.0.0.0 ~ 255.255.255.255。
sin_zero
sin_zero的相关定义:
1 | /* Structure describing an Internet (IP) socket address. */ |
__pad声明了一个长素为8个字节的字符数组。
1 | __SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr) |
sin_family的类型
在C语言实现网络通信中,最开始需要做的就是声明并且初始化sockaddr_in结构体。
其中sin_family的类型有这些:
1 | /* Protocol families. */ |
其中最常用的AF_INET就是PF_INET,也就2所代表的IP协议家族。
sin_addr的赋值
sin_addr需要一个IPv4的地址作为值,in.h中定义了这样几个常用值,其中包括本地地址、多拨地址等:
写服务端socket最常用的INADDR_ANY地址就是((unsigned long int) 0x00000000)
,即 0.0.0.0:
1 |
|
socket的类型
关于socket的类型,在bits/socket_type.h中定义:
1 | /* Types of sockets. */ |
主要是定义在sys/socket.h中的以下函数:
关于这些函数的介绍暂不赘述,sys/socket.h里写的很详细。这里用一个socket基础的例子来演示其用法:
服务端server.c:
1 | /* |
客户端client.c:
1 | /* |
计算机堆与栈的区别就不赘述了,本内容主要通过演练几个C语言的例子来观察和探索堆栈。
一个演示栈和堆内存分配情况的例子
1 | #include <stdio.h> |
综上:
内存中的栈区主要用于分配局部变量空间,处于相对较高的地址,其栈地址是向下增长的;而堆区则主要用于分配程序员申请的内存空间,堆地址是向上增长的。
另一个关于堆栈地址分配的例子
1 | /* |
可见,程序运行时候栈底位于0x5661f000,而fmt和p这两个字符串分别存放在栈上的0x5661d0080x5661d00f和0x5661d0110x5661d015。
堆空间的分配
malloc(int)
,如果分配成功则返回分配好的地址,所以用一个指针去接收这个地址。输入参数指定分配的大小,单位是字节。
例:
char *p=(char *)malloc(100);
分配100个字节,分配的时候指定类型为char *类型,所以可以存储100个字符。
堆内存泄漏
如果在堆内存申请之后未进行安全释放,则有可能会造成内存泄漏问题。
本次复习重点不在研究堆管理机制,此部分内容留待后续再复习。
此部分内容参考资料:
Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(下)
Understanding glibc malloc
Syscalls used by malloc
把这个部分作为单独的环节来复习,是因为内联汇编在安全工程师的C语言编程当中用处是比较广泛的。无论是开发shellcode,调试exp,或是对代码做免杀,做代码虚拟机(虽然我在这方面并不擅长,但是大概看过一些其实现方式)都需要用到内联汇编。
另外对于高手来说,巧妙运用C语言内联汇编,有时是提高程序运行效率的很好的方式,或是在开发程序时,对调试起到一定的帮助。
不过,要想熟悉内联汇编,首先要熟悉汇编。
这里以Linux下,GCC作为编译器,AT&T汇编作为内联汇编格式:
简单内联汇编
1 | __asm__("汇编语句") |
拓展内联汇编
1 | __asm__ ( 汇编语句 |
简单内联汇编和拓展内联汇编的区别是简单内联汇编只包括指令,而扩展内联汇编包括操作数。
如果希望确保编译器不会在asm内部优化指令,可以在asm后使用关键字volatile。如果程序需要与 ANSI C 兼容,则应该使用 __asm__
和__volatile__
,而不是 asm 和 volatile。
通过内联汇编把a的值赋给b:
1 | #include <stdio.h> |
- 在本例中,b是输出操作数,由%0引用,a是输入操作数,由%1引用。
- “r”是操作数的约束,它指定将变量a和b存储在寄存器中。注意,输出操作数约束应该带有一个约束修饰符”=”,指定它是输出操作数。
- 要在asm内使用寄存器%eax,%eax的前面应该再加一个%,因为asm使用%0、%1等来标识变量。任何带有一个%的数都看作是输入/输出操作数,而不认为是寄存器。
- 第三个冒号后的修饰寄存器%eax告诉将在asm中修改GCC %eax的值,这样GCC 就不使用该寄存器存储任何其它的值。
movl %1, %%eax
将a的值移到%eax中,movl %%eax, %0
将%eax的内容移到b中。- 因为b被指定成输出操作数,因此当asm的执行完成后,它将反映出更新的值。换句话说,对asm内b所做的更改将在asm外反映出来。
需要注意几点第一就是所有寄存器都使用%%作为前缀,第二在这个部分新增了%0
%9的占位符来表示用户填充的数据。那么占位符,占的是什么位呢,谁来填充,怎么填充?%0%9的占位符会用输出部分和输入部分指定的寄存器或变量按照出现的顺序依次填充。如果不够填充则会出现编译错误的情况。
invalid 'asm': operand number out of range
使用内联汇编输出HelloWorld
1 | //编译方式:gcc -m32 -o test test.c |
注意
在编写内联汇编时如果遇到以下报错:
error: invalid 'asm': operand number out of range
很有可能是输入输出的操作数出了问题。说明输入输出操作数超出了范围。
在本例中,如果将输入参数从”m”类型的内存参数,改为”r”类型的寄存器参数,则会报此错。
具体原因尚在研究中。今后在使用时需要多留心参数类型的区别。
另外以上程序再编译时需要采用32位架构,否则会无法运行(因为使用的全都是32位寄存器)。
内联汇编中使用多个输入参数
1 | /*编译:gcc -m32 -o test test.c */ |
内联汇编还有很多的用法是需要琢磨的,也会随着对C语言理解的深入以及汇编的水平,探索出越来越高明的用法。
需要注意以下几点:
Update your browser to view this website correctly. Update my browser now