ICMP ಮೇಲೆ ಪರಮಾಣು ಶೆಲ್

ICMP ಮೇಲೆ ಪರಮಾಣು ಶೆಲ್

ಟಿಎಲ್; ಡಿಆರ್: ನಾನು ಕರ್ನಲ್ ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಬರೆಯುತ್ತಿದ್ದೇನೆ ಅದು ICMP ಪೇಲೋಡ್‌ನಿಂದ ಆಜ್ಞೆಗಳನ್ನು ಓದುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ SSH ಕ್ರ್ಯಾಶ್ ಆಗಿದ್ದರೂ ಸಹ ಅವುಗಳನ್ನು ಸರ್ವರ್‌ನಲ್ಲಿ ಕಾರ್ಯಗತಗೊಳಿಸುತ್ತದೆ. ಹೆಚ್ಚು ತಾಳ್ಮೆಯಿಲ್ಲದವರಿಗೆ, ಎಲ್ಲಾ ಕೋಡ್ ಆಗಿದೆ GitHub.

ಎಚ್ಚರಿಕೆ! ಅನುಭವಿ ಸಿ ಪ್ರೋಗ್ರಾಮರ್‌ಗಳು ರಕ್ತದ ಕಣ್ಣೀರಿನಲ್ಲಿ ಸಿಡಿಯುವ ಅಪಾಯವಿದೆ! ನಾನು ಪರಿಭಾಷೆಯಲ್ಲಿ ತಪ್ಪಾಗಿರಬಹುದು, ಆದರೆ ಯಾವುದೇ ಟೀಕೆ ಸ್ವಾಗತಾರ್ಹ. ಸಿ ಪ್ರೋಗ್ರಾಮಿಂಗ್‌ನ ಸ್ಥೂಲ ಕಲ್ಪನೆಯನ್ನು ಹೊಂದಿರುವ ಮತ್ತು ಲಿನಕ್ಸ್‌ನ ಒಳಭಾಗವನ್ನು ನೋಡಲು ಬಯಸುವವರಿಗೆ ಪೋಸ್ಟ್ ಉದ್ದೇಶಿಸಲಾಗಿದೆ.

ನನ್ನ ಮೊದಲ ಕಾಮೆಂಟ್‌ಗಳಲ್ಲಿ ಲೇಖನ SoftEther VPN ಅನ್ನು ಉಲ್ಲೇಖಿಸಲಾಗಿದೆ, ಇದು ಕೆಲವು "ನಿಯಮಿತ" ಪ್ರೋಟೋಕಾಲ್‌ಗಳನ್ನು ಅನುಕರಿಸಬಹುದು, ನಿರ್ದಿಷ್ಟವಾಗಿ HTTPS, ICMP ಮತ್ತು DNS. ಅವುಗಳಲ್ಲಿ ಮೊದಲನೆಯದು ಮಾತ್ರ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿದೆ ಎಂದು ನಾನು ಊಹಿಸಬಲ್ಲೆ, ಏಕೆಂದರೆ ನಾನು HTTP(S) ನೊಂದಿಗೆ ಬಹಳ ಪರಿಚಿತನಾಗಿದ್ದೇನೆ ಮತ್ತು ನಾನು ICMP ಮತ್ತು DNS ಮೂಲಕ ಸುರಂಗವನ್ನು ಕಲಿಯಬೇಕಾಗಿತ್ತು.

ICMP ಮೇಲೆ ಪರಮಾಣು ಶೆಲ್

ಹೌದು, 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)

0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Length: 17]

Frame 2: 59 bytes on wire (472 bits), 59 bytes captured (472 bits) on interface wlp1s0, id 0
Internet Protocol Version 4, Src: 45.11.26.232, Dst: 192.168.0.240
Internet Control Message Protocol
Type: 0 (Echo (ping) reply)
Code: 0
Checksum: 0xde03 [correct] [Checksum Status: Good] Identifier (BE): 0 (0x0000)
Identifier (LE): 0 (0x0000)
Sequence number (BE): 0 (0x0000)
Sequence number (LE): 0 (0x0000)
[Request frame: 1] [Response time: 19.094 ms] Data (17 bytes)

0000 72 75 6e 3a 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 run:Hello, world
0010 21 !
Data: 72756e3a48656c6c6f2c20776f726c6421
[Length: 17]

