Post

LOB gate ~ skeleton → golem

bash 버그 때문에 반드시 bash2를 사용해야 한다는 점 주의.

구축하고 나서 netconfig로 설정해주어야 telnet 접속이 가능하다.

gate gate - gate ff를 넣으면 00으로 바뀜. 왜 ff가 00으로 바뀌는건지 찾아보니 bash버그가 있다고 한다. 그래서 bash2를 사용해야 한다고.

gate - gate

gremlin gremlin - hello bof world 별거 없음. 버퍼가 작을 뿐.

gremlin - hello bof world

cobolt cobolt - hacking exposed gets를 이용해 stdin으로 받는다는 것이 차이점. 세가지 방법이 생각났는데, 1) hex값을 ASCII문자로 변경해서 복붙 2) ASCII code표 보고 입력 가능한 문자 hex값을 고려해 본 다음 가능한 메모리 공간에 적재.

  • 이건 불가능할거같다. 프로세스에 삽입하는 데이터의 위치를 내가 임의로 지정할 수 없다. 3) 파일입출력을 이용해 hex data를 바로 쓰는 방법. file descriptor를 이용하거나 pointer를 이용해서 표준 입력에 쓰는 방법.

조금 더 생각해보니 3번 방법을 파이프로 간단하게 처리할 수 있을 것 같아서

