The code sample code uses BSD sockets directly to establish a connection to httpbin.org on port 80. The demo doesn't send any data over the connection, but for the purposes of this demo, assume that it does.
importFoundationstructMastgTest{//SUMMARY:ThissampledemonstratestheuseofBSDsocketsthatbypassATS.staticfuncmastgTest(completion:@escaping(String)->Void){varresult="Testing BSD socket connection (bypasses ATS):\n\n"//FAIL:[MASTG-TEST-0322]UsingBSDsocketsbypassesATSlethost="httpbin.org"letport:UInt16=80//ResolvehostnametoIPaddressvarhints=addrinfo()hints.ai_family=AF_INEThints.ai_socktype=SOCK_STREAMhints.ai_protocol=IPPROTO_TCPvarserverInfo:UnsafeMutablePointer<addrinfo>?letstatus=getaddrinfo(host,String(port),&hints,&serverInfo)guardstatus==0,letinfo=serverInfoelse{result+="Failed to resolve hostname: \(String(cString: gai_strerror(status)))\n"completion(result)return}defer{freeaddrinfo(serverInfo)}//Createsocket-thisbypassesATSentirelyletsock=socket(info.pointee.ai_family,info.pointee.ai_socktype,info.pointee.ai_protocol)guardsock>=0else{result+="Failed to create socket\n"completion(result)return}defer{close(sock)}//ConnecttoserverusingrawsocketletconnectResult=connect(sock,info.pointee.ai_addr,info.pointee.ai_addrlen)guardconnectResult==0else{result+="Failed to connect: \(String(cString: strerror(errno)))\n"completion(result)return}result+="Socket connection established to httpbin.org:80 (cleartext)\n"result+="This connection bypasses ATS because it uses BSD sockets directly\n"//SendHTTPrequestovercleartextsocketletrequest="GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n"letsendResult=request.withCString{ptrinsend(sock,ptr,strlen(ptr),0)}ifsendResult>0{result+="HTTP request sent successfully over cleartext socket connection\n"//Receiveresponsevarbuffer=[CChar](repeating:0,count:1024)letrecvResult=recv(sock,&buffer,buffer.count-1,0)ifrecvResult>0{letresponse=String(cString:buffer)letfirstLine=response.components(separatedBy:"\r\n").first??""result+="Received response: \(firstLine)\n"}}else{result+="Failed to send request\n"}completion(result)}}
Unzip the app package and locate the main binary file ( Exploring the App Package), which in this case is ./Payload/MASTestApp.app/MASTestApp.
Run radare2 (iOS) with the script to search for BSD socket APIs in the binary.
bsd_sockets.r2
1 2 3 4 5 6 7 8 9101112131415161718192021222324
easm.bytes=falseescr.color=falseeasm.var=false?eUsesoftheBSDsocketsfunctions:ii~getaddrinfo,send,recv,connect,socket?e?exrefstogetaddrinfo:axt@0x1000069c4?e?eUseofgetaddrinfo:# Seek to the function where getaddrinfo is calledpd--20@0x1000041d0?e?eValuepassedtogetaddrinfo:?0x50~uint32[1]
UsesoftheBSDsocketsfunctions:1390x100006940NONEFUNCconnect1500x1000069c4NONEFUNCgetaddrinfo1550x100006a00NONEFUNCrecv1570x100006a18NONEFUNCsend1580x100006a24NONEFUNCsocketxrefstogetaddrinfo:sym.func.1000041080x1000041d0[CALL:--x]blsym.imp.getaddrinfoUseofgetaddrinfo:│0x100004180strw8,[arg_f0hx5c]│0x100004184strxzr,[var_48h]│0x100004188movw8,0x50;'P'│0x10000418cstrhw8,[var_30h]│0x100004190adrpx0,segment.__DATA_CONST;0x100008000│0x100004194ldrx0,[x0,0x3e8];[0x1000083e8:4]=124;"|"│0x100004198adrpx1,segment.__DATA_CONST;0x100008000│0x10000419cldrx1,[x1,0x3f0];[0x1000083f0:4]=125;"}"│0x1000041a0addx20,sp,0x30│0x1000041a4blsym.imp.CustomStringConvertible.description__String_...vgTj_;CustomStringConvertible.description__String(...vgTj)│0x1000041a8movx20,x1│0x1000041acblsym.imp.utf8CString.ContiguousArray.setter...Vys4Int8VGvg│0x1000041b0movx24,x0│0x1000041b4movx0,x20;void*arg0│0x1000041b8blsym.imp.swift_bridgeObjectRelease;voidswift_bridgeObjectRelease(void*arg0)│0x1000041bcadrpx0,0x100006000│0x1000041c0addx0,x0,0xd50;0x100006d50;"httpbin.org"│0x1000041c4addx1,x24,0x20│0x1000041c8addx2,sp,0x50│0x1000041ccaddx3,sp,0x48│0x1000041d0blsym.imp.getaddrinfo│0x1000041d4movx21,x0│0x1000041d8movx0,x24;void*arg0│0x1000041dcblsym.imp.swift_release;voidswift_release(void*arg0)│┌─<0x1000041e0cbzw21,0x10000429c││;CODEXREFfromsym.func.100004108@0x1000042a0(x)││0x1000041e4movx8,-0x2000000000000000││0x1000041e8stpxzr,x8,[var_30h]││0x1000041ecaddx20,sp,0x30││0x1000041f0movw0,0x1f││0x1000041f4blsym.imp._StringGuts.grow_...SiF_;_StringGuts.grow(...SiF)││0x1000041f8ldrx0,[arg0];void*arg0││0x1000041fcblsym.imp.swift_bridgeObjectRelease;voidswift_bridgeObjectRelease(void*arg0)││0x100004200adrpx8,0x100006000││0x100004204addx8,x8,0xd60;0x100006d60;"Failed to resolve hostname: "││0x100004208subx8,x8,0x20││0x10000420corrx8,x8,0x8000000000000000││0x100004210addx9,x27,9││0x100004214stpx9,x8,[var_30h]││0x100004218movx0,x21││0x10000421cblsym.imp.gai_strerrorValuepassedtogetaddrinfo:80
The test fails because the app uses BSD sockets directly, including socket, connect, send, recv, and getaddrinfo. The binary imports these symbols and calls them to create a cleartext network connection that bypasses ATS. The getaddrinfo call resolves the hostname httpbin.org and is supplied with a service value representing port 80.
The signature of the getaddrinfo function from the Darwin libc man pages is as follows:
Where nodename is the host (e.g., "example.com") and servname is the service name or port string (e.g., "http" or "80").
At 0x100004188 the code loads w8 with 0x50 and stores it as a halfword at var_30h, then sets x20 to sp + 0x30 and later calls utf8CString.ContiguousArray.setter. This sequence uses the numeric value 0x50, which is 80 in decimal, and converts it into a NUL terminated UTF8 string buffer.
At 0x1000041c4 the code sets x1 to x24 + 0x20, which points to the previous string buffer, and passes it into getaddrinfo as the servname parameter. The nodename parameter in x0 points to the literal httpbin.org. The hints and res pointers are passed via stack addresses sp + 0x50 and sp + 0x48.