^C2 packets captured

ಪ್ರತಿಕ್ರಿಯೆ ಪ್ಯಾಕೇಜ್‌ನಲ್ಲಿನ ಪೇಲೋಡ್ ಬದಲಾಗುವುದಿಲ್ಲ.

ಕರ್ನಲ್ ಮಾಡ್ಯೂಲ್

ಡೆಬಿಯನ್ ವರ್ಚುವಲ್ ಗಣಕದಲ್ಲಿ ನಿರ್ಮಿಸಲು ನಿಮಗೆ ಕನಿಷ್ಠ ಪಕ್ಷ ಬೇಕಾಗುತ್ತದೆ make и linux-headers-amd64, ಉಳಿದವು ಅವಲಂಬನೆಗಳ ರೂಪದಲ್ಲಿ ಬರುತ್ತವೆ. ನಾನು ಲೇಖನದಲ್ಲಿ ಸಂಪೂರ್ಣ ಕೋಡ್ ಅನ್ನು ಒದಗಿಸುವುದಿಲ್ಲ; ನೀವು ಅದನ್ನು Github ನಲ್ಲಿ ಕ್ಲೋನ್ ಮಾಡಬಹುದು.

ಹುಕ್ ಸೆಟಪ್

ಪ್ರಾರಂಭಿಸಲು, ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಲೋಡ್ ಮಾಡಲು ಮತ್ತು ಅದನ್ನು ಇಳಿಸಲು ನಮಗೆ ಎರಡು ಕಾರ್ಯಗಳು ಬೇಕಾಗುತ್ತವೆ. ಇಳಿಸುವಿಕೆಯ ಕಾರ್ಯವು ಅಗತ್ಯವಿಲ್ಲ, ಆದರೆ ನಂತರ rmmod ಅದು ಕೆಲಸ ಮಾಡುವುದಿಲ್ಲ; ಆಫ್ ಮಾಡಿದಾಗ ಮಾತ್ರ ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಇಳಿಸಲಾಗುತ್ತದೆ.

#include <linux/module.h>
#include <linux/netfilter_ipv4.h>

static struct nf_hook_ops nfho;

static int __init startup(void)
{
  nfho.hook = icmp_cmd_executor;
  nfho.hooknum = NF_INET_PRE_ROUTING;
  nfho.pf = PF_INET;
  nfho.priority = NF_IP_PRI_FIRST;
  nf_register_net_hook(&init_net, &nfho);
  return 0;
}

static void __exit cleanup(void)
{
  nf_unregister_net_hook(&init_net, &nfho);
}

MODULE_LICENSE("GPL");
module_init(startup);
module_exit(cleanup);

ಇಲ್ಲಿ ಏನು ನಡೆಯುತ್ತಿದೆ:

  1. ಮಾಡ್ಯೂಲ್ ಮತ್ತು ನೆಟ್‌ಫಿಲ್ಟರ್ ಅನ್ನು ಕುಶಲತೆಯಿಂದ ನಿರ್ವಹಿಸಲು ಎರಡು ಹೆಡರ್ ಫೈಲ್‌ಗಳನ್ನು ಎಳೆಯಲಾಗುತ್ತದೆ.
  2. ಎಲ್ಲಾ ಕಾರ್ಯಾಚರಣೆಗಳು ನೆಟ್ಫಿಲ್ಟರ್ ಮೂಲಕ ಹೋಗುತ್ತವೆ, ನೀವು ಅದರಲ್ಲಿ ಕೊಕ್ಕೆಗಳನ್ನು ಹೊಂದಿಸಬಹುದು. ಇದನ್ನು ಮಾಡಲು, ಹುಕ್ ಅನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಲಾದ ರಚನೆಯನ್ನು ನೀವು ಘೋಷಿಸಬೇಕಾಗಿದೆ. ಹುಕ್ ಆಗಿ ಕಾರ್ಯಗತಗೊಳ್ಳುವ ಕಾರ್ಯವನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸುವುದು ಅತ್ಯಂತ ಮುಖ್ಯವಾದ ವಿಷಯ: 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);
  3. ಅಂತಿಮ ಕಾರ್ಯವು ಹುಕ್ ಅನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ.
  4. ಕಂಪೈಲರ್ ದೂರು ನೀಡದಂತೆ ಪರವಾನಗಿಯನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ಸೂಚಿಸಲಾಗುತ್ತದೆ.
  5. ಕಾರ್ಯಗಳು module_init() и module_exit() ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಮತ್ತು ಅಂತ್ಯಗೊಳಿಸಲು ಇತರ ಕಾರ್ಯಗಳನ್ನು ಹೊಂದಿಸಿ.

