시스템 보안 운영 - 4 (교육 86일차)

SW·2023년 4월 1일
0

실습>

/root/rpmbuild/SOURCES/Linux-PAM-1.1.8/modules/pam_rootok

-- pam_rootok.c --
 43 static int
 44 _pam_parse (const pam_handle_t *pamh, int argc, const char **argv)
 45 {
 46     int ctrl=0;
 47 
 48     printf("%s: _pam_parse() 실행!\n", __FILE__);
 49
 50     /* step through arguments */
 51     for (ctrl=0; argc-- > 0; ++argv) {


157 PAM_EXTERN int
158 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
159              int argc, const char **argv)
160 {
161     int ctrl;
162 
163     printf("%s: pam_sm_authenticate() 실행!\n", __FILE__);
164     ctrl = _pam_parse(pamh, argc, argv);
165 
166     return check_for_root (pamh, ctrl);
167 }
168 

-- pam_rootok.c --

[root@localhost pam_rootok]# PAMCompile.sh 

-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 #auth        requisite   pam_deny.so
  3 auth        sufficient  pam_rootok.so
-- /etc/pam.d/su --

3번 라인에 의해서 root 이므로 관리자로 변경되었다.
[root@localhost ~]# su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 09:36:50 KST 2023 일시 pts/2
[root@localhost ~]# exit


-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 auth        requisite   pam_deny.so
  3 auth        sufficient  pam_rootok.so
-- /etc/pam.d/su --

2번 라인의 requisite은 실패이면 무조건 인증이 실패하고 종료하므로 그 다음 모듈을 검사하지 않는다.
[root@localhost ~]# su -
su: 인증 실패
   <-- pam_rootok.c: pam_sm_authenticate() 실행! 부분이 출력되지 않음.


-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 auth        requisite   pam_permit.so  <-- 성공이므로 다음 모듈인 pam_rootok.so를 실행한다.
  3 auth        sufficient  pam_rootok.so
-- /etc/pam.d/su --

[root@localhost ~]# ps
   PID TTY          TIME CMD
  1286 pts/2    00:00:00 bash
  1632 pts/2    00:00:00 ps
[root@localhost ~]# su - 
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 09:47:32 KST 2023 일시 pts/2
[root@localhost ~]# ps
   PID TTY          TIME CMD
  1286 pts/2    00:00:00 bash
  1633 pts/2    00:00:00 su
  1634 pts/2    00:00:00 bash
  1651 pts/2    00:00:00 ps
[root@localhost ~]# exit


pam_permit.c 수정
[root@localhost pam_permit]# pwd
/root/rpmbuild/SOURCES/Linux-PAM-1.1.8/modules/pam_permit

-- pam_permit.c --
 35 PAM_EXTERN int
 36 pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
 37             int argc UNUSED, const char **argv UNUSED)
 38 {
 39     int retval;
 40     const char *user=NULL;
 41 
 42     printf(">>> %s : pam_sm_authenticate() 호출! <<<\n", __FILE__);


 72 PAM_EXTERN int
 73 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
 74            int argc UNUSED, const char **argv UNUSED)
 75 {
 76     printf(">>> %s : pam_sm_setcred() 호출! <<<\n", __FILE__);
 77      return PAM_SUCCESS;
 78 }
-- pam_permit.c --

[root@localhost pam_permit]# PAMCompile.sh 

다른 터미널에서 su -  실행한다.
[root@localhost ~]# su -
>>> pam_permit.c : pam_sm_authenticate() 호출! <<<  <-- requisite에 의해 성공을 했으므로 
pam_rootok.c: pam_sm_authenticate() 실행!            <-- pam_rootok.so 모듈을 실행한다.
pam_rootok.c: _pam_parse() 실행!
>>> pam_permit.c : pam_sm_setcred() 호출! <<<
마지막 로그인: 목  3월 30 10:04:32 KST 2023 일시 pts/1
[root@localhost ~]# 


o auth sufficient  pam_deny.so 일 경우
-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 auth        sufficient  pam_deny.so
  3 auth        sufficient  pam_rootok.so
-- /etc/pam.d/su --

[root@localhost pam_deny]# pwd
/root/rpmbuild/SOURCES/Linux-PAM-1.1.8/modules/pam_deny

-- pam_deny.c --
 28 PAM_EXTERN int
 29 pam_sm_authenticate(pam_handle_t *pamh UNUSED, int flags UNUSED,
 30             int argc UNUSED, const char **argv UNUSED)
 31 {
 32     printf(">>> %s: pam_sm_authenticate() 호출! <<<\n", __FILE__);
 33     return PAM_AUTH_ERR;
 34 }   
 35 
 36 PAM_EXTERN int
 37 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
 38            int argc UNUSED, const char **argv UNUSED)
 39 {
 40     printf(">>> %s: pam_sm_setcred() 호출! <<<\n", __FILE__);
 41      return PAM_CRED_ERR;
 42 }
-- pam_deny.c --

[root@localhost pam_deny]# PAMCompile.sh 

다른 터미널에서 su -  실행한다.
[root@localhost ~]# su - user1
>>> pam_deny.c: pam_sm_authenticate() 호출! <<<
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
>>> pam_deny.c: pam_sm_setcred() 호출! <<<
마지막 로그인: 목  3월 30 09:47:22 KST 2023 일시 pts/2
[user1@localhost ~]$ exit


o auth sufficient  pam_permit.so 일 경우
-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 auth        sufficient  pam_permit.so   <-- sufficient에 의해 성공을 했으므로 
  3 auth        sufficient  pam_rootok.so   <-- pam_rootok.so를 실행하지 않고 인증에 성공한다.
-- /etc/pam.d/su --

[root@localhost ~]# su - user1
>>> pam_permit.c : pam_sm_authenticate() 호출! <<<  <-- sufficient에 의해 성공을 했으므로 
>>> pam_permit.c : pam_sm_setcred() 호출! <<<       <-- pam_rootok.so를 실행하지 않고 인증에 성공한다.
마지막 로그인: 목  3월 30 10:24:34 KST 2023 일시 pts/1
[user1@localhost ~]$ exit  <-- 그러므로 user1의 셸이 떨어진 것이다.

o  auth required pam_deny.so 인 경우
-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 auth        required    pam_deny.so    <-- required 에 의해 실패를 했으므로 
  3 auth        sufficient  pam_rootok.so  <-- 여기서 성공을 해도 최종적으로 인증은 실패가 된다.
-- /etc/pam.d/su --

[root@localhost ~]# passwd --stdin user1
user1 사용자의 비밀 번호 변경 중
111111
passwd: 모든 인증 토큰이 성공적으로 업데이트 되었습니다.

[root@localhost ~]# su - user1
>>> pam_deny.c: pam_sm_authenticate() 호출! <<<
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:  <-- 111111  입력한다.
>>> pam_deny.c: pam_sm_authenticate() 호출! <<<
su: 인증 실패
[root@localhost ~]#

o  auth required pam_permit.so 인 경우
-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 auth        required    pam_permit.so  <-- required 에 의해 성공을 했지만 최종 성공은 아니고 다음 모듈을 본다.
  3 auth        sufficient  pam_rootok.so  <-- sufficient에 의해서 인증이 성공된다.
-- /etc/pam.d/su --

[root@localhost ~]# su - user1
>>>  pam_permit.c : pam_sm_authenticate() 호출! <<<  <-- required  의해 성공을 했지만 최종 성공을 아니다.
pam_rootok.c: pam_sm_authenticate() 실행!    <-- sufficient에 의해서 root이므로 인증이 최종 성공된다.
pam_rootok.c: _pam_parse() 실행!
>>> pam_permit.c : pam_sm_setcred() 호출! <<<
마지막 로그인: 목  3월 30 10:42:10 KST 2023 일시 pts/1
마지막 로그인 실패: 목  3월 30 10:43:02 KST 2023 일시 pts/1 
마지막 로그인 후 1 번의 로그인 시도가 실패하였습니다.  
[user1@localhost ~]$ 

실습> pam_wheel.so

[root@localhost ~]# usermod -G wheel user1
[root@localhost ~]# usermod -G wheel user2

-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 #auth        required    pam_permit.so
  3 auth        sufficient  pam_rootok.so
  4 # Uncomment the following line to implicitly trust     users in the "wheel" group.
  5 #auth       sufficient  pam_wheel.so trust use_uid
  6 # Uncomment the following line to require a user to     be in the "wheel" group.
  7 auth        required    pam_wheel.so use_uid
  8 auth        substack    system-auth
-- /etc/pam.d/su --