echo `perl …../goblin 해봤더니 성공.
AB 파이프를 이용해서 A의 표준출력을 B의 표준입력으로 넘길 수 있다.

cobolt - hacking exposed

goblin - hackers proof

EGGhunter 1)환경변수 포인터를 memset으로 0으로 만들어 환경변수를 쓸 수 없게함. 2)argv[1][47]이 bf인지 검사해서 bf가 아니면 exit. 저 자리는 retaddr의 첫번째 자리임. 따라서 stack으로만 ret가능.

stack 여유 공간은 40Byte이고 여기에 쉘코드 삽입하면 충분히 가능함. 쉘코드는 이렇다. 31 c0 50 68 70 61 73 73 68 2f 6d 79 2d 68 2f 62 69 6e 89 e3 50 53 89 e1 89 c2 b0 0b cd 80

근데 자꾸 안되길래 coredump로 stack을 확인해 보니 shellcode 삽입이 제대로 안되어 있었다. 잘 들어가다가 중간에 0x00000000같은게 존재하고 그 이후부터 제대로 안들어 가는 듯 했다. 레지스터를 확인해 보니 역시 eip가 stack 한 가운데의 0x00000000부분을 가리키는 상태에서 멈춰있었고. 즉 실행흐름은 스택으로 잘 넘어오긴 왔다는것.

뭐가문제일까 생각하다 보니까 삽입이 제대로 안되어 있던게 아니라 ret직후 esp가 stack의 한 가운데 0x00000000부분을 가리키고 있더라. 그래서 쉘코드가 삽입된 stack의 한 가운데에 쉘코드의 첫번째 명령어 push eax (eax는 0)를 하게되어 0x00000000으로 덮어 써버린 것.

방법1

esp를 다른 곳으로 돌리면 해결가능할 듯 해서 쉘코드 첫부분에 bc f0 fa ff bf를 추가해서 성공.

방법2

esp 주변에 데이터 삽입이 가능한 상황이니 RTL처럼 libc를 이용할 수도 있다. bf 검사 때문에 무조건 스택으로만 ret가능하므로 스택으로 ret한 후 libc로 jmp하면 해결가능할 듯. 이 방법도 2가지로 나뉜다. 1)leave시 ebp에 x-ebp가 들어가므로 x-ebp에 적당한 값을 넣고 jmp system+3으로 prologue를 건너뛰는 방법. 2)일반적인 RTL하듯 인자 넘기고 단순히 jmp system 하는 방법.

mov $0x40058ae0, %eax

jmp *%eax

b8 e0 8a 05 40

ff e0

일단 jmp하는 쉘코드는 얻어냈는데, system함수의 인자가 문제다. 환경변수를 못쓰니까. 그럼 어쨌든 쉘코드 내에서 주소를 얻어내야하고, 그 뒤에 적당한 위치에 옮겨쓰는 작업까지 해줘야한다. stack에 system함수에서 실행할 문자열을 삽입하고 push %esp로 주소를 얻어낸 다음, esp-4의 위치에 이 주소가 있어야 하므로 한번 더 push해서 esp를 올린다. 메모리에는 이렇게으로 들어간다.

| | | | | | — | — | — | — | | 주소 | 삽입전 | 삽입후 | 쉘코드 실행 후 | | af0 | | c1 c0 50 68 | | | af4 | | 70 61 73 73 | | | af8 | | 68 2f 6d 79 | | | afc | | 2d 68 2f 62 | | | b00 | | 69 6e 54 54 | | | b04 | | b8 e0 8a 05 | | | b08 | | 40 ff e0 | push된esp | | b0c | | | push된esp | | b10 | | | /bin | | b14 | | | /my- | | b18 | x-ebp | | pass | | b1c | ret | | 0 | 쉘코드를 실행하는 동안 push하면서 쉘코드를 덮어 써버려 제대로 실행이 안될 것 같아서 /bin/my-pass문자열 길이를 줄였다. aa라는 스크립트를 만든 후 ./aa를 실행하기로 했다.

goblin디렉토리에 orc권한으로 접근 불가해서 실패.

/tmp/aaa를 만들어서 다시 해봤다. 성공. 31 c0 50 68 2f 61 61 61 68 2f 74 6d 70 54 54 b8 e0 8a 05 40 ff e0

orc orc - cantata 이전 문제와 동일한 코드에 buffer hunter가 추가되었다. 그래서 ret 밑에 이전에 썼던 쉘코드를 삽입한 다음 그곳으로 ret하니 한방에 성공.

orc - cantata

wolfman wolfman - love eyuna 이번에는 argv[1] > 48 검사 코드가 추가되었다. 딱 48바이트까지 쓸 수 있어서 이전 문제 풀듯이 풀면 해결이 안된다. 쓸 수 있는건 x-ebp, ret자리 8바이트 뿐인데, ret는 어차피 retaddr적어야하니까 4byte로 어떻게 하지 생각하던차에 얘가 argc <2밖에 검사를 안한다는걸 깨달았다. 그냥 argv[2]로 쉘코드 넘기면 된다.

BOF 취약점은 retaddr을 조작할 수 있다는게 핵심이다.조작할 수만 있으면 쉘코드 삽입이야 어디로 넘기든 걍 넘기면 된다.

wolfman - love eyuna

darkelf darkelf - kernel crashed argv[0] 길이 검사가 추가되었다. 스크립트를 이용해 /를 많이 출력해서 길이를 맞추거나, 심볼릭링크를 이용해 해결하면 된다.

darkelf - kernel crashed

orge - timewalker

argv[0]를 통해서 넘겨야 하는 것 밖에는 방법이 없는 것 같은데 링크가 안된다.. 쉘코드에 포함되어있는 /를 디렉토리 구분자로 인식해서 ln에서 인자 읽다가 오류난다. PATH 조회해보니 /home/orge/bin 폴더가 들어있어서 bin폴더 만들고 그안에 넣으면 /없이 실행 가능.

ssss: command not found까지 가는거 보면 system은 잘 실행되는데 거기서 ssss를 못찾는가본데 PATH 환경변수가 빠지나?? egghunter가 있다고 PATH환경변수가 빠지는 것은 아닌 것 같다. 환경변수 초기화해도 /bin/에 있는my-pass같은건 잘 실행된다. system함수를 이용해서 실행할 때 쉘이 새로 떠서 /home/orge/bin이 PATH에서 빠지는게 맞는 것 같다. 근데 그것도 좀 이상하네 환경변수 초기화했는데 my-pass를 system이 어디서 찾지?? 쉘이 새로 떠서 환경변수 다시 가져오는건가???

아무튼 위 방법대로는 안되서 그냥 / hex값 -1을 ebx에 넣고 1더하기해줘야겠다.

mov $0x61616130, %ebx sub 0x1, %ebx push %ebx bb 30 61 61 61 80 1d 01 00 00 00 54 보다시피 sub나 add나 00이 들어있어서 안된다. 1) bl을 써서 00을 제거하거나 add $0x1, %bl 80 c3 01

2) 아예 2e 61 61 61을 push 한 다음 esp 포인터로 add 1 addl $0x1, (%esp, 1) 03 04 24 01

31 c0 50 68 2e 61 61 61 03 04 24 01 68 2e 74 6d 70 03 04 24 01 54 54 b8 e0 8a 05 40 ff e0

이렇게 하니까

03 04 24 01 이후 push가 add명령어에 묶여서 제대로 instruction을 인식 못한다.

뒤의 push 하는 어셈도 추가해서 다시 objdump 해보니 addl $0x1, (%esp, 1) 83 04 24 01 같은 어셈이지만 기계어 코드가 다른 경우도 있다. 이걸로 바꿔서 성공.

troll troll - aspirin argv[1][46] == ff이면 exit한다. [47]은 bf여야하고 [46]은 ff가 아니어야함. 47이 bf여야 하니까 stack으로 ret하는게 맞긴 맞는 것 같은데 어디로 ret할 수 있을까 생각해보다 argument개수 제한이 없길래 그냥 argument 개수를 많이 써서 stack frame을 올려버렸다. “ A”를 2만개쯤 출력하니까 stack이 bffe..까지 올라간다.

troll - aspirin

vampire vampire - music world buffer초기화 하고 argv도 모두 초기화해버린다. argv가 실제로 초기화 되는지 조회해보다argv[0]은 stack의 최하단(0xbfffffff 부근)에도 저장된다는 것을 발견. 그래서 초기화해도 여기는 남아있었다. troll처럼 symlink로 파일 이름에 쉘코드 넘겨 해결.

vampire - music world

skeleton - shellcoder

ret있는 4byte 제외하고 buffer부터 stack의 최하단까지 모두 0으로 초기화해버린다. 지금까지 사용했던

  • buffer를 사용하는 방법
  • main frame아래 공간을 사용하는 방법
  • argv를 사용하는 방법
  • stack최하단의 argv[0]을 사용하는 방법 모두 불가능하다. dll injection같은 방식으로 어떻게 가능하지 않을까 싶었는데, shared library를 사용하면 가능할 것 같다.

dll injection같이 활용하려면 단순히 공유 라이브러리를 사용하는게 아니라, LD_PRELOAD를 사용하거나, ptrace를 사용해서 injection해야한다.

2016/12/19 - [System/LINUX & UNIX] - LD_PRELOAD를 이용한 so injection과 hooking. + wrapping function
* my-pass에 LD_PRELOAD를 사용해 setuid를 hooking할 수 있겠지만 이는 금지되어있다.

원래는 setUID가 걸려있어 LD_PRELOAD나 ptrace나 불가능하지만,

구식 OS에서는 LD_PRELOAD를 지정하면 $esp-1100 정도에 LD_PRELOAD로 넘긴 library의 pathname이 남는 취약점이 있다.

일단 b main하고 x/1000s $esp-1100 하니까 library pathname이 나온다. 저기 왜 저장되는건지는 모르겠지만 아무튼 이를 이용하면 된다.

* LD_PRELOAD로 지정한 library가 출력 동작을 할 경우, 출력되는 문자열이 argv로 섞여서 들어간다. 무슨말이냐면,

이런식으로 똑같이 A 47개 \xbf 넘겼는데 안되길래 gdb로 확인해보니 kka가 argv[1]로 들어가고 AAAA….\xbf가 argv[2]로 들어간다. 그래서 library를 아무런 동작도 안하도록 바꾸니까 LD_PRELOAD지정 하건 말건 똑같이 동작. 아무튼, 이건 별로 중요한건 아니다.

This post is licensed under CC BY 4.0 by the author.