ಪೇಲೋಡ್ ಅನ್ನು ಹಿಂಪಡೆಯಲಾಗುತ್ತಿದೆ

ಈಗ ನಾವು ಪೇಲೋಡ್ ಅನ್ನು ಹೊರತೆಗೆಯಬೇಕಾಗಿದೆ, ಇದು ಅತ್ಯಂತ ಕಷ್ಟಕರವಾದ ಕೆಲಸವಾಗಿದೆ. ಪೇಲೋಡ್‌ಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡಲು ಕರ್ನಲ್ ಅಂತರ್ನಿರ್ಮಿತ ಕಾರ್ಯಗಳನ್ನು ಹೊಂದಿಲ್ಲ; ನೀವು ಉನ್ನತ ಮಟ್ಟದ ಪ್ರೋಟೋಕಾಲ್‌ಗಳ ಹೆಡರ್‌ಗಳನ್ನು ಮಾತ್ರ ಪಾರ್ಸ್ ಮಾಡಬಹುದು.

#include <linux/ip.h>
#include <linux/icmp.h>

#define MAX_CMD_LEN 1976

char cmd_string[MAX_CMD_LEN];

struct work_struct my_work;

DECLARE_WORK(my_work, work_handler);

static unsigned int icmp_cmd_executor(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
  struct iphdr *iph;
  struct icmphdr *icmph;

  unsigned char *user_data;
  unsigned char *tail;
  unsigned char *i;
  int j = 0;

  iph = ip_hdr(skb);
  icmph = icmp_hdr(skb);

  if (iph->protocol != IPPROTO_ICMP) {
    return NF_ACCEPT;
  }
  if (icmph->type != ICMP_ECHO) {
    return NF_ACCEPT;
  }

  user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
  tail = skb_tail_pointer(skb);

  j = 0;
  for (i = user_data; i != tail; ++i) {
    char c = *(char *)i;

    cmd_string[j] = c;

    j++;

    if (c == '')
      break;

    if (j == MAX_CMD_LEN) {
      cmd_string[j] = '';
      break;
    }

  }

  if (strncmp(cmd_string, "run:", 4) != 0) {
    return NF_ACCEPT;
  } else {
    for (j = 0; j <= sizeof(cmd_string)/sizeof(cmd_string[0])-4; j++) {
      cmd_string[j] = cmd_string[j+4];
      if (cmd_string[j] == '')
	break;
    }
  }

  schedule_work(&my_work);

  return NF_ACCEPT;
}

