本篇博客主要用题目记录一下ROP的学习
ROP的定义
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)
Defcon-qualifier-ctf-2015 r0pbaby
首先,拿到程序先运行:
大概明白程序可以做到三件事
1
.
获取libc的地址
2.
libc中任意函数的地址
3.
将buffer植入栈中
初步认为,选项三会导致栈溢出
导入IDA
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v3; // rsi@1
const char *v4; // rdi@1
__int64 v5; // rax@2
signed int v6; // eax@4
__int64 v7; // rax@12
unsigned __int64 v8; // r14@15
int v9; // er13@17
size_t v10; // r12@17
int v11; // eax@18
void *handle; // [sp+8h] [bp-448h]@1
char nptr[1088]; // [sp+10h] [bp-440h]@2
__int64 savedregs; // [sp+450h] [bp+0h]@22
setvbuf(stdout, 0LL, 2, 0LL);
signal(14, handler);
alarm(0x3Cu);
puts("\nWelcome to an easy Return Oriented Programming challenge...");
puts("Menu:");
v3 = (char *)1;
v4 = "libc.so.6";
handle = dlopen("libc.so.6", 1);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
sub_BF7(v4, v3);
LODWORD(v5) = sub_B9A(nptr, 1024LL);
if ( !v5 )
{
puts("Bad choice.");
return 0LL;
}
v3 = 0LL;
v6 = strtol(nptr, 0LL, 10);
if ( v6 != 2 )
break;
__printf_chk(1LL, "Enter symbol: ");
v3 = (char *)64;
LODWORD(v7) = sub_B9A(nptr, 64LL);
if ( v7 )
{
dlsym(handle, nptr);
v3 = "Symbol %s: 0x%016llX\n";
v4 = (const char *)1;
__printf_chk(1LL, "Symbol %s: 0x%016llX\n");
}
else
{
v4 = "Bad symbol.";
puts("Bad symbol.");
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
goto LABEL_24;
v3 = "libc.so.6: 0x%016llX\n";
v4 = (const char *)1;
__printf_chk(1LL, "libc.so.6: 0x%016llX\n");
}
if ( v6 != 3 )
break;
__printf_chk(1LL, "Enter bytes to send (max 1024): ");
sub_B9A(nptr, 1024LL);
v3 = 0LL;
v8 = (signed int)strtol(nptr, 0LL, 10);
if ( v8 - 1 > 0x3FF )
{
v4 = "Invalid amount.";
puts("Invalid amount.");
}
else
{
if ( v8 )
{
v9 = 0;
v10 = 0LL;
while ( 1 )
{
v11 = _IO_getc(stdin);
if ( v11 == -1 )
break;
nptr[v10] = v11;
v10 = ++v9;
if ( v8 <= v9 )
goto LABEL_22;
}
v10 = v9 + 1;
}
else
{
v10 = 0LL;
}
LABEL_22:
v3 = nptr;
v4 = (const char *)&savedregs;
memcpy(&savedregs, nptr, v10);
}
}
if ( v6 == 4 )
break;
LABEL_24:
v4 = "Bad choice.";
puts("Bad choice.");
}
dlclose(handle);
puts("Exiting.");
return 0LL;
}
很明显在函数memcpy(&savedregs, nptr, v10);
中,由于未检验nptr长度会导致栈溢出
函数原型
1
void memcpy(void dest, const void * src, size_t n);
由src指向地址为起始地址的连续n个字节的数据复制到以dest指向地址为起始地址的空间内。
查看saveargs的栈情况
buf应该是8位
检查防护机制
打开了NX,无法直接将shellcode直接写入栈中,这个时候就要使用我们的rop了
分析大概要构造的payload应该是
[buf] + [gadget rdi] + [“/bin/sh”的地址] + [system函数的地址]
由于是64位程序,这里gadget主要用来将”/bin/sh”这个字符串压入rdi这个64位程序用来优先放置参数的寄存器,以便执行system函数
那么首先,用ROPgadget寻找一个gadget的偏移,又由于是在本机测试,就直接在自己本机的libc.so.6 中找
则gadget的offset就为0x21102
然后查找”/bin/sh”的地址
/bin/sh的offset为0x18cd17
最后查找system函数的偏移
一开始试了各种方法,就是找不对,后来经Magican。表哥提醒,方才找到
最后写exp,一开始用的是程序libc的基址,但是奇怪的是system函数基址比程序得到的libc地址小,很奇怪;而且用程序提供的libc地址也没法pwn成功;然后直接用程序提供的system函数地址减去固定的offset就可以得到libc的基址了,
上exp:
1 | #!/usr/bin/env python |
pwn it