TL; DR: నేను కెర్నల్ మాడ్యూల్ని వ్రాస్తున్నాను, అది ICMP పేలోడ్ నుండి ఆదేశాలను చదివి, మీ SSH క్రాష్ అయినప్పటికీ సర్వర్లో వాటిని అమలు చేస్తుంది. చాలా అసహనానికి, అన్ని కోడ్ github.
జాగ్రత్త! అనుభవజ్ఞులైన సి ప్రోగ్రామర్లు రక్తంతో కన్నీళ్లు పెట్టుకునే ప్రమాదం ఉంది! నేను పరిభాషలో తప్పు కూడా ఉండవచ్చు, కానీ ఏ విమర్శ అయినా స్వాగతించబడుతుంది. ఈ పోస్ట్ సి ప్రోగ్రామింగ్ గురించి చాలా కఠినమైన ఆలోచన ఉన్నవారి కోసం ఉద్దేశించబడింది మరియు Linux లోపలి భాగాలను పరిశీలించాలనుకునే వారి కోసం ఉద్దేశించబడింది.
నా మొదటి వ్యాఖ్యలలో వ్యాసం SoftEther VPN గురించి ప్రస్తావించబడింది, ఇది కొన్ని "రెగ్యులర్" ప్రోటోకాల్లను అనుకరిస్తుంది, ప్రత్యేకించి HTTPS, ICMP మరియు DNS కూడా. నేను HTTP(S)తో బాగా పరిచయం ఉన్నందున వాటిలో మొదటిది మాత్రమే పని చేస్తుందని నేను ఊహించగలను, మరియు నేను ICMP మరియు DNS ద్వారా టన్నెలింగ్ నేర్చుకోవాలి.
అవును, 2020లో మీరు ICMP ప్యాకెట్లలోకి ఏకపక్ష పేలోడ్ని చొప్పించవచ్చని నేను తెలుసుకున్నాను. కానీ ఎప్పుడూ కంటే ఆలస్యం! మరియు దాని గురించి ఏదైనా చేయవచ్చు కాబట్టి, అది చేయవలసి ఉంటుంది. నా రోజువారీ జీవితంలో నేను చాలా తరచుగా SSH ద్వారా సహా కమాండ్ లైన్ని ఉపయోగిస్తాను కాబట్టి, ICMP షెల్ యొక్క ఆలోచన మొదట నా మనసులోకి వచ్చింది. మరియు పూర్తి బుల్షీల్డ్ బింగోను సమీకరించడానికి, నేను దానిని లైనక్స్ మాడ్యూల్గా వ్రాయాలని నిర్ణయించుకున్నాను, దాని గురించి నాకు మాత్రమే స్థూలమైన ఆలోచన ఉంది. అటువంటి షెల్ ప్రక్రియల జాబితాలో కనిపించదు, మీరు దానిని కెర్నల్లోకి లోడ్ చేయవచ్చు మరియు అది ఫైల్ సిస్టమ్లో ఉండదు, మీరు లిజనింగ్ పోర్ట్ల జాబితాలో అనుమానాస్పదంగా ఏదైనా చూడలేరు. దీని సామర్థ్యాల పరంగా, ఇది పూర్తి స్థాయి రూట్కిట్, కానీ SSH ద్వారా లాగిన్ చేయడానికి మరియు కనీసం అమలు చేయడానికి లోడ్ సగటు చాలా ఎక్కువగా ఉన్నప్పుడు దీన్ని మెరుగుపరచాలని మరియు చివరి ప్రయత్నంగా దీన్ని ఉపయోగించాలని నేను ఆశిస్తున్నాను. echo i > /proc/sysrq-triggerరీబూట్ చేయకుండా యాక్సెస్ పునరుద్ధరించడానికి.
మేము టెక్స్ట్ ఎడిటర్, ప్రాథమిక ప్రోగ్రామింగ్ నైపుణ్యాలను పైథాన్ మరియు సి, గూగుల్ మరియు వర్చువల్ ప్రతిదీ విచ్ఛిన్నమైతే (ఐచ్ఛికం - స్థానిక వర్చువల్బాక్స్/కెవిఎం/మొదలైనవి) కత్తి కింద పెట్టడానికి మీకు అభ్యంతరం లేదు మరియు వెళ్దాం!
క్లయింట్ వైపు
క్లయింట్ భాగం కోసం నేను దాదాపు 80 లైన్లతో స్క్రిప్ట్ రాయవలసి ఉంటుందని నాకు అనిపించింది, కాని నా కోసం దీన్ని చేసిన దయగల వ్యక్తులు ఉన్నారు. అన్ని పని. కోడ్ ఊహించని విధంగా సరళమైనది, 10 ముఖ్యమైన పంక్తులుగా సరిపోతుంది:
import sys
from scapy.all import sr1, IP, ICMP
if len(sys.argv) < 3:
print('Usage: {} IP "command"'.format(sys.argv[0]))
exit(0)
p = sr1(IP(dst=sys.argv[1])/ICMP()/"run:{}".format(sys.argv[2]))
if p:
p.show()
స్క్రిప్ట్ రెండు ఆర్గ్యుమెంట్లను తీసుకుంటుంది, చిరునామా మరియు పేలోడ్. పంపే ముందు, పేలోడ్కు ముందుగా ఒక కీ ఉంటుంది run:, యాదృచ్ఛిక పేలోడ్లతో ప్యాకేజీలను మినహాయించడానికి మాకు ఇది అవసరం.
ప్యాకేజీలను రూపొందించడానికి కెర్నల్కు అధికారాలు అవసరం, కాబట్టి స్క్రిప్ట్ను సూపర్యూజర్గా అమలు చేయాలి. అమలు అనుమతులు ఇవ్వడం మరియు స్కేపీని ఇన్స్టాల్ చేయడం మర్చిపోవద్దు. డెబియన్ అనే ప్యాకేజీ ఉంది python3-scapy. ఇప్పుడు మీరు అదంతా ఎలా పనిచేస్తుందో తనిఖీ చేయవచ్చు.
ఆదేశాన్ని అమలు చేయడం మరియు అవుట్పుట్ చేయడం morq@laptop:~/icmpshell$ sudo ./send.py 45.11.26.232 "Hello, world!"
Begin emission:
.Finished sending 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 45
id = 17218
flags =
frag = 0
ttl = 58
proto = icmp
chksum = 0x3403
src = 45.11.26.232
dst = 192.168.0.240
options
###[ ICMP ]###
type = echo-reply
code = 0
chksum = 0xde03
id = 0x0
seq = 0x0
###[ Raw ]###
load = 'run:Hello, world!
స్నిఫర్లో ఇది కనిపిస్తుంది morq@laptop:~/icmpshell$ sudo tshark -i wlp1s0 -O icmp -f "icmp and host 45.11.26.232"
Running as user "root" and group "root". This could be dangerous.
Capturing on 'wlp1s0'
Frame 1: 59 bytes on wire (472 bits), 59 bytes captured (472 bits) on interface wlp1s0, id 0
Internet Protocol Version 4, Src: 192.168.0.240, Dst: 45.11.26.232
Internet Control Message Protocol
Type: 8 (Echo (ping) request)
Code: 0
Checksum: 0xd603 [correct]
[Checksum Status: Good]
Identifier (BE): 0 (0x0000)
Identifier (LE): 0 (0x0000)
Sequence number (BE): 0 (0x0000)
Sequence number (LE): 0 (0x0000)
Data (17 bytes)
డెబియన్ వర్చువల్ మెషీన్లో నిర్మించడానికి మీకు కనీసం అవసరం make и linux-headers-amd64, మిగిలినవి డిపెండెన్సీల రూపంలో వస్తాయి. నేను వ్యాసంలో మొత్తం కోడ్ను అందించను; మీరు దానిని Githubలో క్లోన్ చేయవచ్చు.
హుక్ సెటప్
ప్రారంభించడానికి, మాడ్యూల్ను లోడ్ చేయడానికి మరియు దాన్ని అన్లోడ్ చేయడానికి మనకు రెండు ఫంక్షన్లు అవసరం. అన్లోడ్ చేయడానికి ఫంక్షన్ అవసరం లేదు, కానీ అప్పుడు rmmod ఇది పని చేయదు; మాడ్యూల్ ఆఫ్ చేయబడినప్పుడు మాత్రమే అన్లోడ్ చేయబడుతుంది.
మాడ్యూల్ మరియు నెట్ఫిల్టర్ను మార్చడానికి రెండు హెడర్ ఫైల్లు లాగబడతాయి.
అన్ని కార్యకలాపాలు నెట్ఫిల్టర్ ద్వారా జరుగుతాయి, మీరు దానిలో హుక్స్ సెట్ చేయవచ్చు. దీన్ని చేయడానికి, మీరు హుక్ కాన్ఫిగర్ చేయబడే నిర్మాణాన్ని ప్రకటించాలి. హుక్గా అమలు చేయబడే ఫంక్షన్ను పేర్కొనడం చాలా ముఖ్యమైన విషయం: nfho.hook = icmp_cmd_executor; నేను తర్వాత ఫంక్షన్కి వస్తాను.
అప్పుడు నేను ప్యాకేజీ కోసం ప్రాసెసింగ్ సమయాన్ని సెట్ చేసాను: NF_INET_PRE_ROUTING కెర్నల్లో మొదట కనిపించినప్పుడు ప్యాకేజీని ప్రాసెస్ చేయాలని నిర్దేశిస్తుంది. వాడుకోవచ్చు NF_INET_POST_ROUTING కెర్నల్ నుండి నిష్క్రమించినప్పుడు ప్యాకెట్ను ప్రాసెస్ చేయడానికి.
నేను ఫిల్టర్ని IPv4కి సెట్ చేసాను: nfho.pf = PF_INET;.
నేను నా హుక్కు అత్యధిక ప్రాధాన్యత ఇస్తాను: nfho.priority = NF_IP_PRI_FIRST;
మరియు నేను డేటా నిర్మాణాన్ని అసలు హుక్గా నమోదు చేస్తున్నాను: nf_register_net_hook(&init_net, &nfho);
చివరి ఫంక్షన్ హుక్ని తొలగిస్తుంది.
కంపైలర్ ఫిర్యాదు చేయని విధంగా లైసెన్స్ స్పష్టంగా సూచించబడింది.
విధులు module_init() и module_exit() మాడ్యూల్ను ప్రారంభించడానికి మరియు ముగించడానికి ఇతర ఫంక్షన్లను సెట్ చేయండి.
పేలోడ్ని తిరిగి పొందుతోంది
ఇప్పుడు మనం పేలోడ్ను సేకరించాలి, ఇది చాలా కష్టమైన పనిగా మారింది. పేలోడ్లతో పని చేయడానికి కెర్నల్కు అంతర్నిర్మిత ఫంక్షన్లు లేవు; మీరు ఉన్నత-స్థాయి ప్రోటోకాల్ల హెడర్లను మాత్రమే అన్వయించగలరు.
నేను IP మరియు ICMP హెడర్లను మార్చటానికి ఈసారి అదనపు హెడర్ ఫైల్లను చేర్చవలసి వచ్చింది.
నేను గరిష్ట పంక్తి పొడవును సెట్ చేసాను: #define MAX_CMD_LEN 1976. సరిగ్గా ఇది ఎందుకు? కంపైలర్ దాని గురించి ఫిర్యాదు చేసినందున! నేను స్టాక్ మరియు కుప్పను అర్థం చేసుకోవాలని వారు ఇప్పటికే నాకు సూచించారు, ఏదో ఒక రోజు నేను ఖచ్చితంగా దీన్ని చేస్తాను మరియు బహుశా కోడ్ను కూడా సరిదిద్దవచ్చు. నేను వెంటనే ఆదేశాన్ని కలిగి ఉండే పంక్తిని సెట్ చేసాను: char cmd_string[MAX_CMD_LEN];. ఇది అన్ని ఫంక్షన్లలో కనిపించాలి; నేను దీని గురించి మరింత వివరంగా 9వ పేరాలో మాట్లాడుతాను.
ఇప్పుడు మనం ప్రారంభించాలి (struct work_struct my_work;) నిర్మాణం మరియు దానిని మరొక ఫంక్షన్తో కనెక్ట్ చేయండి (DECLARE_WORK(my_work, work_handler);) ఇది ఎందుకు అవసరమో నేను తొమ్మిదవ పేరాలో కూడా మాట్లాడతాను.
ఇప్పుడు నేను ఒక ఫంక్షన్ను ప్రకటించాను, అది హుక్ అవుతుంది. రకం మరియు ఆమోదించబడిన వాదనలు నెట్ఫిల్టర్ ద్వారా నిర్దేశించబడతాయి, మేము మాత్రమే ఆసక్తి కలిగి ఉన్నాము skb. ఇది సాకెట్ బఫర్, ప్యాకెట్ గురించి అందుబాటులో ఉన్న మొత్తం సమాచారాన్ని కలిగి ఉండే ప్రాథమిక డేటా నిర్మాణం.
ఫంక్షన్ పని చేయడానికి, మీకు రెండు ఇటరేటర్లతో సహా రెండు స్ట్రక్చర్లు మరియు అనేక వేరియబుల్స్ అవసరం.
మనం లాజిక్తో ప్రారంభించవచ్చు. మాడ్యూల్ పని చేయడానికి, ICMP ఎకో తప్ప మరే ఇతర ప్యాకెట్లు అవసరం లేదు, కాబట్టి మేము అంతర్నిర్మిత ఫంక్షన్లను ఉపయోగించి బఫర్ను అన్వయిస్తాము మరియు అన్ని ICMP కాని మరియు నాన్-ఎకో ప్యాకెట్లను విసిరివేస్తాము. తిరిగి NF_ACCEPT ప్యాకేజీని అంగీకరించడం అని అర్థం, కానీ మీరు తిరిగి ఇవ్వడం ద్వారా ప్యాకేజీలను కూడా వదలవచ్చు NF_DROP.
IP హెడర్లను తనిఖీ చేయకుండా ఏమి జరుగుతుందో నేను పరీక్షించలేదు. C గురించి నాకున్న కనీస జ్ఞానం అదనపు తనిఖీలు లేకుండా, ఏదో భయంకరమైనది జరగవచ్చని నాకు చెబుతోంది. మీరు దీని నుండి నన్ను నిరాకరిస్తే నేను సంతోషిస్తాను!
ఇప్పుడు ప్యాకేజీ మీకు అవసరమైన ఖచ్చితమైన రకాన్ని కలిగి ఉంది, మీరు డేటాను సంగ్రహించవచ్చు. అంతర్నిర్మిత ఫంక్షన్ లేకుండా, మీరు మొదట పేలోడ్ ప్రారంభానికి పాయింటర్ను పొందాలి. ఇది ఒకే చోట జరుగుతుంది, మీరు పాయింటర్ను ICMP హెడర్ ప్రారంభానికి తీసుకెళ్లి, ఈ హెడర్ పరిమాణానికి తరలించాలి. ప్రతిదీ నిర్మాణాన్ని ఉపయోగిస్తుంది icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
హెడర్ ముగింపు తప్పనిసరిగా పేలోడ్ ఇన్ ముగింపుతో సరిపోలాలి skb, కాబట్టి మేము దానిని సంబంధిత నిర్మాణం నుండి అణు మార్గాలను ఉపయోగించి పొందుతాము: tail = skb_tail_pointer(skb);.
చిత్రం దొంగిలించబడింది ఇక్కడ నుండి, మీరు సాకెట్ బఫర్ గురించి మరింత చదువుకోవచ్చు.
మీరు ప్రారంభం మరియు ముగింపుకు పాయింటర్లను కలిగి ఉంటే, మీరు డేటాను స్ట్రింగ్లోకి కాపీ చేయవచ్చు cmd_string, ఉపసర్గ ఉనికి కోసం దాన్ని తనిఖీ చేయండి run: మరియు, ప్యాకేజీ తప్పిపోయినట్లయితే దాన్ని విస్మరించండి లేదా ఈ ఉపసర్గను తీసివేసి, లైన్ను మళ్లీ మళ్లీ వ్రాయండి.
అంతే, ఇప్పుడు మీరు మరొక హ్యాండ్లర్కు కాల్ చేయవచ్చు: schedule_work(&my_work);. అటువంటి కాల్కు పరామితిని పాస్ చేయడం సాధ్యం కాదు కాబట్టి, ఆదేశంతో లైన్ తప్పనిసరిగా గ్లోబల్గా ఉండాలి. schedule_work() పాస్ చేసిన స్ట్రక్చర్తో అనుబంధించబడిన ఫంక్షన్ను టాస్క్ షెడ్యూలర్ యొక్క సాధారణ క్యూలో ఉంచుతుంది మరియు పూర్తి చేస్తుంది, ఆదేశం పూర్తయ్యే వరకు వేచి ఉండకుండా మిమ్మల్ని అనుమతిస్తుంది. హుక్ చాలా వేగంగా ఉండాలి ఎందుకంటే ఇది అవసరం. లేకపోతే, మీ ఎంపిక ఏదీ ప్రారంభించబడదు లేదా మీరు కెర్నల్ భయాందోళనకు గురవుతారు. ఆలస్యం మరణం లాంటిది!
అంతే, మీరు సంబంధిత రిటర్న్తో ప్యాకేజీని అంగీకరించవచ్చు.
యూజర్స్పేస్లో ప్రోగ్రామ్కి కాల్ చేస్తోంది
ఈ ఫంక్షన్ చాలా అర్థమయ్యేది. దాని పేరు లో ఇవ్వబడింది DECLARE_WORK(), రకం మరియు ఆమోదించబడిన వాదనలు ఆసక్తికరంగా లేవు. మేము కమాండ్తో లైన్ను తీసుకుంటాము మరియు దానిని పూర్తిగా షెల్కు పాస్ చేస్తాము. అన్వయించడం, బైనరీలు మరియు మిగతా వాటి కోసం శోధించడంతో అతను వ్యవహరించనివ్వండి.
ఆర్గ్యుమెంట్లను స్ట్రింగ్ల శ్రేణికి సెట్ చేయండి argv[]. ప్రోగ్రామ్లు వాస్తవానికి ఈ విధంగా అమలు చేయబడతాయని మరియు ఖాళీలతో నిరంతర లైన్గా కాకుండా అందరికీ తెలుసునని నేను ఊహిస్తాను.
ఎన్విరాన్మెంట్ వేరియబుల్స్ సెట్ చేయండి. నేను కనీస పాత్ల సెట్తో PATHని మాత్రమే చొప్పించాను, అవన్నీ ఇప్పటికే కలిపి ఉన్నాయని ఆశిస్తున్నాను /bin с /usr/bin и /sbin с /usr/sbin. ఇతర మార్గాలు ఆచరణలో చాలా అరుదుగా ముఖ్యమైనవి.
పూర్తయింది, చేద్దాం! కెర్నల్ ఫంక్షన్ call_usermodehelper() ప్రవేశాన్ని అంగీకరిస్తుంది. బైనరీకి మార్గం, వాదనల శ్రేణి, పర్యావరణ వేరియబుల్స్ యొక్క శ్రేణి. ఎక్జిక్యూటబుల్ ఫైల్కు పాత్ను ప్రత్యేక ఆర్గ్యుమెంట్గా మార్చడం యొక్క అర్ధాన్ని ప్రతి ఒక్కరూ అర్థం చేసుకున్నారని ఇక్కడ నేను ఊహిస్తున్నాను, కానీ మీరు అడగవచ్చు. చివరి వాదన ప్రక్రియ పూర్తయ్యే వరకు వేచి ఉండాలో లేదో నిర్దేశిస్తుంది (UMH_WAIT_PROC), ప్రక్రియ ప్రారంభం (UMH_WAIT_EXEC) లేదా అస్సలు వేచి ఉండకండి (UMH_NO_WAIT) మరి కొన్ని ఉన్నాయా UMH_KILLABLE, నేను దానిని పరిశీలించలేదు.
అసెంబ్లీ
కెర్నల్ మాడ్యూల్స్ యొక్క అసెంబ్లీ కెర్నల్ మేక్-ఫ్రేమ్వర్క్ ద్వారా నిర్వహించబడుతుంది. పిలిచారు make కెర్నల్ వెర్షన్తో ముడిపడి ఉన్న ప్రత్యేక డైరెక్టరీ లోపల (ఇక్కడ నిర్వచించబడింది: KERNELDIR:=/lib/modules/$(shell uname -r)/build), మరియు మాడ్యూల్ యొక్క స్థానం వేరియబుల్కు పంపబడుతుంది M వాదనలలో. icmpshell.ko మరియు క్లీన్ టార్గెట్లు ఈ ఫ్రేమ్వర్క్ను పూర్తిగా ఉపయోగిస్తాయి. IN obj-m మాడ్యూల్గా మార్చబడే ఆబ్జెక్ట్ ఫైల్ను సూచిస్తుంది. రీమేక్ చేసే సింటాక్స్ main.o в icmpshell.o (icmpshell-objs = main.o) నాకు చాలా లాజికల్గా కనిపించడం లేదు, కానీ అలానే ఉంటుంది.
KERNELDIR:=/lib/modules/$(shell uname -r)/build
obj-m = icmpshell.o
icmpshell-objs = main.o
all: icmpshell.ko
icmpshell.ko: main.c
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
మేము సేకరిస్తాము: make. లోడ్: insmod icmpshell.ko. పూర్తయింది, మీరు తనిఖీ చేయవచ్చు: sudo ./send.py 45.11.26.232 "date > /tmp/test". మీ మెషీన్లో ఫైల్ ఉంటే /tmp/test మరియు ఇది అభ్యర్థన పంపబడిన తేదీని కలిగి ఉంది, అంటే మీరు ప్రతిదీ సరిగ్గా చేసారు మరియు నేను ప్రతిదీ సరిగ్గా చేసాను.
తీర్మానం
అణు అభివృద్ధితో నా మొదటి అనుభవం నేను ఊహించిన దానికంటే చాలా సులభం. C లో అభివృద్ధి చేసిన అనుభవం లేకపోయినా, కంపైలర్ సూచనలు మరియు Google ఫలితాలపై దృష్టి సారించి, నేను వర్కింగ్ మాడ్యూల్ని వ్రాయగలిగాను మరియు కెర్నల్ హ్యాకర్గా మరియు అదే సమయంలో స్క్రిప్ట్ కిడ్డీలాగా భావించగలిగాను. అదనంగా, నేను కెర్నల్ న్యూబీస్ ఛానెల్కి వెళ్లాను, అక్కడ నేను ఉపయోగించమని చెప్పాను schedule_work() కాల్ చేయడానికి బదులుగా call_usermodehelper() హుక్ లోపల మరియు అతనికి అవమానం, సరిగ్గా ఒక స్కామ్ అనుమానిస్తున్నారు. నా ఖాళీ సమయంలో ఒక వారం అభివృద్ధి కోసం వంద లైన్ల కోడ్ నాకు ఖర్చు అవుతుంది. సిస్టమ్ డెవలప్మెంట్ యొక్క అధిక సంక్లిష్టత గురించి నా వ్యక్తిగత అపోహను నాశనం చేసిన విజయవంతమైన అనుభవం.
ఎవరైనా గితుబ్లో కోడ్ సమీక్ష చేయడానికి అంగీకరిస్తే, నేను కృతజ్ఞతతో ఉంటాను. నేను చాలా తెలివితక్కువ తప్పులు చేశానని ఖచ్చితంగా అనుకుంటున్నాను, ముఖ్యంగా స్ట్రింగ్స్తో పని చేస్తున్నప్పుడు.