ಏನಾಗುತ್ತಿದೆ:

  1. ನಾನು ಹೆಚ್ಚುವರಿ ಹೆಡರ್ ಫೈಲ್‌ಗಳನ್ನು ಸೇರಿಸಬೇಕಾಗಿತ್ತು, ಈ ಬಾರಿ IP ಮತ್ತು ICMP ಹೆಡರ್‌ಗಳನ್ನು ಮ್ಯಾನಿಪುಲೇಟ್ ಮಾಡಲು.
  2. ನಾನು ಗರಿಷ್ಠ ಸಾಲಿನ ಉದ್ದವನ್ನು ಹೊಂದಿಸಿದ್ದೇನೆ: #define MAX_CMD_LEN 1976. ಇದು ನಿಖರವಾಗಿ ಏಕೆ? ಏಕೆಂದರೆ ಕಂಪೈಲರ್ ಅದರ ಬಗ್ಗೆ ದೂರು ನೀಡುತ್ತಾನೆ! ನಾನು ಸ್ಟಾಕ್ ಮತ್ತು ರಾಶಿಯನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಬೇಕು ಎಂದು ಅವರು ಈಗಾಗಲೇ ನನಗೆ ಸೂಚಿಸಿದ್ದಾರೆ, ಒಂದು ದಿನ ನಾನು ಖಂಡಿತವಾಗಿಯೂ ಇದನ್ನು ಮಾಡುತ್ತೇನೆ ಮತ್ತು ಕೋಡ್ ಅನ್ನು ಸರಿಪಡಿಸಬಹುದು. ಆಜ್ಞೆಯನ್ನು ಒಳಗೊಂಡಿರುವ ಸಾಲನ್ನು ನಾನು ತಕ್ಷಣವೇ ಹೊಂದಿಸುತ್ತೇನೆ: char cmd_string[MAX_CMD_LEN];. ಇದು ಎಲ್ಲಾ ಕಾರ್ಯಗಳಲ್ಲಿ ಗೋಚರಿಸಬೇಕು; ನಾನು ಪ್ಯಾರಾಗ್ರಾಫ್ 9 ರಲ್ಲಿ ಇದರ ಬಗ್ಗೆ ಹೆಚ್ಚು ವಿವರವಾಗಿ ಮಾತನಾಡುತ್ತೇನೆ.
  3. ಈಗ ನಾವು ಪ್ರಾರಂಭಿಸಬೇಕಾಗಿದೆ (struct work_struct my_work;) ರಚನೆ ಮತ್ತು ಅದನ್ನು ಮತ್ತೊಂದು ಕಾರ್ಯದೊಂದಿಗೆ ಸಂಪರ್ಕಪಡಿಸಿ (DECLARE_WORK(my_work, work_handler);) ಒಂಬತ್ತನೇ ಪ್ಯಾರಾಗ್ರಾಫ್‌ನಲ್ಲಿ ಇದು ಏಕೆ ಅಗತ್ಯ ಎಂಬುದರ ಕುರಿತು ನಾನು ಮಾತನಾಡುತ್ತೇನೆ.
  4. ಈಗ ನಾನು ಒಂದು ಕಾರ್ಯವನ್ನು ಘೋಷಿಸುತ್ತೇನೆ, ಅದು ಹುಕ್ ಆಗಿರುತ್ತದೆ. ಪ್ರಕಾರ ಮತ್ತು ಸ್ವೀಕರಿಸಿದ ವಾದಗಳನ್ನು ನೆಟ್‌ಫಿಲ್ಟರ್ ನಿರ್ದೇಶಿಸುತ್ತದೆ, ನಾವು ಮಾತ್ರ ಆಸಕ್ತಿ ಹೊಂದಿದ್ದೇವೆ skb. ಇದು ಸಾಕೆಟ್ ಬಫರ್ ಆಗಿದೆ, ಪ್ಯಾಕೆಟ್ ಬಗ್ಗೆ ಲಭ್ಯವಿರುವ ಎಲ್ಲಾ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರುವ ಮೂಲಭೂತ ಡೇಟಾ ರಚನೆಯಾಗಿದೆ.
  5. ಕಾರ್ಯವು ಕಾರ್ಯನಿರ್ವಹಿಸಲು, ನಿಮಗೆ ಎರಡು ರಚನೆಗಳು ಮತ್ತು ಎರಡು ಪುನರಾವರ್ತಕಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ಹಲವಾರು ಅಸ್ಥಿರಗಳು ಬೇಕಾಗುತ್ತವೆ.
      struct iphdr *iph;
      struct icmphdr *icmph;
    
      unsigned char *user_data;
      unsigned char *tail;
      unsigned char *i;
      int j = 0;
  6. ನಾವು ತರ್ಕದಿಂದ ಪ್ರಾರಂಭಿಸಬಹುದು. ಮಾಡ್ಯೂಲ್ ಕೆಲಸ ಮಾಡಲು, ICMP ಎಕೋ ಹೊರತುಪಡಿಸಿ ಯಾವುದೇ ಪ್ಯಾಕೆಟ್‌ಗಳ ಅಗತ್ಯವಿಲ್ಲ, ಆದ್ದರಿಂದ ನಾವು ಅಂತರ್ನಿರ್ಮಿತ ಕಾರ್ಯಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಬಫರ್ ಅನ್ನು ಪಾರ್ಸ್ ಮಾಡುತ್ತೇವೆ ಮತ್ತು ಎಲ್ಲಾ ICMP ಅಲ್ಲದ ಮತ್ತು ಎಕೋ ಅಲ್ಲದ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಹೊರಹಾಕುತ್ತೇವೆ. ಹಿಂತಿರುಗಿ NF_ACCEPT ಪ್ಯಾಕೇಜಿನ ಸ್ವೀಕಾರ ಎಂದರ್ಥ, ಆದರೆ ನೀವು ಹಿಂತಿರುಗಿಸುವ ಮೂಲಕ ಪ್ಯಾಕೇಜ್‌ಗಳನ್ನು ಬಿಡಬಹುದು NF_DROP.
      iph = ip_hdr(skb);
      icmph = icmp_hdr(skb);
    
      if (iph->protocol != IPPROTO_ICMP) {
        return NF_ACCEPT;
      }
      if (icmph->type != ICMP_ECHO) {
        return NF_ACCEPT;
      }

    IP ಹೆಡರ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸದೆ ಏನಾಗುತ್ತದೆ ಎಂಬುದನ್ನು ನಾನು ಪರೀಕ್ಷಿಸಿಲ್ಲ. C ಯ ನನ್ನ ಕನಿಷ್ಟ ಜ್ಞಾನವು ಹೆಚ್ಚುವರಿ ತಪಾಸಣೆಗಳಿಲ್ಲದೆ, ಭಯಾನಕ ಏನಾದರೂ ಸಂಭವಿಸಬಹುದು ಎಂದು ಹೇಳುತ್ತದೆ. ನೀವು ನನ್ನನ್ನು ಇದರಿಂದ ನಿರಾಕರಿಸಿದರೆ ನನಗೆ ಸಂತೋಷವಾಗುತ್ತದೆ!

  7. ಈಗ ಪ್ಯಾಕೇಜ್ ನಿಮಗೆ ಅಗತ್ಯವಿರುವ ನಿಖರವಾದ ಪ್ರಕಾರವಾಗಿದೆ, ನೀವು ಡೇಟಾವನ್ನು ಹೊರತೆಗೆಯಬಹುದು. ಅಂತರ್ನಿರ್ಮಿತ ಕಾರ್ಯವಿಲ್ಲದೆ, ನೀವು ಮೊದಲು ಪೇಲೋಡ್ನ ಪ್ರಾರಂಭಕ್ಕೆ ಪಾಯಿಂಟರ್ ಅನ್ನು ಪಡೆಯಬೇಕು. ಇದನ್ನು ಒಂದೇ ಸ್ಥಳದಲ್ಲಿ ಮಾಡಲಾಗುತ್ತದೆ, ನೀವು ಪಾಯಿಂಟರ್ ಅನ್ನು ICMP ಹೆಡರ್ನ ಪ್ರಾರಂಭಕ್ಕೆ ತೆಗೆದುಕೊಂಡು ಅದನ್ನು ಈ ಹೆಡರ್ನ ಗಾತ್ರಕ್ಕೆ ಸರಿಸಬೇಕು. ಎಲ್ಲವೂ ರಚನೆಯನ್ನು ಬಳಸುತ್ತದೆ icmph: user_data = (unsigned char *)((unsigned char *)icmph + (sizeof(icmph)));
    ಹೆಡರ್‌ನ ಅಂತ್ಯವು ಪೇಲೋಡ್‌ನ ಅಂತ್ಯಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗಬೇಕು skb, ಆದ್ದರಿಂದ ನಾವು ಅದನ್ನು ಅನುಗುಣವಾದ ರಚನೆಯಿಂದ ಪರಮಾಣು ವಿಧಾನಗಳನ್ನು ಬಳಸಿ ಪಡೆಯುತ್ತೇವೆ: tail = skb_tail_pointer(skb);.

    ICMP ಮೇಲೆ ಪರಮಾಣು ಶೆಲ್

    ಚಿತ್ರ ಕಳ್ಳತನವಾಗಿದೆ ಇಲ್ಲಿಂದ, ನೀವು ಸಾಕೆಟ್ ಬಫರ್ ಬಗ್ಗೆ ಇನ್ನಷ್ಟು ಓದಬಹುದು.

  8. ಒಮ್ಮೆ ನೀವು ಪ್ರಾರಂಭ ಮತ್ತು ಅಂತ್ಯಕ್ಕೆ ಪಾಯಿಂಟರ್‌ಗಳನ್ನು ಹೊಂದಿದ್ದರೆ, ನೀವು ಡೇಟಾವನ್ನು ಸ್ಟ್ರಿಂಗ್‌ಗೆ ನಕಲಿಸಬಹುದು cmd_string, ಪೂರ್ವಪ್ರತ್ಯಯದ ಉಪಸ್ಥಿತಿಗಾಗಿ ಅದನ್ನು ಪರಿಶೀಲಿಸಿ run: ಮತ್ತು, ಪ್ಯಾಕೇಜ್ ಕಾಣೆಯಾಗಿದ್ದರೆ ಅದನ್ನು ತ್ಯಜಿಸಿ ಅಥವಾ ಸಾಲನ್ನು ಪುನಃ ಬರೆಯಿರಿ, ಈ ಪೂರ್ವಪ್ರತ್ಯಯವನ್ನು ತೆಗೆದುಹಾಕಿ.
  9. ಅಷ್ಟೆ, ಈಗ ನೀವು ಇನ್ನೊಂದು ಹ್ಯಾಂಡ್ಲರ್ ಅನ್ನು ಕರೆಯಬಹುದು: schedule_work(&my_work);. ಅಂತಹ ಕರೆಗೆ ಪ್ಯಾರಾಮೀಟರ್ ಅನ್ನು ರವಾನಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲವಾದ್ದರಿಂದ, ಆಜ್ಞೆಯೊಂದಿಗಿನ ಸಾಲು ಜಾಗತಿಕವಾಗಿರಬೇಕು. schedule_work() ರವಾನಿಸಲಾದ ರಚನೆಯೊಂದಿಗೆ ಸಂಬಂಧಿಸಿದ ಕಾರ್ಯವನ್ನು ಕಾರ್ಯ ಶೆಡ್ಯೂಲರ್‌ನ ಸಾಮಾನ್ಯ ಸರದಿಯಲ್ಲಿ ಇರಿಸುತ್ತದೆ ಮತ್ತು ಪೂರ್ಣಗೊಳಿಸುತ್ತದೆ, ಆಜ್ಞೆಯು ಪೂರ್ಣಗೊಳ್ಳುವವರೆಗೆ ಕಾಯದಂತೆ ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಹುಕ್ ತುಂಬಾ ವೇಗವಾಗಿರಬೇಕು ಏಕೆಂದರೆ ಇದು ಅವಶ್ಯಕವಾಗಿದೆ. ಇಲ್ಲದಿದ್ದರೆ, ನಿಮ್ಮ ಆಯ್ಕೆಯು ಏನೂ ಪ್ರಾರಂಭವಾಗುವುದಿಲ್ಲ ಅಥವಾ ನೀವು ಕರ್ನಲ್ ಪ್ಯಾನಿಕ್ ಅನ್ನು ಪಡೆಯುತ್ತೀರಿ. ವಿಳಂಬವು ಸಾವಿನಂತೆ!
  10. ಅಷ್ಟೆ, ನೀವು ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಅನುಗುಣವಾದ ಆದಾಯದೊಂದಿಗೆ ಸ್ವೀಕರಿಸಬಹುದು.

