name = input('Enter file:')
handle = open(name)
di = dict()
for line in handle:
words = line.split()
for word in words:
di[word] = di.get(word,0) + 1
largest = -1
theword = None
for name, count in di.items() :
if count > largest :
largest = count
theword = name
print('Done', theword, largest)
딕셔너리 정렬
- 먼저, 튜플의 특성: 불변 속성, 여러 값끼리 한 번에 비교 가능, 딕셔너리 .item() 메소드는 (키,값) 쌍인 튜플의 리스트를 반환
d = {'b':1, 'a':10, 'c':22}
d.items()
# dict_items([('b', 1), ('a', 10), ('c', 22)])
sorted(d.items())
# [('a', 10), ('b', 1), ('c', 22)]
# OR
for k, v in sorted(d.items()):
print(k, v)
# a 10
# b 1
# c 22
- 값을 기준으로 정렬
c = {'a':10, 'b':1, 'c':22}
tmp = list()
for k, v in c.items() :
tmp.append( (v, k) )
print(tmp) # [(10, 'a'), (1, 'b'), (22, 'c')]
tmp = sorted(tmp)
print(tmp) # [(1, 'b'), (10, 'a'), (22, 'c')]
# 내림차순을 원할 경우
# tmp = sorted(tmp, reverse=True)
- 리스트 컴프리헨션 (List Comprehension)
c = {'a':10, 'b':1, 'c':22}
print( sorted( [ (v,k) for k,v in c.items() ] ) )
# [(1, 'b'), (10, 'a'), (22, 'c')]
가장 많이 등장한 단어 Top 10 출력
fhand = open('test.txt')
counts = {} # "단어:빈도수" 인 딕셔너리
for line in fhand:
words = line.split()
for word in words:
counts[word] = counts.get(word, 0 ) + 1
for val, key in sorted([ (v, k) in counts.items() ], reverse=True):
print(key, val)
ex) if1.sh ------------------------------------------------------------------------- #! /bin/sh if [ "woo" = "woo" ] then echo "참입니다." fi
if [ "woo" != "woo" ] then echo "참입니다." else echo "거짓입니다." fi exit 0 ------------------------------------------------------------------------- ~$ sh if1.sh
조건문에 들어가는 비교 연산자
-n "문자열" : 문자열이 NULL이 아니면 참
-z "문자열" : 문자열이 NULL이면 참
수식1 -eq 수식2 : 두 수식이 같으면 참
수식1 -ne 수식2 : 두 수식이 같지 않으면 참
수식1 -gt 수식2 : 수식1이 더 크다면 참
수식1 -ge 수식2 : 수식1이 크거나 같으면 참
수식1 -lt 수식2 : 수식1이 작으면 참
수식1 -le 수식2 : 수식1이 작거나 같으면 참
!수식 : 수식이 거짓이면 참
파일과 관련된 조건
-d 파일이름 : 디렉토리이면 참
-e 파일이름 : 파일 존재하면 참
-f 파일이름 : 일반파일이면 참
-g 파일이름 : 파일에 set-group-id가 설정되면 참
-r 파일이름 : 파일이 읽기 가능이면 참
-s 파일이름 : 크기가 0이 아니면 참
-u 파일이름 : 파일에 set-user-id가 설정되면 참
-w 파일이름 : 파일이 쓰기 가능 상태이면 참
-x 파일이름 : 파일이 실행 가능 상태이면 참
ex) if2.sh ------------------------------------------------------------------------- #! /bin/sh fname=/lib/systemd/system/httpd.service if [ -f $fname ] then head -5 $fname else echo "웹 서버가 설치되지 않았습니다." fi exit 0 ------------------------------------------------------------------------- ~$ sh if2.sh 또는 ~$ chmod +x if2.sh ~$ ./if2.sh
case~esac 문 : 여러 가지 경우의 수가 있다면 case문 (다중분기), if문은 2중분기
ex) case1.sh -------------------------------------------------------------------------- #! /bin/sh case "$1" in start) echo "시작~~";; stop) echo "중지~~";; restart) echo "다시 시작~~";; *) echo "뭔지 모름~~";; esac exit 0 -------------------------------------------------------------------------- ~$ sh case1.sh stop
ex) case2.sh -------------------------------------------------------------------------- #! /bin/sh echo "리눅스가 재미있나요? (yes/no)" read answer # 사용자 입력값 받아오기 case $answer in yes | y | Y | Yes | YES) echo "다행입니다." echo "더욱 열심히 하세요 ^^";; [nN]*) echo "안타깝네요. ㅠㅠ";; *) echo "yes 아니면 no만 입력했어야죠" exit 1;; esac exit 0 -------------------------------------------------------------------------- ~$ sh case2.sh
관계 연산자
and는 -a 또는 && 를 사용
or는 -o 또는 || 를 사용
ex) and_or.sh -------------------------------------------------------------------------- #! /bin/sh echo "보고 싶은 파일명을 입력하세요." read fname if [ -f $fname ] && [ -s $fname ] ; then head -5 $fname else echo "파일이 없거나 크기가 0입니다." fi exit 0 -------------------------------------------------------------------------- ~$ sh and_or.sh
5. 쉘 스크립트 - 반복문
for 문 for 변수 in 값1 값2 값3 ... do 반복할 문장 done
ex) for1.sh -------------------------------------------------------------------------- #! /bin/sh hap=0 for i in 1 2 3 4 5 6 7 8 9 10 # 또는 for i in 'seq 1 10' do hap='exp $hap + $i' done echo "1부터 10까지의 합: "$hap exit 0 -------------------------------------------------------------------------- ~$ sh for1.sh
ex) for2.sh -------------------------------------------------------------------------- #! /bin/sh for fname in $(ls *.sh) # 해당 명령어의 결과로 나온 파일명을 변수에 대입 do echo "--------$fname---------" head -3 $fname done exit 0 -------------------------------------------------------------------------- ~$ sh for2.sh
while 문 ex) while1.sh -------------------------------------------------------------------------- #! /bin/sh while [ 1 ] # 또는 while [ : ] do echo "CentOS 7" done exit 0 --------------------------------------------------------------------------
ex) while2.sh -------------------------------------------------------------------------- #! /bin/sh hap=0 i=1 while [ $i -le 10 ] # 또는 until [ $i -gt 10 ] : 거짓인동안 계속 반복함. do hap='expr $hap + $i' i='expr $i + 1' done echo "1부터 10까지의 합:"$hap exit 0 -------------------------------------------------------------------------- ~$ sh while2.sh
ex) while3.sh -------------------------------------------------------------------------- #! /bin/sh echo "비밀번호를 입력하세요." read mypass while [ $mypass != "1234" ] do echo "틀렸음. 다시 입력하세요." read mypass done echo "통과~~" exit 0 -------------------------------------------------------------------------- ~$ sh while3.sh
6. 쉘 스크립트 - 종료문
ex) break_continue_exit.sh -------------------------------------------------------------------------- #! /bin/sh echo "무한 반복 입력을 시작합니다.(b: break, c: continue, e: exit)" while [ 1 ]; do read input case $input in b | B) break ;; c | C) echo "continue"를 누르면 while의 조건으로 돌아감" continue ;; e | E) echo "exit를 누르면 프로그램을 완전히 종료함" exit 1 ;; esac; done echo "break를 누르면 while을 빠져나와 지금 이 문장이 출력됨." exit 0 -------------------------------------------------------------------------- ~$ sh break_continue_exit.sh
7. 쉘 스크립트 - 사용자 정의 함수
함수 기본구조 FuncName () { # body } FuncName
ex) func1.sh -------------------------------------------------------------------------- #! /bin/sh myFunction () { echo "함수 안으로 들어왔음" return # 함수 내에서만 사용되는 종료문으로 함수가 호출된 시점으로 돌아감 } echo "프로그램을 시작합니다." myFunction echo "프로그램을 종료합니다." exit 0 -------------------------------------------------------------------------- ~$ sh func1.sh
함수의 파라미터 사용 FuncName () { # $1, $2, ... 등을 사용 } FuncName $param1 $param2
BROP: 소스코드 및 바이너리가 주어지지 않은 상태에서 프로세스의 상태 또는 출력내용으로 공격을 수행하는 기법
= ROP Chain + Brute Force
무작위 대입 과정이 있어서 시간이 오래걸림..
이 기법을 이해하기 앞서 다음 두 가지 가젯에 대해 이해해야 함
STOP Gadget : main() 함수 또는 취약한 함수를 다시 실행하는 주소 (_start 주소일 수도 있고 정말 함수의 시작주소 일 수도 있으나 결과적으로 복귀주소를 이 가젯으로 변경하면 프로세스는 처음 실행했을 때의 출력값을 보인다.)
BROP Gadget : 보통 libc_csu_init 함수에 있는 pop 6개 + ret 가젯을 의미하는데, 이처럼 레지스터를 많이 쓸 수 있는 희소한 가젯을 일컫는다. 이 가젯을 찾는 이유는 주변에 0x9 그리고 0x7 오프셋으로 pop rdi ; ret 가젯과 pop rsi ; ret 가젯을 찾을 수 있기 때문이다. 두 가젯은 모두 system() 함수에서 첫번째 인자로 /bin/sh 를 넣기 위해 사용된다.
2. 공격 순서
정말 아~~무것도 주어지지 않았기 때문에 메모리 릭부터 가젯 구성에 필요한 주소까지 전부 알아내야 한다.
단계를 나름 나눠봤는데 9단계나 된다;; (무엇?)
(1) 스택 오버플로우 크기 확인
복귀주소를 변경하기 때문에 당연히 스택에서 오버플로가 날 것을 예상할 수 있다. 간혹 CTF 에서 이 크기를 알려준다고 한다.
파이썬 스크립트로 brute forcing 해서 프로세스가 죽거나 기존의 출력결과와 다른 출력값을 낸다면 스택 오버플로를 의심할 수 있다.
(2) STOP Gadget 찾기
오버플로우 크기를 알고 있기 때문에 "A" * SIZE + CodeAddress 를 페이로드로 공격했을 때, 처음 출력값이 나오면 CodeAddress 가 STOP Gadget 이 된다.
** Code 영역을 알아야 하니 checksec 같은 툴을 써서 No PIE 인지 확인하면 될 것
** 코드 세그먼트 크기가 0x1000 이므로, 보통 오프셋은 0 ~ 0x1000 이다.
당연히 Code Base Address 부터 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림
(3) BROP Gadget 찾기
pop 6개 + ret 가젯을 찾는 과정이기 때문에 페이로드는 "A" * SIZE + CodeAddress + PARAM * 6 + STOP_GADGET 이 된다. PARAM * 6 은 더미로 채우는데 32비트면 4바이트 * 6 = 24 바이트 일 것이고 64비트면 8바이트 * 6 = 48바이트 가 된다.
공격했을 때 처음 출력내용이 그대로 나오면 CodeAddress 는 BROP Gadget 이 된다.
(pop 6개 + ret 이 성공했으니 처음 출력내용이 나오는거니까)
** pwntools 에 p32() 랑 p64() 쓰면 편하게 계산가능하다.
이 가젯을 찾는 이유는 근처에 pop rdi ; ret (offset: 0x9) 과 pop rsi ; ret (0x7) 가젯이 있어서라 했다.
따라서 여기서 얻은 CodeAddress + 0x9 또는 CodeAddress + 0x7 주소를 실제로 사용한다.
당연히 Code Base Address 부터 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림
(4) Printable Function 주소 찾기
출력함수는 C 언어에서 printf 와 puts 함수가 있는데 둘의 차이는 출력했을 때 개행 문자 "\n"가 출력되는지이다.
printf 일 경우 위에서 설명한 가젯 2개를 모두 사용 --> pop rdi ; ret (BROP Gadget +0x9) 과 pop rsi ; ret (+0x7)
puts 일 경우 하나만 사용 --> pop rdi ; ret (BROP Gadget +0x9)
보통 puts 함수를 쓸거라 생각하고 brute forcing 을 한다.
페이로드는 "A" * SIZE + POP_RDI_GADGET + CodeBase + CodeAddress 이다.
CodeBase 는 코드 영역의 시작주소이고, CodeAddress는 Base 부터 오프셋 범위(0 ~ 0x1000) 내로 1씩 증가시킨다. 위 페이로드로 공격했을 때 출력결과가 "\x7fELF" 문자열을 포함한다면 CodeAddress 는 puts@plt 가 된다.
** 리눅스 실행파일은 ELF 구조라 무조건 코드영역의 첫 4바이트는 "\x7fELF"이다. 그래서 CodeBase를 인자로 주고 puts 인지 아닌지 알아낸다.
당연히 Code Base Address 부터 오프셋 1씩 늘려주면서 찾아야 한다. 역시 brute forcing 이므로 시간이 걸림
(5) 메모리 덤프
자 이제 puts@plt 를 알아냈으니 출력함수를 사용할 수 있다. 인자로 메모리 영역만 넣어주면 바이너리 정보를 제대로 얻어낼 수 있는 거다.
코드 영역만 필요하니 CodeBase ~ CodeBase + 0x1000 범위를 출력해서 덤프 파일을 만든다.
페이로드는 "A" * SIZE + POP_RDI_GADGET + CodeAddress + PUTS_PLT 가 된다.
** 계속 출력하다보면 프로세스가 처음 출력하는 문자열이 있는데 그 부분 전까지만 포함해야 한다.
보통 파이썬 코드로 data = response[:response.index(처음출력결과)] 라고 작성한다. 문자열의 index 함수는 발견못하면 -1 이라 끝까지 넣기 때문이다. 아무튼 저런식으로 data 를 계속 쌓아서 한번에 파일 출력을 해준다.