Pwn(4.1) - passcode (前)
已經不敢寫時間了笑死。
這篇裡面用了 `<details>` 來做補充說明,藍藍的斜體字應該要可以按開
另外這題太長ㄌ,所以分成兩篇寫
# passcode
他的 code 長這樣
```c
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
```
第一個發現可以操作的地方是 `welcome` 跟 `login` 的連續呼叫
我們知道呼叫函式會打開一個 stackframe ,在函式回傳的時候 stackframe 會被棄置
而函式內的變數是放在 stackframe 裡面的,所以這裡的連續呼叫就可能可以下手
`welcome` 裡面的 `name` 是一個 `char[]` 而且在下面的 `scanf()` 讀進來
也就是說這個 `name` 內容是可以操作(用輸入的字元來佈置)的
`login` 裡面的兩個變數 `passcode1` 跟 `passcode2` 沒有給過值,所以他們會是在 `login` 的 stackframe 展開之後的垃圾
運氣好的話(如果 `name` 跟 `passcode1` `passcode2` 有重疊),我們有機會利用 `name` ,在裡面放設計好的值藉此污染 `passcode1` `passcode2` ,讓他們變成任意我們設定的值
有了這樣的想法之後,我們來實際看看這個效果能不能被實現
首先我們要找出 `name` `passcode1` `passcode2` 的位置在哪裡
因為這些東西都在 stack 上,所以要看他們和 `sp` 或者 `bp` 的相對位置(也就是在 stackframe 中的位置),絕對位置是會隨著 `bp` 偏移的
我們可以透過看函式的參數來知道位置,下面用 gdb-peda 來做這件事
進入 gdb
```shell
$ gdb passcode
```
我們要看 `name` 的位置,可以選 `scanf("%100s", name);` 這行的指令來看
所以 `disas` (disassemble 的意思)
```shell
gdb-peda$ disas welcome
Dump of assembler code for function welcome:
0x08048609 <+0>: push ebp
0x0804860a <+1>: mov ebp,esp
0x0804860c <+3>: sub esp,0x88
0x08048612 <+9>: mov eax,gs:0x14
0x08048618 <+15>: mov DWORD PTR [ebp-0xc],eax
0x0804861b <+18>: xor eax,eax
0x0804861d <+20>: mov eax,0x80487cb
0x08048622 <+25>: mov DWORD PTR [esp],eax
0x08048625 <+28>: call 0x8048420 <printf@plt>
0x0804862a <+33>: mov eax,0x80487dd
0x0804862f <+38>: lea edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804863e <+53>: mov eax,0x80487e3
0x08048643 <+58>: lea edx,[ebp-0x70]
0x08048646 <+61>: mov DWORD PTR [esp+0x4],edx
0x0804864a <+65>: mov DWORD PTR [esp],eax
0x0804864d <+68>: call 0x8048420 <printf@plt>
0x08048652 <+73>: mov eax,DWORD PTR [ebp-0xc]
0x08048655 <+76>: xor eax,DWORD PTR gs:0x14
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 <__stack_chk_fail@plt>
0x08048663 <+90>: leave
0x08048664 <+91>: ret
End of assembler dump.
```
我們很快可以找到呼叫 `scanf` 的那一行,然後用 `b` 來下斷點
```shell
gdb-peda$ b *0x08048639
```
弄好了之後讓他跑起來
```shell
gdb-peda$ r
[----------------------------------registers-----------------------------------]
EAX: 0x80487dd ("%100s")
EBX: 0x0
ECX: 0x0
EDX: 0xff9a2a58 --> 0xf7f5cd80 --> 0xfbad2a84
ESI: 0xf7f5c000 --> 0x1d7d8c
EDI: 0x0
EBP: 0xff9a2ac8 --> 0xff9a2ae8 --> 0x0
ESP: 0xff9a2a40 --> 0x80487dd ("%100s")
EIP: 0x8048639 (<welcome+48>: call 0x80484a0 <__isoc99_scanf@plt>)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804862f <welcome+38>: lea edx,[ebp-0x70]
0x8048632 <welcome+41>: mov DWORD PTR [esp+0x4],edx
0x8048636 <welcome+45>: mov DWORD PTR [esp],eax
=> 0x8048639 <welcome+48>: call 0x80484a0 <__isoc99_scanf@plt>
0x804863e <welcome+53>: mov eax,0x80487e3
0x8048643 <welcome+58>: lea edx,[ebp-0x70]
0x8048646 <welcome+61>: mov DWORD PTR [esp+0x4],edx
0x804864a <welcome+65>: mov DWORD PTR [esp],eax
Guessed arguments:
arg[0]: 0x80487dd ("%100s")
arg[1]: 0xff9a2a58 --> 0xf7f5cd80 --> 0xfbad2a84
[------------------------------------stack-------------------------------------]
0000| 0xff9a2a40 --> 0x80487dd ("%100s")
0004| 0xff9a2a44 --> 0xff9a2a58 --> 0xf7f5cd80 --> 0xfbad2a84
0008| 0xff9a2a48 --> 0xff9a2ac8 --> 0xff9a2ae8 --> 0x0
0012| 0xff9a2a4c --> 0xf7df71bd (<_IO_new_do_write+29>: cmp ebx,eax)
0016| 0xff9a2a50 --> 0xf7f5cd80 --> 0xfbad2a84
0020| 0xff9a2a54 --> 0x80487f0 ("Toddler's Secure Login System 1.0 beta.")
0024| 0xff9a2a58 --> 0xf7f5cd80 --> 0xfbad2a84
0028| 0xff9a2a5c --> 0xf7df758b (<_IO_new_file_overflow+267>: add esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x08048639 in welcome ()
```
可以看到參數是用 stack 傳遞的,而且是從右往左 push 進 stack (雖然這裡沒有用 push 這個指令啦)
<details>
<summary><i style="color:blue">為什麼從右往左進 stack?</i></summary>
從右往左的原因是在那個函數裡面,只要 pop 出來就會是正確的順序,這是 x86 32bit 的做法
</details><br />
所以我們知道這次 `name` 這個參數的位置就是 `0xff9a2a58`
為了知道這個值怎麼出現的,我們需要讀讀 `scanf` 的附近幾行
```asm
0x0804862a <+33>: mov eax,0x80487dd
0x0804862f <+38>: lea edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt>
```
你可以看到 `name` 的位置本來是放在 `edx` 裡面
因為 `mov DWORD PTR [esp+0x4],edx`
這個簡單換成 c 就是 `*(esp+4) = edx;`
<details>
<summary><i style="color:blue">為什麼 <code>esp+4</code> 是 <code>name</code> 而不是 <code>"%100s"</code>?</i></summary>
可以回去觀察上面的 stack ,你會發現 <code>"%100s"</code> 在 stack 的位置是 0xff9a2a40 ,比 <code>name</code>要低 <br />
所以 <code>esp+4</code> 是 <code>name</code> 而 <code>esp</code> 是 <code>"%100s"</code>
(或者也可以透過對 stack 的了解看出來)
</details>
<details>
<summary><i style="color:blue">為什麼寫成 c 的時候 <code>DWORD PTR</code> 不見了?</i></summary>
<code>DWORD PTR</code> 是為了指定 <code>mov</code> 要移動的大小是多大,<code>DWORD</code> 指定是 4 bytes <br />
也就是說是把 <code>edx</code> 裡面的東西移進 <code>esp+0x4</code> ~ <code>esp+0x1</code> 的地方
</details><br />
而 `edx` 裡面這個地址是哪來的呢?
這裡 `lea edx,[ebp-0x70]`
`lea` (load effective address)的用法是 `lea reg, [addr]`
(你說什麼是 effective address?抱歉我也搞不懂,我只會用, SADD)
總之就是把 [] 裡面的地址放到 register 裡面,所以
`name` 的地址放在 `edx` 裡面 + `edx` 裡面放的地址是 `ebp-0x70` =
`name` 的地址是 `ebp-0x70`! 好耶!
然後我們就可以用一樣的手法看出 `passcode1` 跟 `passcode2` ㄌ
先讀一下 `login` (這裡用 disas )
```
0x08048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28
0x0804856a <+6>: mov eax,0x8048770
0x0804856f <+11>: mov DWORD PTR [esp],eax
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov eax,ds:0x804a02c
0x08048590 <+44>: mov DWORD PTR [esp],eax
0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov eax,0x8048786
0x0804859d <+57>: mov DWORD PTR [esp],eax
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: mov DWORD PTR [esp],0x8048799
0x080485c0 <+92>: call 0x8048450 <puts@plt>
0x080485c5 <+97>: cmp DWORD PTR [ebp-0x10],0x528e6
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmp DWORD PTR [ebp-0xc],0xcc07c9
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: mov DWORD PTR [esp],0x80487a5
0x080485de <+122>: call 0x8048450 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: mov DWORD PTR [esp],0x80487bd
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: mov DWORD PTR [esp],0x0
0x08048604 <+160>: call 0x8048480 <exit@plt>
```
`scanf("%d", passcode1)` 的地方:
```
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
```
可以看到這次第二個參數又是 `edx` 的值,而 `edx` 的值是 `ebp-0x10` 的值
`mov edx, DWORD PTR [ebp-0x10]` 相當於 `edx = *(ebp-0x10)`
所以可以知道 `passcode1` 的位置是 `ebp-0x10`
(因為這裡第二個參數是 `passcode1` 的值而非地址)
再看 `passcode2`
```
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
```
同上看出 `passcode2` 的位置是 `ebp-0xc`
所以我們現在三個變數的地址都有了
`name`: `ebp-0x70`
`passcode1`: `ebp-0x10`
`passcode2`: `ebp-0xc`
注意 `name` 是個長度 100 的陣列,所以我們可操控的範圍是 `ebp-0x70` ~ `ebp-0xd`
這時候 @t510599 做的 [stack 工具](https://app.stoneapp.tech/stack/)就可以派上用場啦(不用全部自己畫了,好耶!)
這個圖的最頂端是 `ebp`
![](https://i.imgur.com/jRwa4rZ.png)
可以發現只有 `passcode1` 能被安排,所以這個方法就 SADD 了
不過安排 `passcode1` 是有用的,在下篇會用到
[下篇傳送門](https://stoneapp.tech/cavern/post.php?pid=910)
2021-07-14 17:58:59
留言
Last fetch: --:--
現在還沒有留言!