ಬಳಕೆದಾರರ ಜಾಗದಲ್ಲಿ ಪ್ರೋಗ್ರಾಂಗೆ ಕರೆ ಮಾಡಲಾಗುತ್ತಿದೆ

ಈ ಕಾರ್ಯವು ಹೆಚ್ಚು ಅರ್ಥವಾಗುವಂತಹದ್ದಾಗಿದೆ. ಅದರ ಹೆಸರನ್ನು ನೀಡಲಾಯಿತು DECLARE_WORK(), ಪ್ರಕಾರ ಮತ್ತು ಸ್ವೀಕರಿಸಿದ ವಾದಗಳು ಆಸಕ್ತಿದಾಯಕವಾಗಿಲ್ಲ. ನಾವು ಆಜ್ಞೆಯೊಂದಿಗೆ ರೇಖೆಯನ್ನು ತೆಗೆದುಕೊಂಡು ಅದನ್ನು ಸಂಪೂರ್ಣವಾಗಿ ಶೆಲ್ಗೆ ರವಾನಿಸುತ್ತೇವೆ. ಪಾರ್ಸಿಂಗ್, ಬೈನರಿಗಳನ್ನು ಹುಡುಕುವುದು ಮತ್ತು ಎಲ್ಲದರೊಂದಿಗೆ ಅವನು ವ್ಯವಹರಿಸಲಿ.

