How to Deal With Strings in Python
TL;DR
Python에서서의 문자열 인코딩 방식을 설명하기 위해 ASCII
와 Unicode
에 대해 간단히 설명한다. Python2에서는 디폴트 인코딩 방식이 ASCII
이지만 Python3에서는 UTF-8
을 채택했다. UTF-8
은 Unicode
인코딩 방식 중 하나로 전세계에서 널리 쓰이는 인코딩 방식이다.
Python2와 Python3에서 문자열을 다루는 방식이 바뀌었으므로, payload를 작성할 때 Python2에 익숙한 player가 Python3를 사용하기 위해서는 Unicode로 인코딩한 byte 문자열과 일반 문자열의 차이를 알고 있어야 한다.
ASCII와 Unicode
ASCII
와 Unicode
에 대해 간단히 알아보자.
아스키 (ASCII, American Standard Code for Information Interchange)
- 1960년대 미국에서 정의한 표준화한 부호체계
ASCII 코드
는 7bit 즉, 128(=2^7)개의 고유한 값만 사용한다. 컴퓨터의 기본 저장 단위는 1byte(=8bits)인데, ASCII 코드
는 7bit만 사용한다. 나머지 1bit는 Parity Bit인데, 이는 통신 에러 검출을 의미한다.
10진수로 0부터 127, 16진수로 0x00부터 0x7F의 범위로 문자열 Char 즉, 고유값을 표현할 수 있다.
7bit로 표현할 수 있는 ASCII 코드
는 ‘영문’만을 표현할 수 있다. 다른 언어를 표현하기에는 7bit로는 역부족이었다. 그래서 8bits로 확장한 아스키 코드가 등장했는데, 이를 ANSI 코드
라고 부르기로 했다더라.
ANSI 코드
는 8bits로 표현할 수 있으니 256(=2^8)개의 값을 쓸 수 있는데, 여전히 ‘한글’을 포함한 비유럽 국가의 문자를 표현하기에는 역부족이었다. 그래서 유니코드 (Unicode)
라는 전 세계 언어의 문자를 정의하기 위한 국제 표준 코드가 등장했다.
유니코드 (Unicode)
유니코드
는 ASCII 코드
나 ANSI 코드
보다 훨씬 크게 용량을 2byte(=2^16, 65536)로 확장한 코드이다. 무려 65536(=2^16)개나 문자를 저장할 수 있었다. 참고로 유니코드 3.0버전
까지는 이를 기본 언어판 (BMP, Basic Multilingual Plane)
이라고 불렀다. 비트맵이 아니다.
0x0000부터 0xFFFF까지 표현이 가능하다.
하지만 이는 또 세상의 모든 언어를 감당하기가 힘들었다. 그래서 유니코드 3.0버전
부터 보충 언어판 (Supplementary Plane)
을 정의했다. 이때 한자가 가장 많이 할당받았다고 한다. 그런데 이것은 bit로 어떻게 표현할 수 있을까?
사실 기본 언어판
에는 대행코드 영역이란 것이 존재한다. 상위 대행코드와 하위 대행코드를 조합해서 보충 언어판
의 문자를 표현한다. 즉, 2byte가 꼭 한 개의 문자를 의미하는 것은 아니다. 한개를 읽어보고 대행코드이면 그 다음 한개를 더 읽어 한 문자를 완성한다.
기본 언어판
의 대행코드 영역은 아래와 같다.
1 | 상위 대행코드 (High Surrogates) : D800 - DBFF |
정리하자면 유니코드는 2byte로만 표현할 수 있다.
는 완전히 틀린 소리이다. 결국 유니코드
는 모든 문자에 index(Unidocde code point, 고유한 값)를 부여한 것이다.
1 | 'A'라는 글자는 0x0041(U+0041)이라는 index를 가진다. |
참고로 U+
라는 접두어가 붙어있으면 유니코드
라는 의미이다. U+0000 ~ U+007F
로 ASCII
문자를 모두 표현할 수 있다. 참고로 U+AC00 ~ U+D7AF
로 한글 음절을 표현할 수 있다.
유니코드를 표현하는 방법
유니코드
의 index를 표현하는 방법에 대해 알아보자. 이러한 index는 여러 형식으로 변환될 수 있는데, 그 중 개인적으로 가장 익숙한 UTF-8
인코딩을 알아봤다.
그 전에 궁금증이 있다. index를 바로 컴퓨터에 저장하면 안되는 걸까? 왜 컴퓨터에 저장하기 위해서 또 변환을 해야할까? 용량때문이다. 예를 들어, 1byte로 충분히 표현할 수 있는 A를 굳이 2byte로 저장한다면 용량 문제가 생길 것이다. 호환이 잘되고 용량을 적게 만들기 위해 유니코드의 index(숫자)를 컴퓨터에 효과적으로 할당해서 0과 1로 표현할 지 결정하는 인코딩 방식
이 필요하다.
UTF-8 (가변길이 인코딩, Unicode Transformation Format)
유니코드
가 인터페이스라면 UTF-8
은 구현체라고 할 수 있다. UTF-8
은 8bit 문자 인코딩 형식으로 유니코드
문자를 index에 따라 8bit(=1byte) ~ 4byte로 변환한다.
UTF-8
은 가변바이트를 사용하기 때문에, 1byte로 표현이 충분한 A는 0x41로 표현한다. 이는 UTF-8
과 ASCII 코드
와 영문 영역에서는 100% 호환된다는 것을 의미한다.
Code point ↔ UTF-8 conversion
First code point | Last code point | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
---|---|---|---|---|---|
U+0000 | U+007F | 0xxxxxxx | |||
U+0080 | U+07FF | 110xxxxx | 10xxxxxx | ||
U+0800 | U+FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | |
U+10000 | U+10FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
출처: https://en.wikipedia.org/wiki/UTF-8
위와 같이 유니코드
범위에 따라 UTF-8
인코딩의 byte 개수가 정해진다. 참고로 한글은 3byte로 표현된다.
Python에서서의 문자열 인코딩
컴퓨터가 저장할 수 유일한 것은 byte이다. 그래서 인코딩 즉, byte로 변환해야하는 과정을 거쳐야 한다. Python에서의 문자열 인코딩 방식을 알아보자.
Python2
Python2에서는 ASCII
가 디폴트 인코딩 방법으로 되어있다. ASCII
로는 한글을 표현할 수 없으므로, 한글을 표현하기 위해서 아래의 코드를 코드 맨 윗줄에 따로 명시해야한다.
1 | # -*- coding: utf-8 -*- |
Python3
Python3에서는 UTF-8
이 디폴트 인코딩 방법이다. 그래서 모든 문자열은 유니코드
로 처리된다. b''
는 byte임을 의미한다. 디폴트로 UTF-8
로 인코딩되어 byte 문자열로 표현했으므로 이것을 다시.decode()
하면 UTF-8
로 디코딩하여 문자열로 표현할 수 있다.
정리하면 인코딩 방식만 알면 byte 문자열을 다시 문자열로 디코딩할 수 있다는 의미다.
1 | >>> b'jir4vvit' |
요즘 Pwnable을 풀 때 Python3으로 작성하고 있다. 이것을 헷갈리고 가장 처음에 했던 실수는 아래와 같다. 지금 생각해보면 왜 이렇게 했었는지 이해가 안간다.
1 | >>> str(b'jir4vvit') |
문자열에 대한 이해를 제대로 하지 않고 저지른 실수이다. Python3니까 어디서 많이 본 b''
으로 문자열을 감싸고 이걸 str()
로 감싸서 p.send()
했었다.
여러 시도 끝에 마음편하게 byte 문자열로 payload를 구성하기로 했다.
아래처럼 문자열로 payload를 구성하면 될 때도 있고 안될 때도 있다. 되는 건 운이 좋은 때고, 안될 때는 에러가 발생한다. (안되는 경우가 더 많을 것 같다.)
1 | payload = '' |
실제로 아래와 같은 디코드가 안된다는 에러를 받았었다.
1 | Traceback (most recent call last): |
마음편히 byte로 보내면 저런 디코딩 에러를 만날 일이 없다. pwntools는 기본적으로 byte로 처리하기 때문에 payload를 아래처럼 byte로 작성해주면 정말정말 디코딩 에러를 만날 일이 없다.
1 | payload = '' |
또한 Python2와 다르게 byte 문자열과 문자열을 혼합해서 작성할 수 없다. 일관되게 작성해야 한다.
Reference
- https://whatisthenext.tistory.com/103
- https://velog.io/@zionhann/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C-%EB%AC%B8%EC%9E%90-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0
- https://finebe.tistory.com/42
- https://velog.io/@goggling/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%EC%99%80-UTF-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0
이 글을 작성할 당시엔 댓글기능이 없다. 틀린 말이나 첨언이 있다면 메일 한 통 부탁드립니다. 메일 주소는 블로그 메인 화면 에서 찾을 수 있습니다.