[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 12:04:28 KST 2023 일시 pts/1
[user1@localhost ~]$ id
uid=1001(user1) gid=1001(user1) groups=1001(user1),10(wheel) ...(생략)
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목  3월 30 11:56:52 KST 2023 일시 pts/1
마지막 로그인 실패: 목  3월 30 11:57:33 KST 2023 일시 pts/1 
[root@localhost ~]# exit
[user1@localhost ~]$ exit

[root@localhost ~]# su - user2
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 11:58:39 KST 2023 일시 pts/1
[user2@localhost ~]$ id
uid=1002(user2) gid=1002(user2) groups=1002(user2),10(wheel) ...(생략)
[user2@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목  3월 30 12:04:53 KST 2023 일시 pts/1
마지막 로그인 실패: 목  3월 30 12:07:32 KST 2023 일시 pts/1 
[root@localhost ~]# exit
[user2@localhost ~]$ exit

실습> pam_succeed_if.so

if문으로 여러가지 값들을 비교할 때 사용하는 모듈

[root@localhost ~]# usermod -G wheel user1
[root@localhost ~]# usermod -G wheel user2

OPTION
- use_uid: 명령어를 실행하는 사용자를 의미한다.

-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 #auth        required    pam_permit.so
  3 auth        sufficient  pam_rootok.so   <-- user2 사용자는 인증실패
  4 # Uncomment the following line to implicitly trust users in the "wheel" group.
  5 #auth       sufficient  pam_wheel.so trust use_uid
  6 # Uncomment the following line to require a user to be in the "wheel" group.
  7 # wheel(user1,user2)
  8 # user1 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
  9 # user2 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
 10 auth        required    pam_succeed_if.so user = user1 use_uid  <-- user2 사용자는 인증실패
 11 auth        required    pam_wheel.so use_uid    <-- user2 사용자는 인증성공      
 12 auth        substack    system-auth <-- 비밀번호 입력 user2 사용자는 인증성공을 했지만 10번 때문에 최종 실패

>>> 12번 위에 required 부분 10번에 실패가 있기 때문에 12번에서 최종 인증에 실패하므로 로그인이 안된다. <<<

>>> 12번에서 입력된 root의 비밀번호 검증이 맞으면 12번 위에 10번과 11번의 required 부분에 <<<
>>> 실패가 있는지 확인하고 실패가 있다면 12번에서 최종 인증에 실패하므로 root로 로그인할 수 없다. <<<
-- /etc/pam.d/su --

[root@localhost ~]# su - user2
[user2@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
su: 인증 실패
[user2@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
su: 인증 실패
[user2@localhost ~]$ exit


-- /etc/pam.d/su --
  1 #%PAM-1.0
  2 #auth        required    pam_permit.so
  3 auth        sufficient  pam_rootok.so   <-- user1 사용자는 인증실패
  4 # Uncomment the following line to implicitly trust users in the "wheel" group.
  5 #auth       sufficient  pam_wheel.so trust use_uid
  6 # Uncomment the following line to require a user to be in the "wheel" group.
  7 # wheel(user1,user2)
  8 # user1 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
  9 # user2 사용자는 wheel 그룹에 포함되므로 root로 변경가능 O
 10 auth        required    pam_succeed_if.so user = user1 use_uid  <-- user1 사용자는 인증성공
 11 auth        required    pam_wheel.so use_uid    <-- user1 사용자는 인증성공      
 12 auth        substack    system-auth <-- user1 사용자는 비밀번호 검증에 성공을 했고 10,11번을 체크한  최종 성공

>>> 12번에서 입력된 root의 비밀번호 검증이 맞으면 12번 위에 10번과 11번의 required 부분에 <<<
>>> 실패가 없는지 확인하고 실패가 없다면 12번에서 최종 인증에 성공하므로 root로 로그인할 수 있다. <<<
-- /etc/pam.d/su --

[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 12:04:40 KST 2023 일시 pts/1
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목  3월 30 12:22:31 KST 2023 일시 pts/1
마지막 로그인 실패: 목  3월 30 12:31:54 KST 2023 일시 pts/1 
마지막 로그인 후 4 번의 로그인 시도가 실패하였습니다.  
[root@localhost ~]# 

실습> 라이브러리 경로 변경하기

-- /etc/pam.d/su --
 11 auth        required    /libmydir/pam_wheel.so use_uid
 12 auth        substack    system-auth
-- /etc/pam.d/su --

# mkdir /libmydir
# mv /usr/lib64/security/pam_wheel.so /libmydir/

# yum -y install tree
# tree /libmydir/
/libmydir/
└── pam_wheel.so

0 directories, 1 file

# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 14:57:16 KST 2023 일시 pts/0
[user1@localhost ~]$
[user1@localhost ~]$ id
uid=1001(user1) gid=1001(user1) groups=1001(user1),10(wheel) ...(생략)
    

[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목  3월 30 14:57:19 KST 2023 일시 pts/0
[root@localhost ~]# exit
[user1@localhost ~]$ exit

라이브러리 경로를 원래대로 돌려놓는다.
- 모듈의 경로가 상대경로 이므로 /usr/lib64/security에서 찾는다.
[root@localhost ~]# vi /etc/pam.d/su
 11 auth        required    pam_wheel.so use_uid
 12 auth        substack    system-auth

[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 15:14:57 KST 2023 일시 pts/0
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
su: 모듈을 알 수 없음  <-- /usr/lib64/security/pam_wheel.so 파일이 없기 때문에 에러가 발생한 것이다.
[user1@localhost ~]$ exit

pam_wheel.so 파일을 원래 디렉터리인 /usr/lib64/security 디렉터리에 이동시킨다.
[root@localhost ~]# mv /libmydir/pam_wheel.so /usr/lib64/security/

[root@localhost ~]# su - user1
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
마지막 로그인: 목  3월 30 15:17:08 KST 2023 일시 pts/0
[user1@localhost ~]$ su -
pam_rootok.c: pam_sm_authenticate() 실행!
pam_rootok.c: _pam_parse() 실행!
암호:
마지막 로그인: 목  3월 30 15:15:32 KST 2023 일시 pts/0
마지막 로그인 실패: 목  3월 30 15:17:14 KST 2023 일시 pts/0
마지막 로그인 후 1 번의 로그인 시도가 실패하였습니다.
[root@localhost ~]#  <-- 정상적으로 root로 변경된 것을 확인할 수 있다.

실습> salt값에 의한 해쉬값 확인하기

# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$oE7VchFp72O2w2Oi$Y1UdgnitPA04bqADoVKWdEGK3SRRteUMzY4HHVWpDgvXAE3YVaxWeI0yqltU9eYypdCxfdhoAKje9wNioyK0p1:19310:0:99999:7:::
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$RLIGFZB9Bkhmteiz$.TXpkv6jElyuWeXOlQZRB2RNGGxElVB/TsJ84N.Z0MwQc02FBeBUlEq0O7besapj13lseDYZU0ughSXMpwx8B0:19310:0:99999:7:::
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$4DF2Zt1qlu8SEXUp$X4aHQJvEB47WgbhBGpgEjf8TaxjB6wxxSWye0YFzFDU5vCO6toL00hlQ7fDmo2yZniwPy8CNwUCjsRLJY7Irw/:19310:0:99999:7:::
# echo 1 | passwd --stdin root
# head -1 /etc/shadow
root:$6$jq0qV0QeVhD/AZuA$CbPvUqdomVPtX5byvVuX3JHFcWgLRtEFYxjG5Jx/KH/.UqVZ9KpCEqdv2zoNQa394/.y.cigOOWpBC8OHBGUz/:19310:0:99999:7:::

실습> crypt() 함수 사용하기

crypt(): Linux에서 비밀번호 해쉬값을 만들어주는 함수
/etc/shadow: 두 번째 필드에 암호화된 해쉬값(비밀번호)이 저장되어 있다.
형식: $암호화방식$salt$암호화된해쉬값
해쉬값은 해쉬함수에 의해서 만들어진 값으로 임의의 문자열(개수가 정해지지 않음)을 넣으면 고정길이로 뽑아주는 함수
해쉬는 단반향 암호화이므로 역으로 풀 수 없다. 풀 수 있다면 암호화가 깨진 것이다.

1. 사용자 생성
passuser 사용자를 생성한다.
/etc/shadow 파일에 저장된 비밀번호는 각 시스템마다 모두 다르다.
# useradd passuser
# tail -1 /etc/shadow
passuser:!!:19093:0:99999:7:::
         ~~

2. 비밀번호 확인
# echo 111111 | passwd --stdin passuser
# tail -1 /etc/shadow
passuser:$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.:19446:0:99999:7:::

3. 프로그램 작성
# yum -y install man man-pages gcc make

# vi pass.c
/*
 * 파일명: pass.c
 * 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
 * 작성자: 리눅스마스터넷
 * 
 * char *crypt(const char *key, const char *salt);
 * 컴파일 방법: gcc -o pass pass.c -lcrypt
 */
#include<stdio.h>
#include<string.h>
#include<crypt.h>

int main()
{
    char pPass[] = "111111";
    char pSalt[] = "$6$5dzvDOsn$"; // 각자 다르므로 자신의 것으로 설정한다.

    char *pHash;
    pHash = crypt(pPass,pSalt);

    printf("%s \n",pHash);

    return 0;
}

-l라이브러리명 : crypt 라이브러리
# ll /usr/lib64/libcrypt*
# ll /usr/lib64/libcrypt-2.17.so
# gcc -o pass pass.c -lcrypt
# ldd pass
	linux-vdso.so.1 =>  (0x00007fff10baf000)
	libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fafbc0d8000)  <-- crypt 라이브러리
	libc.so.6 => /lib64/libc.so.6 (0x00007fafbbd0a000)
	libfreebl3.so => /lib64/libfreebl3.so (0x00007fafbbb07000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fafbc30f000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007fafbb903000)

4. 실행
실행파일 pass를 실행해서 /etc/shadow에 저장된 passuser와 암호가 동일한지 확인한다.
# tail -1 /etc/shadow
passuser:$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.:19446:0:99999:7:::
# tail -1 /etc/shadow|awk -F : '{print $2}'
$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.

# ./pass 
$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9FDYD2Yh5on5636FCj8BMjY4jfrY0qyeCCCWpSqvOq1xyAXp.

실습> crypt() 함수 사용하기

1. 소스코드 작성
# vi pass2.c 
/*
 * 파일명: pass2.c
 * 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
 * 작성자: 리눅스마스터넷
 * 
 * char *crypt(const char *key, const char *salt);
 * 컴파일 방법: gcc -o pass2 pass2.c -lcrypt
 */
#include<stdio.h>
#include<string.h>
#include<crypt.h>

int main(int argc, char *argv[])
{
    if(argc != 2) {
        fprintf(stderr, "Usage: %s password\n", argv[0]);
        return 1;
    }

    // passuser:$6$5dzvDOsn$XbVcSVAWJRqDKujU1EqeIsMyjyf7Geqf1BEoK9 ...(생략)
    char *pPass = argv[1];
    char pSalt[] = "$6$5dzvDOsn$"; // 각자 다르므로  grep passuser /etc/shadow 로 확인한다.
    char *pHash;

    pHash = crypt(pPass,pSalt);
    printf("passuser:%s \n",pHash);

    return 0;
}

2. 컴파일
컴파일할 때 -lcrpyt 라이브러리를 포함시킨다.
라이브러리 위치: /usr/lib64, /lib64(/usr/lib64 심볼릭 링크)
# gcc -o pass2 pass2.c -lcrypt

3. 실행
# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::

# ./pass2 111111
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.

사용자 passuser2를 생성하고 비밀번호를 111111 로 설정한다.
# useradd passuser2
# passwd --stdin passuser2
passuser2 사용자의 비밀 번호 변경 중
111111
passwd: 모든 인증 토큰이 성공적으로 업데이트 되었습니다.

# grep passuser2 /etc/shadow
passuser2:$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.:19435:0:99999:7:::

pass2.c 의 소스에서 아래 두 군데만 passuser2 로 변경한다. 변경한다.
    
-- pass2.c 의 수정된 소스 --
    char pSalt[] = "$6$2AO8Wuo7$"; // passuser2의 salt 값으로 변경한다.
    
    printf("passuser2:%s \n",pHash);
-- pass2.c 의 수정된 소스 --

# gcc -o pass2 pass2.c -lcrypt
# grep passuser2 /etc/shadow
passuser2:$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.:19435:0:99999:7:::
# ./pass2 111111
passuser2:$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.

실습> salt 값이 없는 경우

1. 소스코드 작성
# vi pass3.c 
/*
 * 파일명: pass3.c
 * 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
 * 작성자: 리눅스마스터넷
 * 
 * char *crypt(const char *key, const char *salt);
 * 컴파일 방법: gcc -o pass3 pass3.c -lcrypt
 */
#include<stdio.h>
#include<string.h>
#include<crypt.h>

int main(int argc, char *argv[])
{
    if(argc != 2) {
        fprintf(stderr, "Usage: %s password\n", argv[0]);
        return 1;
    }

    char *pPass = argv[1];
    char pSalt[] = "$6$"; // salt 값이 없는 경우

    char *pHash;
    pHash = crypt(pPass,pSalt);

    printf("%s \n",pHash);

    return 0;
}

2. 컴파일
# gcc -o pass3 pass3.c -lcrypt

3. 실행
salt의 추가 첨가물 문자열이 없다면 해쉬값은 동일하게 나온다.
# ./pass3 111111
$6$$2/5CYZn./k1Kv4XDyvESqncw9V8t.hF3Ah7VtjJXlOM9ao4r9S79dvNnYa4LVRA8pUzrdymGll1PY245M9J8e. 
# ./pass3 111111
$6$$2/5CYZn./k1Kv4XDyvESqncw9V8t.hF3Ah7VtjJXlOM9ao4r9S79dvNnYa4LVRA8pUzrdymGll1PY245M9J8e. 
# ./pass3 111111
$6$$2/5CYZn./k1Kv4XDyvESqncw9V8t.hF3Ah7VtjJXlOM9ao4r9S79dvNnYa4LVRA8pUzrdymGll1PY245M9J8e. 

실습> strcat 함수 사용하기

함수의 원형: 
#include <string.h>
char *strcat(char *dest, const char *src);

함수 기능: 
src 값을 dest에 연결한다.
리턴값: dest 

# vi strcatTest.c
#include <stdio.h>
#include <string.h>

int main()
{
    // char *strcat(char *dest, const char *src);
    char src[50]  = "ABC"; 
    char dest[50] = "12345";
    
    printf("%s\n", strcat(dest, src));  // 12345ABC

    printf("src: %s\n", src);   // ABC
    printf("dest: %s\n", dest); // 12345ABC 

    return 0;
}
# gcc -o strcatTest strcatTest.c 
# ./strcatTest 
12345ABC
src: ABC
dest: 12345ABC


# gdb strcatTest
Reading symbols from /root/strcatTest...done.
(gdb) b main

(gdb) i b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x08048478 in main at strcatTest.c:7

(gdb) r
7           char src[50]  = "ABC";

(gdb) n
8           char dest[50] = "12345";

(gdb) n
10          printf("%s\n", strcat(dest, src));  // 12345ABC

(gdb) p src
$1 = "ABC", '\000' <repeats 46 times>

(gdb) p dest
$2 = "12345", '\000' <repeats 44 times>

(gdb) p &src
$3 = (char (*)[50]) 0xffffd5fe

(gdb) p &dest
$4 = (char (*)[50]) 0xffffd5cc

(gdb) x/16xw &src
0xffffd5fe:     0x00434241      0x00000000      0x00000000      0x00000000
0xffffd60e:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd61e:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd62e:     0xe0000000      0x0000f7fc      0x00000000      0x12d30000
(gdb) x/xb 0xffffd5fe
0xffffd5fe:     0x41
(gdb) x/xb 0xffffd5ff
0xffffd5ff:     0x42
(gdb) x/xb 0xffffd600
0xffffd600:     0x43
(gdb) x/xb 0xffffd601
0xffffd601:     0x00
(gdb) x/16xw 0xffffd5cc
0xffffd5cc:     0x34333231      0x00000035      0x00000000      0x00000000
0xffffd5dc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd5ec:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd5fc:     0x42410000      0x00000043      0x00000000      0x00000000
(gdb) x/xb 0xffffd5cc
0xffffd5cc:     0x31
(gdb) x/xb 0xffffd5cd
0xffffd5cd:     0x32
(gdb) x/xb 0xffffd5ce
0xffffd5ce:     0x33
(gdb) x/xb 0xffffd5cf
0xffffd5cf:     0x34
(gdb) x/xb 0xffffd5d0
0xffffd5d0:     0x35
(gdb) x/xb 0xffffd5d1
0xffffd5d1:     0x00
(gdb) n
12345ABC
12          printf("src: %s\n", src);   // ABC
(gdb) x/16xw &src
0xffffd5fe:     0x00434241      0x00000000      0x00000000      0x00000000
0xffffd60e:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd61e:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd62e:     0xe0000000      0x0000f7fc      0x00000000      0x12d30000
(gdb) x/16xw &dest
0xffffd5cc:     0x34333231      0x43424135      0x00000000      0x00000000
0xffffd5dc:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd5ec:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd5fc:     0x42410000      0x00000043      0x00000000      0x00000000
(gdb) c
Continuing.
src: ABC
dest: 12345ABC
[Inferior 1 (process 1407) exited normally]

(gdb) q

실습> 동적으로 암호와 salt를 입력 받아서 사용하기

1. 소스 코드 작성
# vi pass4.c 
/*
 * 파일명: pass4.c
 * 프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
 * 작성자: 리눅스마스터넷
 * 
 * char *crypt(const char *key, const char *salt);
 * 컴파일 방법: gcc -o pass4 pass4.c -lcrypt
 */
#include<stdio.h>
#include<string.h>
#include<crypt.h>

int main(int argc, char *argv[])
{
    if(argc != 3) {
        fprintf(stderr, "Usage: %s password salt\n", argv[0]);
        return 1;
    }

    char *pPass = argv[1];
    char pSalt[20] = "$6$";
    char *pHash;

    strcat(pSalt, argv[2]); 
    pHash = crypt(pPass,pSalt);

    printf("%s \n",pHash);

    return 0;
}

2. 컴파일
# gcc -m32 -g -o pass4 pass4.c -lcrypt

3. 실행
# ./pass4 
Usage: ./pass4 password salt
# ./pass4 111111
Usage: ./pass4 password salt

테스트는 직접 본인의 비밀번호로 작업한다.
# tail -1 /etc/shadow|awk -F : '{print $2}'
$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.

# ./pass4 111111 2AO8Wuo7
$6$2AO8Wuo7$BrDrs38fP7pu9DQwMKObc81YPCEeXtYGJdrEJMqSb9T7inkJh.6qY65kbtic4IYLPp8UMpBwQg36mgU18edKE.

# ./pass4 111111 jp3hK011
$6$jp3hK011$hgouaAnJ8TBWVlmD8EcU7s6f2utk7hkEfKH.WTevrZfBrVTqU5T8Ja8hdZ9EqqyD50Ivm4VXl6SzVMCkC6jPK. 

실습> 파이썬으로 변경하기

python crypt() 함수
https://docs.python.org/ko/3/library/crypt.html

파일명: 
pass.c  -> pass.py
pass4.c -> pass4.py

파이썬 가상환경을 만들고 소스코드를 만든다.

1. 파이썬 3.8 설치
yum -y install centos-release-scl
yum -y install rh-python38
scl enable rh-python38 bash
echo 'scl enable rh-python38 bash' >> ~/.bash_profile
python --version

2. 가상환경 생성
# python -m venv cryptProject
# . cryptProject/bin/activate
(cryptProject) [root@localhost ~]# 

(cryptProject) [root@localhost ~]# python -m pip install --upgrade pip
(cryptProject) [root@localhost ~]# python -m pip list
Package    Version
---------- -------
pip        23.0.1
setuptools 41.6.0

pass.c -> pass.py 변경하기
(cryptProject) [root@localhost ~]# vi pass.py 
#!/usr/bin/env python
"""
파일명: pass.py
프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
작성자: 리눅스마스터넷
"""

import crypt
pPass = "111111"        # 비밀번호
pSalt = "$6$qh/lKXOk$"  # /etc/shadow 파일에서 각자 자신의 설정된 값으로 설정한다.
print(f"passuser:{crypt.crypt(pPass,pSalt)}")

(cryptProject) [root@localhost ~]# tail -1 /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::

(cryptProject) [root@localhost ~]# chmod 755 pass.py
(cryptProject) [root@localhost ~]# ./pass.py
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.
(cryptProject) [root@localhost ~]# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::



pass4.c -> pass4.py 변경하기
(cryptProject) [root@localhost ~]# vi pass4.py 
#!/usr/bin/env python
"""
파일명: pass4.py
프로그램 설명: crypt() 함수를 이용한 해쉬값 확인하기
작성자: 리눅스마스터넷
"""

import crypt
import sys

argc = len(sys.argv)

if argc != 3:
    print(f"Usage: {sys.argv[0]} password salt", file=sys.stderr)
    sys.exit(1)

pPass = sys.argv[1]
pSalt = "$6$" + sys.argv[2]
pHash = crypt.crypt(pPass,pSalt)
print(f"passuser:{pHash}")

(cryptProject) [root@localhost ~]# chmod 755 pass4.py 
(cryptProject) [root@localhost ~]# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::

(cryptProject) [root@localhost ~]# ./pass4.py
Usage: ./pass4.py password salt
(cryptProject) [root@localhost ~]# ./pass4.py 111111
Usage: ./pass4.py password salt
(cryptProject) [root@localhost ~]# ./pass4.py 111111 qh/lKXOk
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.

실습> 사전 파일 공격 프로그램 작성하기

1. 사용자 생성
passuser 사용자를 생성한다.
# useradd passuser
# echo 111111 | passwd --stdin passuser

2. 비밀번호 확인
passuser 계정의 비번을 확인한다.
# grep passuser /etc/shadow
passuser:$6$LSOx7Bx.$O2KVaIXsboxexxrLxfp2jayeXt.YBCbbwQmz5n5zEiIkDgROxnRmplahQm6wVD8KHJbbNbywhlvINOhC9sLJq1:19093:0:99999:7:::

3. 프로그램 작성
vi에서 붙여넣기할 때 :set noai, :set paste를 설정하고 붙여넣는다.
# vi dictAttack.c 
/*
 * 파일명: dictAttack.c
 * 프로그램 설명: 사전파일 공격
 * 작성자: 리눅스마스터넷 
 * 컴파일: gcc -m32 -g -o dictAttack dictAttack.c -lcrypt
 */

#include <stdio.h>
#include <string.h>
#include <crypt.h>

int main()
{
    // /etc/shadow에서 각자의 비밀번호 값으로 채운다.
    // grep passuser /etc/shadow
    // passuser 비밀번호: 111111
    char hash[] = "$6$LSOx7Bx.$O2KVaIXsboxexxrLxfp2jayeXt.YBCbbwQmz"
                  "5n5zEiIkDgROxnRmplahQm6wVD8KHJbbNbywhlvINOhC9sLJq1";
    char salt[] = "$6$LSOx7Bx.";
    char word[20] = "\0";
    char *result;
    int count = 1;
    int found = 0;

    FILE *fp = fopen("dict.txt","r");
    if(fp == NULL)
    {   
        fprintf(stderr, "[-] dict.txt 파일이 없습니다.\n");
        return 1;
    }   

    while(fscanf(fp,"%s",word) != EOF)
    {

#ifdef DEBUG
        printf("Debug: %s\n", word);
#endif
        result = crypt(word, salt);
        if(strcmp(result, hash) == 0)  // 비교해서 맞으면
        {
            printf(">>> count : %d <<<\n", count);
            printf("[*] password is: %s\n" , word);
            found = 1;
            break;
        }
        count++;
    }

    if(!found)  // 비밀번호를 찾지 못했다면
        printf("password not found!\n");

    fclose(fp);

    return 0;
}

4. 컴파일/실행
# gcc -g -o dictAttack dictAttack.c -DDEBUG -lcrypt
# ./dictAttack
[-] dict.txt 파일이 없습니다.

5. 사전 파일 생성
dict.txt 이름으로 사전 파일을 생성한다.
# vi dict.txt
1
11
111
1111
11111
111111
1111111
a
aa
aaa

6. 실행
# ./dictAttack 
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111

# gcc -m32 -g -o dictAttack dictAttack.c -lcrypt
# ./dictAttack 
>>> count : 6 <<<
[*] password is: 111111

7. 비번 변경
# echo aaa | passwd --stdin passuser
# grep passuser /etc/shadow
passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::

실습> 디버깅으로 분석하기

# gdb dictAttack
Reading symbols from /root/dictAttack...done.
(gdb)
(gdb) b main
Breakpoint 1 at 0x804860c: file dictAttack.c, line 17.
(gdb) r
Starting program: /root/dictAttack

Breakpoint 1, main () at dictAttack.c:17
17          char hash[] = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51"
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.i686 nss-softokn-freebl-3.79.0-4.el7_9.i686
(gdb) n
19          char salt[] = "$6$qh/lKXOk$";
(gdb) n
20          char word[20] = "\0";
(gdb) n
22          int count = 1;
(gdb) n
23          int found = 0;
(gdb) n
25          FILE *fp = fopen("dict.txt","r");
(gdb) p hash
$1 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) p salt
$2 = "$6$qh/lKXOk$"
(gdb) p count
$3 = 1
(gdb) p found
$4 = 0
(gdb) p fp
$5 = (FILE *) 0xffffd544
(gdb) p *fp
$6 = {_flags = -10605, _IO_read_ptr = 0x0, _IO_read_end = 0xffffd6a4 "MANPATH=/opt/rh/rh-python38/root/usr/share/man:",
  _IO_read_base = 0xffffd6d4 "XDG_SESSION_ID=1", _IO_write_base = 0xffffd6e5 "HOSTNAME=localhost.localdomain",
  _IO_write_ptr = 0xffffd704 "SELINUX_ROLE_REQUESTED=", _IO_write_end = 0xffffd71c "TERM=xterm",
  _IO_buf_base = 0xffffd727 "SHELL=/bin/bash", _IO_buf_end = 0xffffd737 "HISTSIZE=1000",
  _IO_save_base = 0xffffd745 "SSH_CLIENT=200.200.200.1 50137 22", _IO_backup_base = 0xffffd767 "SELINUX_USE_CURRENT_RANGE=",
  _IO_save_end = 0xffffd782 "X_SCLS=rh-python38 ", _markers = 0xffffd796, _chain = 0xffffd7a9, _fileno = -10317, _flags2 = -10               266,
  _old_offset = -8830, _cur_column = 56718, _vtable_offset = -1 '\377', _shortbuf = "\377", _lock = 0xffffddad, _offset = -369               36718754361,
  __pad1 = 0xffffde76, __pad2 = 0xffffde80, __pad3 = 0xffffde91, __pad4 = 0xffffdeaa, __pad5 = 4294958771, _mode = -8502,
  _unused2 = "\322\336\377\377\335\336\377\377\352\336\377\377\067\337\377\377k\337\377\377\250\337\377\377\313\337\377\377\00               0\000\000\000 \000\000\000 \224\375", <incomplete sequence \367>}
(gdb) n
26          if(fp == NULL)
(gdb) p *fp
$7 = {_flags = -72539000, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0               x0,
  _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0,
  _markers = 0x0, _chain = 0xf7f9d980 <_IO_2_1_stderr_>, _fileno = 7, _flags2 = 0, _old_offset = 0, _cur_column = 0,
  _vtable_offset = 0 '\000', _shortbuf = "", _lock = 0x804b0a0, _offset = -1, __pad1 = 0x0, __pad2 = 0x804b0ac, __pad3 = 0x0,                __pad4 = 0x0,
  __pad5 = 0, _mode = 0, _unused2 = '\000' <repeats 39 times>}
(gdb) p fp
$8 = (FILE *) 0x804b008
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38              result = crypt(word, salt);
(gdb) p word
$9 = "1", '\000' <repeats 18 times>
(gdb) x/16xw &word
0xffffd3fc:     0x00000031      0x00000000      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x71243624      0x4b6c2f68      0x246b4f58
0xffffd41c:     0x24362400      0x6c2f6871      0x6b4f584b      0x4f556824
0xffffd42c:     0x4c4b3051      0x35305759      0x465a654f      0x766b6b4f
(gdb) n
39              if(strcmp(result, hash) == 0)  // 비교해서 맞으면
(gdb) p result
$10 = 0x804b170 "$6$qh/lKXOk$ZxAnywAnw6ypFIMpdThNUIel/ijRDX.kn4kHVVVLzyXouhUczi/PmjRCcDo3clbV59CTtKH3/tBC1ZSNnqTJ8/"
(gdb) p hash
$11 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46              count++;
(gdb) p count
$12 = 1
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)
(gdb) p count
$13 = 2
(gdb) n
38              result = crypt(word, salt);
(gdb) p word
$14 = "11", '\000' <repeats 17 times>
(gdb) x/16xw word
0xffffd3fc:     0x00003131      0x00000000      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x71243624      0x4b6c2f68      0x246b4f58
0xffffd41c:     0x24362400      0x6c2f6871      0x6b4f584b      0x4f556824
0xffffd42c:     0x4c4b3051      0x35305759      0x465a654f      0x766b6b4f
(gdb) x/16xw count
0x2:    Cannot access memory at address 0x2
(gdb) n
39              if(strcmp(result, hash) == 0)  // 비교해서 맞으면
(gdb) n
46              count++;
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38              result = crypt(word, salt);
(gdb) x/16xw word
0xffffd3fc:     0x00313131      0x00000000      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x71243624      0x4b6c2f68      0x246b4f58
0xffffd41c:     0x24362400      0x6c2f6871      0x6b4f584b      0x4f556824
0xffffd42c:     0x4c4b3051      0x35305759      0x465a654f      0x766b6b4f
(gdb) n
39              if(strcmp(result, hash) == 0)  // 비교해서 맞으면
(gdb) p result
$15 = 0x804b170 "$6$qh/lKXOk$D9nudUorSMOgfT6jCp3IIAAF.5V3MpiIGepdDcUYBU2NxSREtu.ea34tNuNGbuv.kQ3pEYfxwOs4oyG0nkcVX/"
(gdb) p hash
$16 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46              count++;
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38              result = crypt(word, salt);
(gdb) x/16xw word
0xffffd3fc:     0x31313131      0x00000000      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x71243624      0x4b6c2f68      0x246b4f58
0xffffd41c:     0x24362400      0x6c2f6871      0x6b4f584b      0x4f556824
0xffffd42c:     0x4c4b3051      0x35305759      0x465a654f      0x766b6b4f
(gdb) n
39              if(strcmp(result, hash) == 0)  // 비교해서 맞으면
(gdb) p result
$17 = 0x804b170 "$6$qh/lKXOk$SD3Hk0oz4kag5rgWfcl7c7DxMxP1gP/Mdhq8p1fzH3Ucpa2WTAZ/zUTFywgyEDa5NUVKjdhPp4mPFEk.lz/4z."
(gdb) p hash
$18 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46              count++;
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)
(gdb) n
38              result = crypt(word, salt);
(gdb) p word
$19 = "11111", '\000' <repeats 14 times>
(gdb) x/16xw word
0xffffd3fc:     0x31313131      0x00000031      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x71243624      0x4b6c2f68      0x246b4f58
0xffffd41c:     0x24362400      0x6c2f6871      0x6b4f584b      0x4f556824
0xffffd42c:     0x4c4b3051      0x35305759      0x465a654f      0x766b6b4f
(gdb) n
39              if(strcmp(result, hash) == 0)  // 비교해서 맞으면
(gdb) p result
$20 = 0x804b170 "$6$qh/lKXOk$nDdR6a0lGDN.ogb7RE5JuKLeYWUeQ/piuem78tNPug7mZnTnDuquWxgtL.RaiKGAqfyRGuiUoUJV4aPn6oDzs/"
(gdb) p hash
$21 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
46              count++;
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)
(gdb) p count
$22 = 6
(gdb) n
38              result = crypt(word, salt);
(gdb) x/16xw word
0xffffd3fc:     0x31313131      0x00003131      0x00000000      0x00000000
0xffffd40c:     0x00000000      0x71243624      0x4b6c2f68      0x246b4f58
0xffffd41c:     0x24362400      0x6c2f6871      0x6b4f584b      0x4f556824
0xffffd42c:     0x4c4b3051      0x35305759      0x465a654f      0x766b6b4f
(gdb) n
39              if(strcmp(result, hash) == 0)  // 비교해서 맞으면
(gdb) x/16xw result
0x804b170:      0x71243624      0x4b6c2f68      0x246b4f58      0x514f5568
0x804b180:      0x594c4b30      0x4f353057      0x4f465a65      0x42766b6b
0x804b190:      0x67577249      0x7a78664e      0x4537772e      0x684a3176
0x804b1a0:      0x432e3135      0x5239424b      0x66314d57      0x2e4f6f51
(gdb) p result
$23 = 0x804b170 "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) p hash
$24 = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(gdb) n
41                  printf(">>> count : %d <<<\n", count);
(gdb) p $eax
$25 = 0
(gdb) n
>>> count : 6 <<<
42                  printf("[*] password is: %s\n" , word);
(gdb) p count
$26 = 6
(gdb) n
[*] password is: 111111
43                  found = 1;
(gdb) p found
$27 = 0
(gdb) n
44                  break;
(gdb) p found
$28 = 1
(gdb) n
49          if(!found)  // 비밀번호를 찾지 못했다면
(gdb) n
52          fclose(fp);
(gdb) n
54          return 0;
(gdb) c
Continuing.
[Inferior 1 (process 2067) exited normally]
(gdb) q

실습> gdb 를 이용한 분석

사전 파일인 dict.txt 파일이 없을 경우
fp 변수에 NULL 포인터(0x00000000)가 리턴된다.

1. 컴파일
# gcc -m32 -g -o dictAttack dictAttack.c -DDEBUG -lcrypt
# ./dictAttack
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111

2. 사전 파일 이름 변경
dict.txt 파일을 dict.txt.bak 파일로 이름을 변경한다.
# mv dict.txt dict.txt.bak

dict.txt 파일이 존재하지 않으므로 에러를 출력하고 프로세스를 종료한다.
# ./dictAttack
[-] dict.txt 파일이 없습니다.

3. gdb 분석
# gdb dictAttack
Reading symbols from /root/dictAttack...done.
(gdb) b main
Breakpoint 1 at 0x804860c: file dictAttack.c, line 17.
(gdb) r
Starting program: /root/dictAttack

Breakpoint 1, main () at dictAttack.c:17
17          char hash[] = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51"
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.i686 nss-softokn-freebl-3.79.0-4.el7_9.i686
(gdb) n
19          char salt[] = "$6$qh/lKXOk$";
(gdb) n
20          char word[20] = "\0";
(gdb) n
22          int count = 1;
(gdb) n
23          int found = 0;
(gdb) n
25          FILE *fp = fopen("dict.txt","r");
(gdb) n
26          if(fp == NULL)

(gdb) p sizeof(fp)
$1 = 4

dict 파일이 없으면 NULL 포인터(32bit 이므로 0x00000000) 가 저장된다.
(gdb) x/xw &fp
0xffffd484:     0x00000000

(gdb) n
28              fprintf(stderr, "[-] dict.txt 파일이 없습니다.\n");
(gdb) n
[-] dict.txt 파일이 없습니다.
29              return 1;
(gdb) n
55      }

eax 레지스터에 리턴값이 저장되므로 확인한다.
(gdb) p $eax
$2 = 1
(gdb) c
Continuing.
[Inferior 1 (process 2148) exited with code 01]
(gdb) q

실습> gdb 를 이용한 분석

사전 파일인 dict.txt 파일이 있을 경우
fp 변수에 어떤 주소가 리턴된다.

1. 사전 파일 이름 변경
# mv dict.txt.bak dict.txt
# ./dictAttack
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111

2. gdb 분석
# gdb dictAttack
Reading symbols from /root/dictAttack...done.
(gdb) b main
Breakpoint 1 at 0x804860c: file dictAttack.c, line 17.
(gdb) r
Starting program: /root/dictAttack

Breakpoint 1, main () at dictAttack.c:17
17          char hash[] = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51"
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.i686 nss-softokn-freebl-3.79.0-4.el7_9.i686
(gdb) n
19          char salt[] = "$6$qh/lKXOk$";
(gdb)
20          char word[20] = "\0";
(gdb)
22          int count = 1;
(gdb)
23          int found = 0;
(gdb)
25          FILE *fp = fopen("dict.txt","r");
(gdb)
26          if(fp == NULL)

파일이 존재할 경우 파일 포인터 fp 변수에 어떤 값이 저장된다.
(gdb) x/xw fp
0x804b008:      0xfbad2488

if(fp == NULL) 이 거짓이므로 그 다음 코드인 while문 쪽으로 이동한 것이다.
(gdb) n
32          while(fscanf(fp,"%s",word) != EOF)

(gdb) c
Continuing.
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
>>> count : 6 <<<
[*] password is: 111111
[Inferior 1 (process 2174) exited normally]
(gdb) q


# vi dictAttack2.c 
/*
 * 파일명: dictAttack2.c
 * 프로그램 설명: 사전파일 공격
 * 작성자: 리눅스마스터넷
 * 컴파일: gcc -g -o dictAttack dictAttack.c -lcrypt
 */

#include <stdio.h>
#include <string.h>
#include <crypt.h>

int main()
{
    // /etc/shadow에서 가져와서 각자의 값으로 채운다.
    char hash[] = "$6$5WdPl43d$Xzv9gUyXaiA5iT4mDyHLloUt/b9sUsGu1p/3rC0Pi"
                  "kFteQ8K2vp1x7vJuGS6zOIsT4g824FrZ/wPHeQYR281G0";
    char key[]  = "$6$5WdPl43d$";
    char word[20] = "\0";
    char *result;
    int count = 1;
    int found = 0;

    FILE *fp = fopen("dict.txt","r");  // 파일 열기
    if(fp == NULL)  // 파일이 없다면
    {   
        fprintf(stderr, "[-] dict.txt 파일이 없습니다.\n");  // 에러를 출력하고 
        return 1;  // 프로세스를 종료한다.
    }   

    while(fscanf(fp,"%s",word) != EOF)  // dict.txt 파일을 한 줄 읽어서 word에 저장한다.
    {

#ifdef DEBUG
        printf("Debug: %s\n", word);                                                                   
#enddef
        result = crypt(word, key);      // 암호화해서 나온 결과를 result 변수에 저장한다.
        if(strcmp(result, hash) == 0)   // 저장된 해쉬값과 result가 맞으면
        {
            found = 1;
            printf(">>> count : %d <<<\n", count);   // count를 출력한다.
            printf("[*] password is: %s\n" , word);  // 암호를 출력한다.
            break;  // while문 탈출
        }
    
        count++;  // 카운트 증가
    }

    fclose(fp);  // 파일 닫기

    if(!found)   // 비밀번호를 못찾으면
    {   
        printf(">>> count : %d <<<\n", --count);
        printf("[-] password not found!!!\n");
    }  

    return 0;
}

# gcc -o dictAttack2 dictAttack2.c -DDEBUG -lcrypt
# ./dictAttack2 
Debug: 1
Debug: 11
Debug: 111
Debug: 1111
Debug: 11111
Debug: 111111
Debug: 1111111
Debug: a
Debug: aa
Debug: aaa
>>> count : 10 <<<
[*] password is: aaa

# gcc -o dictAttack2 dictAttack2.c -lcrypt
# ./dictAttack2 
>>> count : 10 <<<
[*] password is: aaa

실습> dictAttack.c를 파이썬으로 변경하기

파일명: dictAttack.c -> dictAttack.py

1. 예외처리가 안된 코드
파이썬으로 파일 읽기
# vi fileOpen.py
#!/usr/bin/env python
"""
파일명: fileOpen.py
프로그램 설명: 파이썬으로 파일 읽기(예외처리가 없는 경우)
작성자: 리눅스마스터넷
"""

count = 1
f = open("dict.txt", "rt")
for word in f:
    print(f"{count} ", end='')
    print(word, end='')
    count += 1

f.close()

# mv dict.txt dict.txt.bak
# chmod 755 fileOpen.py
# ./fileOpen.py
Traceback (most recent call last):
  File "./fileOpen.py", line 9, in <module>
    f = open("dict.txt", "rt")
FileNotFoundError: [Errno 2] No such file or directory: 'dict.txt'

2. 예외처리가 된 코드
파이썬으로 파일 읽기
# vi fileOpen.py
#!/usr/bin/env python
"""
파일명: fileOpen.py
프로그램 설명: 파이썬으로 파일 읽기(예외처리가 있는 경우)
작성자: 리눅스마스터넷
"""

count = 1
try:
    f = open("dict.txt", "rt")
    for word in f:
        print(f"{count} ", end='')
        print(word, end='')
        count += 1
except FileNotFoundError as e:
    print(e)
else:
    f.close()

# ./fileOpen.py
[Errno 2] No such file or directory: 'dict.txt'

실습> dictAttack.py 파일 생성하기

1. 소스코드
open ~ close 를 이용한 경우
dictAttack.py 파일을 생성한다.
(cryptProject) [root@localhost ~]# vi dictAttack.py 
#!/usr/bin/env python
"""
파일명: dictAttack.py
프로그램 설명: 사전파일 공격
작성자: 리눅스마스터넷
"""

import sys
import crypt

# /etc/shadow에서 가져와서 각자의 값으로 채운다.
# grep passuser /etc/shadow
# passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
salt  = "$6$qh/lKXOk$"
count = 1
found = 0
DEBUG = 0

try:
    fp = open("dict.txt","rt")

    for word in fp:
        word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.

        if DEBUG:
            print(f"Debug: {word}")

        result = crypt.crypt(word, salt)

        if result == hash:  # 비교해서 맞으면
            print(f">>> count : {count} <<<")
            print(f"[*] password is: {word}")
            found = 1
            break
        count += 1

except FileNotFoundError as e:
    #print(e)
    print("[-] dict.txt 파일이 없습니다.\n", file=sys.stderr)
else:
    if not found:  # 비밀번호를 찾지 못했다면
        print("password not found!")
    fp.close()

(cryptProject) [root@localhost ~]# chmod 755 dictAttack.py
(cryptProject) [root@localhost ~]# ./dictAttack.py
[-] dict.txt 파일이 없습니다.

(cryptProject) [root@localhost ~]# mv dict.txt.bak dict.txt
(cryptProject) [root@localhost ~]# ./dictAttack.py 
>>> count : 6 <<<
[*] password is: 111111

2. 소스코드
with문을 이용한 경우

(cryptProject) [root@localhost ~]# vi dictAttack2.py 
#!/usr/bin/env python
"""
파일명: dictAttack2.py
프로그램 설명: 사전파일 공격
작성자: 리눅스마스터넷
"""

import sys
import crypt

# /etc/shadow에서 가져와서 각자의 값으로 채운다.
# grep passuser /etc/shadow
# passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
salt  = "$6$qh/lKXOk$"
count = 1
found = 0
DEBUG = 0

try:
    with open("dict.txt","rt") as fp: 
        for word in fp:
            if DEBUG:
                print(f"Debug: {word}", end='')

            word = word.replace('\n', '')  # 한 줄의 엔터를 제거한다.
            result = crypt.crypt(word, salt)

            if result == hash:  # 비교해서 맞으면
                print(f">>> count : {count} <<<")
                print(f"[*] password is: {word}")
                found = 1
                break
            count += 1

except FileNotFoundError as e:
    #print(e)
    print("[-] dict.txt 파일이 없습니다.\n", file=sys.stderr)
else:
    if not found:  # 비밀번호를 찾지 못했다면
        print("password not found!")

(cryptProject) [root@localhost ~]# chmod 755 dictAttack2.py
(cryptProject) [root@localhost ~]# ./dictAttack2.py 
>>> count : 6 <<<
[*] password is: 111111

실습> python 디버깅

pdb: gdb 하고 비슷한 텍스트형태의 디버깅 툴

(cryptProject) [root@localhost ~]# python -m pdb dictAttack.py
> /root/dictAttack.py(2)<module>()
-> """
(Pdb) list 1, 100
  1     #!/usr/bin/env python
  2  -> """
  3     파일명: dictAttack.py
  4     프로그램 설명: 사전파일 공격
  5     작성자: 리눅스마스터넷
  6     """
  7
  8     import sys
  9     import crypt
 10
 11     # /etc/shadow에서 가져와서 각자의 값으로 채운다.
 12     # grep passuser /etc/shadow
 13     # passuser:$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.:19435:0:99999:7:::
 14     hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
 15     salt  = "$6$qh/lKXOk$"
 16     count = 1
 17     found = 0
 18     DEBUG = 0
 19
 20     try:
 21         fp = open("dict.txt","rt")
 22
 23         for word in fp:
 24             word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
 25
 26             if DEBUG:
 27                 print(f"Debug: {word}")
 28
 29             result = crypt.crypt(word, salt)
 30
 31             if result == hash:  # 비교해서 맞으면
 32                 print(f">>> count : {count} <<<")
 33                 print(f"[*] password is: {word}")
 34                 found = 1
 35                 break
 36             count += 1
 37
 38     except FileNotFoundError as e:
 39         #print(e)
 40         print("[-] dict.txt 파일이 없습니다.\n", file=sys.stderr)
 41     else:
 42         if not found:  # 비밀번호를 찾지 못했다면
 43             print("password not found!")
 44         fp.close()
 45
[EOF]
(Pdb)
(Pdb) b 14
Breakpoint 1 at /root/dictAttack.py:14
(Pdb) r
> /root/dictAttack.py(14)<module>()
-> hash = "$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d."
(Pdb) n
> /root/dictAttack.py(15)<module>()
-> salt  = "$6$qh/lKXOk$"
(Pdb) n
> /root/dictAttack.py(16)<module>()
-> count = 1
(Pdb) n
> /root/dictAttack.py(17)<module>()
-> found = 0
(Pdb) n
> /root/dictAttack.py(18)<module>()
-> DEBUG = 0
(Pdb)
> /root/dictAttack.py(20)<module>()
-> try:
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) p salt
'$6$qh/lKXOk$'
(Pdb) p count
1
(Pdb) p found
0
(Pdb) p DEBUG
0
(Pdb) n
> /root/dictAttack.py(21)<module>()
-> fp = open("dict.txt","rt")
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) p fp
<_io.TextIOWrapper name='dict.txt' mode='rt' encoding='UTF-8'>
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'1\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'1'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash:  # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$ZxAnywAnw6ypFIMpdThNUIel/ijRDX.kn4kHVVVLzyXouhUczi/PmjRCcDo3clbV59CTtKH3/tBC1ZSNnqTJ8/'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb) p count
1
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) p count
2
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'11\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'11'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash:  # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$fJ2AGgP6Pfpmw1qsVRjZyftknF.e3xKFNsAGevfUrh8G5.KaMB2CeYD123mRcoXHjIDPaq5wWZn2.Xs2wWIqP.'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'111'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash:  # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$D9nudUorSMOgfT6jCp3IIAAF.5V3MpiIGepdDcUYBU2NxSREtu.ea34tNuNGbuv.kQ3pEYfxwOs4oyG0nkcVX/'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb) n
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb) p count
4
(Pdb) n
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'1111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb)
> /root/dictAttack.py(31)<module>()
-> if result == hash:  # 비교해서 맞으면
(Pdb)
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb)
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb)
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'11111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb)
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb)
> /root/dictAttack.py(31)<module>()
-> if result == hash:  # 비교해서 맞으면
(Pdb)
> /root/dictAttack.py(36)<module>()
-> count += 1
(Pdb)
> /root/dictAttack.py(23)<module>()
-> for word in fp:
(Pdb)
> /root/dictAttack.py(24)<module>()
-> word = word.replace('\n', '')  # 한 줄의 끝에 있는 엔터('\n')를 제거한다.
(Pdb) p word
'111111\n'
(Pdb) n
> /root/dictAttack.py(26)<module>()
-> if DEBUG:
(Pdb) p word
'111111'
(Pdb) n
> /root/dictAttack.py(29)<module>()
-> result = crypt.crypt(word, salt)
(Pdb) n
> /root/dictAttack.py(31)<module>()
-> if result == hash:  # 비교해서 맞으면
(Pdb) p result
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) p hash
'$6$qh/lKXOk$hUOQ0KLYW05OeZFOkkvBIrWgNfxz.w7Ev1Jh51.CKB9RWM1fQoO.Y1hf9G1x8t6fxKXuv33IUUJIlbT8SRW3d.'
(Pdb) n
> /root/dictAttack.py(32)<module>()
-> print(f">>> count : {count} <<<")
(Pdb) n
>>> count : 6 <<<
> /root/dictAttack.py(33)<module>()
-> print(f"[*] password is: {word}")
(Pdb) n
[*] password is: 111111
> /root/dictAttack.py(34)<module>()
-> found = 1
(Pdb) p found
0
(Pdb) n
> /root/dictAttack.py(35)<module>()
-> break
(Pdb) p found
1
(Pdb) n
> /root/dictAttack.py(42)<module>()
-> if not found:  # 비밀번호를 찾지 못했다면
(Pdb) n
> /root/dictAttack.py(44)<module>()
-> fp.close()
(Pdb) c
The program finished and will be restarted
> /root/dictAttack.py(2)<module>()
-> """
(Pdb) q

실습> CentOS 7에서 john the ripper를 설치하기

시스템에 비밀번호 점검하기

- 관리자가 사용하면 시스템에 취약한 비밀번호를 점검할 수 있는 점검툴로 활용할 수 있다.

소스코드로 되어있는 패키지를 설치하는 방법
압축해제 > 디렉토리 이동 > 환경설정(./configure) > 컴파일(make) > 설치(make install)

# yum -y install wget
# wget --no-check-certificate https://www.openwall.com/john/k/john-1.9.0.tar.gz
# tar xzf john-1.9.0.tar.gz 
# cd john-1.9.0/src/
# make linux-x86-64
# cd ../run/
# file john
# ./john 
# ./john --test

# useradd user1
# useradd user2
# useradd passuser
# passwd --stdin user1     <-- 비번을 111111 으로 설정한다.
# passwd --stdin user2     <-- 비번을 111111 으로 설정한다.
# passwd --stdin passuser  <-- 비번을 222222 으로 설정한다.
# ./unshadow  /etc/passwd /etc/shadow > LinuxPasswd.txt
# ./john LinuxPasswd.txt 

강력하게 설정한 비밀번호를 크랙이 쉽지 않고 쉽게 설정한 비밀번호를 아래처럼 금방 크랙이 된다.
# ./john LinuxPasswd.txt
Loaded 5 password hashes with 5 different salts (crypt, generic crypt(3) [?/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
111111           (user2)
111111           (passuser)
222222           (user1)
^C
3g 0:00:02:11 19% 2/3 0.02277g/s 259.9p/s 507.2c/s 507.2C/s wolverine2..cthulhu2
Use the "--show" option to display all of the cracked passwords reliably
Session aborted

# ./john --show LinuxPasswd.txt
user1:222222:1001:1001::/home/user1:/bin/bash
user2:111111:1002:1002::/home/user2:/bin/bash
passuser:111111:1006:1006::/home/passuser:/bin/bash

3 password hashes cracked, 2 left

# cat john.pot  
$6$FX0Croyq$/J/W3tQh4kan0XPXp4.VkVtdlZdkrTOic6rhzJS98pT9gT2h2EDec7rq4ysFKvsXx5x2nsJCim0nEUvANHtKx0:111111
$6$6PEqsDr1$UVEU66F6ONnwQXs7326ZRp7P8XgK5vJbFu7N5RswaHq8JHsE/ljpgHGScTbp0sR0UclcPxEF5MdwyE04lDBLC1:111111
$6$hqoQGAew$aHQbqsNIjIzsEMXK4xDOfvYfNHW7HVawavPteTcktoWwHbSNiIjoI0ol3SfkMrJkhMHUdupsdSHcGMK36YqRb1:222222
###########
## SELinux
###########

SELinux 를 사용하면 주체(파일, 디바이스...)에 보안 레이블을 하나 추가된다.
ex) 여행가방에 라벨

보안 레이블 확인하는 옵션
-Z --context

실습> SELinux 관련 패키지 설치

[root@webhacking ~]# yum search seinfo
[root@webhacking ~]# yum install setools-console -y
[root@webhacking ~]# yum -y install policycoreutils-python

실습> 보안 레이블 확인

1. 파일 확인
ls -Z 옵션을 이용해서 확인한다.
[root@webhacking ~]# mkdir selinuxtest; cd selinuxtest
[root@webhacking selinuxtest]# touch testfile
[root@webhacking selinuxtest]# mkdir testdir
[root@webhacking selinuxtest]# ls -Z
drwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 testdir
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 testfile
[root@webhacking selinuxtest]# ls --context
drwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 testdir
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 testfile

2. 프로세스 확인
ps -Z 옵션을 이용해서 확인한다.
# ps -Z
# ps -efZ
# ps auxZ



[root@webhacking selinuxtest]# seinfo -u
[root@webhacking selinuxtest]# seinfo -r
[root@webhacking selinuxtest]# seinfo -t  <-- 이 부분이 중요하다! 
[root@webhacking selinuxtest]# seinfo -robject_r -x

[root@webhacking selinuxtest]# pwd
/root/selinuxtest
[root@webhacking selinuxtest]# ls -Z
drwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 testdir
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 testfile

[root@webhacking selinuxtest]# seinfo -tadmin_home_t -x
   admin_home_t
      file_type
      mountpoint
      non_auth_file_type
      non_security_file_type
      polymember
      polyparent
      sandbox_typeattr_4
      sandbox_typeattr_3
      sandbox_typeattr_2
      sandbox_typeattr_1


[root@webhacking selinuxtest]# systemctl start httpd
[root@webhacking selinuxtest]# ps auxZ | grep httpd
system_u:system_r:httpd_t:s0    root        917  0.0  1.4 369016 14316 ?        Ss   09:57   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache     1331  0.0  0.7 369016  6988 ?        S    09:57   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache     1332  0.0  0.7 369016  6988 ?        S    09:57   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache     1333  0.0  0.7 369016  6988 ?        S    09:57   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache     1334  0.0  0.7 369016  6988 ?        S    09:57   0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0    apache     1335  0.0  0.7 369016  6988 ?        S    09:57   0:00 /usr/sbin/httpd -DFOREGROUND
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root 1893 0.0  0.1 116972 1012 pts/0 R+ 11:11   0:00 grep --color=auto httpd

[root@webhacking selinuxtest]# seinfo -thttpd_t -x
   httpd_t
      nsswitch_domain
      can_change_object_identity
      corenet_unlabeled_type
      domain
      kernel_system_state_reader
      netlabel_peer_type
      daemon
      syslog_client_type
      pcmcia_typeattr_7
      pcmcia_typeattr_6
      pcmcia_typeattr_5
      pcmcia_typeattr_4
      pcmcia_typeattr_3
      pcmcia_typeattr_2
      pcmcia_typeattr_1
      sepgsql_client_type
   Aliases
      phpfpm_t

실습> 웹 파일의 보안 레이블

[root@webhacking selinuxtest]# systemctl start httpd
[root@webhacking selinuxtest]# echo "SELinux TEST" > /var/www/html/setest.html
[root@webhacking selinuxtest]# cat /var/www/html/setest.html
SELinux TEST
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -dZ /var/www/html/
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html/


[root@webhacking selinuxtest]# ps -ZC httpd
LABEL                              PID TTY          TIME CMD
system_u:system_r:httpd_t:s0       917 ?        00:00:00 httpd
system_u:system_r:httpd_t:s0      1331 ?        00:00:00 httpd
system_u:system_r:httpd_t:s0      1332 ?        00:00:00 httpd
system_u:system_r:httpd_t:s0      1333 ?        00:00:00 httpd
system_u:system_r:httpd_t:s0      1334 ?        00:00:00 httpd
system_u:system_r:httpd_t:s0      1335 ?        00:00:00 httpd

httpd가 httpd_sys_content_t 의 권한이 있는 경우 (출력된다.)
[root@webhacking selinuxtest]# sesearch -A -t httpd_sys_content_t -s httpd_t -d
Found 4 semantic av rules:
   allow httpd_t httpd_sys_content_t : lnk_file { read getattr } ;
   allow httpd_t httpd_sys_content_t : dir { ioctl read getattr lock search open } ;
   allow httpd_t httpd_sys_content_t : file { ioctl read getattr lock map open } ;
   allow httpd_t httpd_sys_content_t : dir { ioctl read write getattr lock add_name remove_name search open } ;

httpd가 admin_home_t 의 권한이 없는 경우 (출력이 안된다.)
[root@webhacking selinuxtest]# sesearch -A -t admin_home_t -s httpd_t -d
   <-- 출력이 없음.


퍼미션을 확인한다.(644)
[root@webhacking selinuxtest]# ll /var/www/html/setest.html
-rw-r--r--. 1 root root 13  4월  5 11:17 /var/www/html/setest.html

[root@webhacking selinuxtest]# yum -y install lynx

웹페이지 접근
첫 번째 보안 레이블 확인: httpd가 httpd_sys_content_t 에 대해서 read 권한이 있으므로 접근 허용
두 번째 파일 퍼미션 확인: 644이므로 읽기 권한이 있으므로 접근 허용

SELinux의 타입에 대한 도메인의 접근 권한이 있기 때문에 두 번째 파일 퍼미션을 확인한 것이고 
이것도 접근할 수 있으므로 apache 웹서버가 setest.html 파일의 내용을 읽어서 클라이언트에서 전송한 것이다.
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
   SELinux TEST

SELinux Security Context 변경
chcon 사용법
Usage: chcon [OPTION]... CONTEXT FILE...
  or:  chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...
  or:  chcon [OPTION]... --reference=RFILE FILE...

Security Context 를 httpd_sys_content_t -> admin_home_t로 변경한다.
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# chcon -t admin_home_t /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 /var/www/html/setest.html

Security Context 를 변경한 후 접근할려고 했더니 Forbidden 이 나오면서 접근할 수 없다.
이유: 타입(admin_home_t)에 대한 도메인(httpd_t)의 허용된 정책이 없기 때문이다.

[root@webhacking selinuxtest]# sesearch -A -t admin_home_t -s httpd_t -d

[root@webhacking selinuxtest]#

[root@webhacking selinuxtest]# getenforce
Enforcing
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
                                   Forbidden

   You don't have permission to access /setest.html on this server.

SELinux Security Context 변경
변경하는 방법 첫 번째 (자동 변경): 
- restorecon 명령어를 사용하는 방법
- 자신의 디렉터리의 타입으로 그대로 자동으로 변경한다.
변경하는 방법 두 번째 (수동 변경): 
- chcon 명령어를 사용하는 방법
- 자신이 직접 타입을 명시해서 변경한다.

변경하는 방법 첫 번째 (자동 변경): restorecon 을 이용한다.
[root@webhacking selinuxtest]# restorecon /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# sesearch -A -t httpd_sys_content_t -s httpd_t -d
Found 4 semantic av rules:
   allow httpd_t httpd_sys_content_t : lnk_file { read getattr } ;
   allow httpd_t httpd_sys_content_t : dir { ioctl read getattr lock search open } ;
   allow httpd_t httpd_sys_content_t : file { ioctl read getattr lock map open } ;
   allow httpd_t httpd_sys_content_t : dir { ioctl read write getattr lock add_name remove_name search open } ;

[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
   SELinux TEST


변경하는 방법 두 번째 (수동 변경): chcon 을 이용한다.
[root@webhacking selinuxtest]# chcon -t admin_home_t /var/www/html/setest.html
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
                                   Forbidden

   You don't have permission to access /setest.html on this server.
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 /var/www/html/setest.html

[root@webhacking selinuxtest]# chcon -t httpd_sys_content_t /var/www/html/setest.html
[root@webhacking selinuxtest]# ls -Z /var/www/html/setest.html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/setest.html
[root@webhacking selinuxtest]# lynx --dump localhost/setest.html
   SELinux TEST

실습> PAM

pam_succeed_if.so
- if문으로 비교할 때 사용하는 모듈
반환 값
- 성공: 비교가 맞으면
- 실패: 비교가 맞지 않으면

pam_rootok.so
- root인지 아닌지 확인하는 모듈
반환값
- 성공: root 이면 
- 실패: root가 아니면
- 
pam_wheel.so
- wheel 그룹에 포함된 사용자인지 확인하는 모듈
반환값
- 성공: wheel 그룹에 포함되어 있으면
- 실패: wheel 그룹에 포함되어 있지 않으면

sufficient
- 인증 결과가 맞으면 성공을 반환하고 즉시 인증 성공 (다음 모듈을 실행하지 않음)
- 인증 결과가 틀리면 실패를 반환하고 인증에 영향을 미치지 않고 다음 모듈을 실행

requisite
- 인증 결과가 맞으면 성공을 반환하고 인증에 영향을 미치지 않고 다음 모듈을 실행
- 인증 결과가 틀리면 실패를 반환하고 즉 시 인증 실패 (다음 모듈을 실행하지 않음)

required
- 인증 결과가 맞으면 성공을 반환하고 다음 모듈을 실행하고 성공을 해야 최종 인증 결과는 성공
- 인증 결과가 틀리면 실패를 반환하고 다음 모듈을 실행하고 최종 인증 결과는 실패

# useradd test1
# useradd test2

# vi /etc/pam.d/su
  1 #%PAM-1.0
  2 auth        sufficient  pam_rootok.so
  3 # Uncomment the following line to implicitly trust users in the "wheel" group.
  4 #auth       sufficient  pam_wheel.so trust use_uid
  5 # Uncomment the following line to require a user to be in the "wheel" group.
  6 #auth       required    pam_wheel.so use_uid
  7 auth        substack    system-auth
  8 auth        include     postlogin
  9 account     sufficient  pam_succeed_if.so uid = 0 use_uid quiet
 10 account     include     system-auth
   :
   :(생략)


# su - test1
$

$ su -
암호:  <-- 암호를 입력한다.
# exit
$ exit
# 

# usermod -G wheel test1
# grep wheel /etc/group
wheel:x:10:test1


# vi /etc/pam.d/su
  1 #%PAM-1.0
  2 auth        sufficient  pam_rootok.so
  3 # Uncomment the following line to implicitly trust users in the "wheel" group.
  4 #auth       sufficient  pam_wheel.so trust use_uid
  5 # Uncomment the following line to require a user to be in the "wheel" group.
  6 auth        required    pam_wheel.so use_uid
  7 auth        substack    system-auth
  8 auth        include     postlogin
  :
  :(생략)

wheel 그룹에 포함된 사용자
# su - test1
마지막 로그인: 수  4월  5 12:12:48 KST 2023 일시 pts/1
$  <-- 2번 라인에 의해서 root 이므로 성공을 리턴했으므로 일반유저($)로 변경되었다. 

su - 를 하면 /etc/pam.d/su 파일을 검사한다.
use_uid: 현재 실행하는 사용자를 말한다.
 2 auth        sufficient  pam_rootok.so  <-- 인증이 실패되었지만  최종 인증하고는 무관하고 다음 모듈을 실행
 6 auth        required    pam_wheel.so use_uid  <-- wheel 그룹에 포함되어 있으므로 성공을 반환한다.
 7 auth        substack    system-auth    <-- root 로 로그인하는 화면이 출력되고 이 부분에 비밀번호가 맞으면 성공

$ id
uid=1001(test1) gid=1001(test1) groups=1001(test1),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ su -
암호:  <-- 비밀번호를 정확히 입력한다.
마지막 로그인: 수  4월  5 12:14:50 KST 2023 일시 pts/1
# exit
$ exit

wheel 그룹에 포함되지 않은 사용자
# su - test2
$ id
uid=1002(test2) gid=1002(test2) groups=1002(test2) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

su - 를 하면 /etc/pam.d/su 파일을 검사한다.
use_uid: 현재 실행하는 사용자를 말한다.
 2 auth        sufficient  pam_rootok.so  <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
 6 auth        required    pam_wheel.so use_uid  <-- wheel 그룹에 포함되어 있지 않으므로 실패을 반환한다.
 7 auth        substack    system-auth    <-- root 로 로그인하는 화면이 출력되고 이 부분에 비밀번호가 맞으면 성공

$ su -
암호:  <-- 비밀번호를 정확하게 입력한다.
su: 권한 부여 거부
$ exit


# vi /etc/pam.d/su
  1 #%PAM-1.0
  2 auth        sufficient  pam_rootok.so
  3 auth        sufficient  pam_succeed_if.so user = test2 quiet  <-- use_uid 가 없을 경우
   :
   :(생략)

[root@webhacking ~]# su - test1
[test1@webhacking ~]$ id
uid=1001(test1) gid=1001(test1) groups=1001(test1),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023


 2 auth        sufficient  pam_rootok.so  <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
 3 auth        sufficient  pam_succeed_if.so user = test2 quiet  <-- 변경하려는 사용자가 test2와 같으면 성공을 리턴하므로 즉시 성공
7 auth        substack    system-auth  

[test1@webhacking ~]$ su test2  <-- user = test2 와 같기 때문에 인증이 즉시 성공되었다.
[test2@webhacking test1]$ exit

[test1@webhacking ~]$ su - test1
암호:  <-- system-auth 가 실행된 것이다.


[test2@webhacking test1]$ exit
[test1@webhacking ~]$ exit

# vi /etc/pam.d/su
  1 #%PAM-1.0
  2 auth        sufficient  pam_rootok.so
  3 auth        sufficient  pam_succeed_if.so user = test2 use_uid quiet  <-- use_uid 가 있을 경우

[root@webhacking ~]# su - test2


su 명령어를 실행하는 사용자가 test2와 같으면 인증에 즉시 성공을 리턴한다.
 2 auth        sufficient  pam_rootok.so  <-- 인증이 실패되었지만 최종 인증하고는 무관하고 다음 모듈을 실행
 3 auth        sufficient  pam_succeed_if.so user = test2 use_uid quiet  <-- use_uid 가 있을 경우(즉시 성공을 리턴한다.)
[test2@webhacking ~]$ su test1
[test1@webhacking test2]$
profile
정보보안 전문가

0개의 댓글