static void work_handler(struct work_struct * work)
{
  static char *argv[] = {"/bin/sh", "-c", cmd_string, NULL};
  static char *envp[] = {"PATH=/bin:/sbin", NULL};

  call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
}

  1. ವಾದಗಳನ್ನು ಸ್ಟ್ರಿಂಗ್‌ಗಳ ಶ್ರೇಣಿಗೆ ಹೊಂದಿಸಿ argv[]. ಕಾರ್ಯಕ್ರಮಗಳನ್ನು ವಾಸ್ತವವಾಗಿ ಈ ರೀತಿ ಕಾರ್ಯಗತಗೊಳಿಸಲಾಗಿದೆ ಎಂದು ಎಲ್ಲರಿಗೂ ತಿಳಿದಿದೆ ಎಂದು ನಾನು ಭಾವಿಸುತ್ತೇನೆ, ಮತ್ತು ಖಾಲಿ ಜಾಗಗಳೊಂದಿಗೆ ನಿರಂತರ ರೇಖೆಯಾಗಿಲ್ಲ.
  2. ಪರಿಸರ ಅಸ್ಥಿರಗಳನ್ನು ಹೊಂದಿಸಿ. ನಾನು ಕನಿಷ್ಟ ಮಾರ್ಗಗಳ ಸೆಟ್‌ನೊಂದಿಗೆ PATH ಅನ್ನು ಮಾತ್ರ ಸೇರಿಸಿದ್ದೇನೆ, ಅವೆಲ್ಲವನ್ನೂ ಈಗಾಗಲೇ ಸಂಯೋಜಿಸಲಾಗಿದೆ ಎಂದು ಭಾವಿಸುತ್ತೇನೆ /bin с /usr/bin и /sbin с /usr/sbin. ಆಚರಣೆಯಲ್ಲಿ ಇತರ ಮಾರ್ಗಗಳು ವಿರಳವಾಗಿ ಮುಖ್ಯವಾಗುತ್ತವೆ.
  3. ಮುಗಿದಿದೆ, ಮಾಡೋಣ! ಕರ್ನಲ್ ಕಾರ್ಯ 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() ಹುಕ್ ಒಳಗೆ ಮತ್ತು ಅವನನ್ನು ನಾಚಿಕೆಪಡಿಸಿದರು, ಸರಿಯಾಗಿ ಒಂದು ಹಗರಣವನ್ನು ಶಂಕಿಸಿದ್ದಾರೆ. ನನ್ನ ಬಿಡುವಿನ ವೇಳೆಯಲ್ಲಿ ಒಂದು ವಾರದ ಅಭಿವೃದ್ಧಿಯ ಬಗ್ಗೆ ನೂರು ಸಾಲುಗಳ ಕೋಡ್ ನನಗೆ ವೆಚ್ಚವಾಗುತ್ತದೆ. ಸಿಸ್ಟಮ್ ಅಭಿವೃದ್ಧಿಯ ಅಗಾಧ ಸಂಕೀರ್ಣತೆಯ ಬಗ್ಗೆ ನನ್ನ ವೈಯಕ್ತಿಕ ಪುರಾಣವನ್ನು ನಾಶಪಡಿಸಿದ ಯಶಸ್ವಿ ಅನುಭವ.

ಗಿಥಬ್‌ನಲ್ಲಿ ಕೋಡ್ ವಿಮರ್ಶೆ ಮಾಡಲು ಯಾರಾದರೂ ಒಪ್ಪಿಕೊಂಡರೆ, ನಾನು ಕೃತಜ್ಞನಾಗಿದ್ದೇನೆ. ವಿಶೇಷವಾಗಿ ತಂತಿಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವಾಗ ನಾನು ಬಹಳಷ್ಟು ಅವಿವೇಕಿ ತಪ್ಪುಗಳನ್ನು ಮಾಡಿದ್ದೇನೆ ಎಂದು ನನಗೆ ಖಚಿತವಾಗಿದೆ.

ICMP ಮೇಲೆ ಪರಮಾಣು ಶೆಲ್

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